Compare commits
10 Commits
2.0-beta.2
...
2.0-beta.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a2851ac4 | ||
|
|
313382fb82 | ||
|
|
b9ae337a64 | ||
|
|
4dc0a6159b | ||
|
|
e00dfe1532 | ||
|
|
28e1fcabd5 | ||
|
|
2c0496b44b | ||
|
|
89adda60ec | ||
|
|
8c3710cda3 | ||
|
|
b501b9d830 |
@@ -7,6 +7,7 @@ export default class ComputationMemo {
|
|||||||
constructor(props, creature){
|
constructor(props, creature){
|
||||||
this.statsByVariableName = {};
|
this.statsByVariableName = {};
|
||||||
this.constantsByVariableName = {};
|
this.constantsByVariableName = {};
|
||||||
|
this.constantsById = {};
|
||||||
this.extraStatsByVariableName = {};
|
this.extraStatsByVariableName = {};
|
||||||
this.statsById = {};
|
this.statsById = {};
|
||||||
this.originalPropsById = {};
|
this.originalPropsById = {};
|
||||||
@@ -77,11 +78,7 @@ export default class ComputationMemo {
|
|||||||
}
|
}
|
||||||
addConstant(prop){
|
addConstant(prop){
|
||||||
prop = this.registerProperty(prop);
|
prop = this.registerProperty(prop);
|
||||||
if (
|
this.constantsById[prop._id] = prop;
|
||||||
!this.constantsByVariableName[prop.variableName]
|
|
||||||
){
|
|
||||||
this.constantsByVariableName[prop.variableName] = prop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
registerProperty(prop){
|
registerProperty(prop){
|
||||||
this.originalPropsById[prop._id] = cloneDeep(prop);
|
this.originalPropsById[prop._id] = cloneDeep(prop);
|
||||||
|
|||||||
@@ -112,13 +112,14 @@ function combineSkill(stat, aggregator, memo){
|
|||||||
let profBonus = profBonusStat && profBonusStat.value;
|
let profBonus = profBonusStat && profBonusStat.value;
|
||||||
|
|
||||||
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
||||||
let level = memo.statsByVariableName['level'].value;
|
let levelProp = memo.statsByVariableName['level'];
|
||||||
|
let level = levelProp.value;
|
||||||
profBonus = Math.ceil(level / 4) + 1;
|
profBonus = Math.ceil(level / 4) + 1;
|
||||||
if (level._id){
|
if (levelProp._id){
|
||||||
stat.dependencies = union(stat.dependencies, [level._id]);
|
stat.dependencies = union(stat.dependencies, [levelProp._id]);
|
||||||
}
|
}
|
||||||
if (level.dependencies){
|
if (levelProp.dependencies){
|
||||||
stat.dependencies = union(stat.dependencies, level.dependencies);
|
stat.dependencies = union(stat.dependencies, levelProp.dependencies);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stat.dependencies = union(
|
stat.dependencies = union(
|
||||||
|
|||||||
@@ -3,4 +3,10 @@ import applyToggles from '/imports/api/creature/computation/engine/applyToggles.
|
|||||||
export default function computeConstant(constant, memo){
|
export default function computeConstant(constant, memo){
|
||||||
// Apply any toggles
|
// Apply any toggles
|
||||||
applyToggles(constant, memo);
|
applyToggles(constant, memo);
|
||||||
|
if (constant.deactivatedByToggle) return;
|
||||||
|
if (
|
||||||
|
!memo.constantsByVariableName[constant.variableName]
|
||||||
|
){
|
||||||
|
memo.constantsByVariableName[constant.variableName] = constant
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
export default function computeEndStepProperty(prop, memo){
|
export default function computeEndStepProperty(prop, memo){
|
||||||
|
applyToggles(prop, memo);
|
||||||
|
|
||||||
switch (prop.type){
|
switch (prop.type){
|
||||||
case 'action':
|
case 'action':
|
||||||
case 'spell':
|
case 'spell':
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { forOwn, has, union } from 'lodash';
|
import { forOwn, has, union } from 'lodash';
|
||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
|
|
||||||
export default function computeLevels(memo){
|
export default function computeLevels(memo){
|
||||||
computeClassLevels(memo);
|
computeClassLevels(memo);
|
||||||
@@ -7,11 +8,13 @@ export default function computeLevels(memo){
|
|||||||
|
|
||||||
function computeClassLevels(memo){
|
function computeClassLevels(memo){
|
||||||
forOwn(memo.classLevelsById, classLevel => {
|
forOwn(memo.classLevelsById, classLevel => {
|
||||||
|
applyToggles(classLevel, memo);
|
||||||
// class levels are mutually dependent
|
// class levels are mutually dependent
|
||||||
classLevel.dependencies = union(
|
classLevel.dependencies = union(
|
||||||
classLevel.dependencies,
|
classLevel.dependencies,
|
||||||
Object.keys(memo.classLevelsById)
|
Object.keys(memo.classLevelsById)
|
||||||
);
|
);
|
||||||
|
if (classLevel.deactivatedByToggle) return;
|
||||||
let name = classLevel.variableName;
|
let name = classLevel.variableName;
|
||||||
let stat = memo.statsByVariableName[name];
|
let stat = memo.statsByVariableName[name];
|
||||||
if (!stat){
|
if (!stat){
|
||||||
@@ -29,7 +32,7 @@ function computeClassLevels(memo){
|
|||||||
|
|
||||||
function computeTotalLevel(memo){
|
function computeTotalLevel(memo){
|
||||||
let currentLevel = memo.statsByVariableName['level'];
|
let currentLevel = memo.statsByVariableName['level'];
|
||||||
if (!currentLevel){
|
if (!currentLevel || currentLevel.deactivatedByToggle){
|
||||||
currentLevel = {
|
currentLevel = {
|
||||||
value: 0,
|
value: 0,
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import computeConstant from '/imports/api/creature/computation/engine/computeCon
|
|||||||
|
|
||||||
export default function computeMemo(memo){
|
export default function computeMemo(memo){
|
||||||
// Compute all constants that could be used
|
// Compute all constants that could be used
|
||||||
forOwn(memo.constantsByVariableName, constant => {
|
forOwn(memo.constantsById, constant => {
|
||||||
computeConstant (constant, memo);
|
computeConstant (constant, memo);
|
||||||
});
|
});
|
||||||
// Compute level
|
// Compute level
|
||||||
|
|||||||
@@ -22,28 +22,25 @@ export default function computeStat(stat, memo){
|
|||||||
// Apply any toggles
|
// Apply any toggles
|
||||||
applyToggles(stat, memo);
|
applyToggles(stat, memo);
|
||||||
|
|
||||||
if (!stat.deactivatedByToggle){
|
// Compute and aggregate all the effects
|
||||||
// Compute and aggregate all the effects
|
let aggregator = new EffectAggregator(stat, memo)
|
||||||
let aggregator = new EffectAggregator(stat, memo)
|
each(stat.computationDetails.effects, (effect) => {
|
||||||
each(stat.computationDetails.effects, (effect) => {
|
computeEffect(effect, memo);
|
||||||
computeEffect(effect, memo);
|
if (effect.deactivatedByToggle) return;
|
||||||
if (effect._id){
|
if (effect._id){
|
||||||
stat.dependencies = union(
|
|
||||||
stat.dependencies,
|
|
||||||
[effect._id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
stat.dependencies = union(
|
stat.dependencies = union(
|
||||||
stat.dependencies,
|
stat.dependencies,
|
||||||
effect.dependencies
|
[effect._id]
|
||||||
)
|
);
|
||||||
if (!effect.deactivatedByToggle){
|
}
|
||||||
aggregator.addEffect(effect);
|
stat.dependencies = union(
|
||||||
}
|
stat.dependencies,
|
||||||
});
|
effect.dependencies
|
||||||
// Conglomerate all the effects to compute the final stat values
|
)
|
||||||
combineStat(stat, aggregator, memo);
|
aggregator.addEffect(effect);
|
||||||
}
|
});
|
||||||
|
// Conglomerate all the effects to compute the final stat values
|
||||||
|
combineStat(stat, aggregator, memo);
|
||||||
// Mark the attribute as computed
|
// Mark the attribute as computed
|
||||||
stat.computationDetails.computed = true;
|
stat.computationDetails.computed = true;
|
||||||
stat.computationDetails.busyComputing = false;
|
stat.computationDetails.busyComputing = false;
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ export default function writeAlteredProperties(memo){
|
|||||||
// Loop through all properties on the memo
|
// Loop through all properties on the memo
|
||||||
forOwn(memo.propsById, changed => {
|
forOwn(memo.propsById, changed => {
|
||||||
let schema = propertySchemasIndex[changed.type];
|
let schema = propertySchemasIndex[changed.type];
|
||||||
if (!schema) return;
|
if (!schema){
|
||||||
|
console.warn('No schema for ' + changed.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let extraIds = changed.computationDetails.idsOfSameName;
|
let extraIds = changed.computationDetails.idsOfSameName;
|
||||||
let ids;
|
let ids;
|
||||||
if (extraIds && extraIds.length){
|
if (extraIds && extraIds.length){
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const damagePropertiesByName = new ValidatedMethod({
|
|||||||
damagePropertyWork({property, operation, value});
|
damagePropertyWork({property, operation, value});
|
||||||
lastProperty = property;
|
lastProperty = property;
|
||||||
});
|
});
|
||||||
recomputePropertyDependencies(lastProperty);
|
if (lastProperty) recomputePropertyDependencies(lastProperty);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustment
|
|||||||
import { ComputedOnlyAttackSchema } from '/imports/api/properties/Attacks.js';
|
import { ComputedOnlyAttackSchema } from '/imports/api/properties/Attacks.js';
|
||||||
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||||
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||||
// import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||||
|
import { ConstantSchema } from '/imports/api/properties/Constants.js';
|
||||||
import { ComputedOnlyContainerSchema } from '/imports/api/properties/Containers.js';
|
import { ComputedOnlyContainerSchema } from '/imports/api/properties/Containers.js';
|
||||||
import { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
|
import { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
|
||||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||||
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
|
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
|
||||||
import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
// import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
// import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||||
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
||||||
import { ComputedOnlySlotSchema } from '/imports/api/properties/Slots.js';
|
import { ComputedOnlySlotSchema } from '/imports/api/properties/Slots.js';
|
||||||
// import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
||||||
import { ComputedOnlySpellSchema } from '/imports/api/properties/Spells.js';
|
import { ComputedOnlySpellSchema } from '/imports/api/properties/Spells.js';
|
||||||
import { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
|
import { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
|
||||||
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
|
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
|
||||||
@@ -29,23 +30,25 @@ const propertySchemasIndex = {
|
|||||||
attack: ComputedOnlyAttackSchema,
|
attack: ComputedOnlyAttackSchema,
|
||||||
attribute: ComputedOnlyAttributeSchema,
|
attribute: ComputedOnlyAttributeSchema,
|
||||||
buff: ComputedOnlyBuffSchema,
|
buff: ComputedOnlyBuffSchema,
|
||||||
// classLevel: ClassLevelSchema,
|
classLevel: ClassLevelSchema,
|
||||||
|
constant: ConstantSchema,
|
||||||
|
container: ComputedOnlyContainerSchema,
|
||||||
damage: ComputedOnlyDamageSchema,
|
damage: ComputedOnlyDamageSchema,
|
||||||
damageMultiplier: DamageMultiplierSchema,
|
damageMultiplier: DamageMultiplierSchema,
|
||||||
effect: ComputedOnlyEffectSchema,
|
effect: ComputedOnlyEffectSchema,
|
||||||
feature: ComputedOnlyFeatureSchema,
|
feature: ComputedOnlyFeatureSchema,
|
||||||
// folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
|
item: ComputedOnlyItemSchema,
|
||||||
note: ComputedOnlyNoteSchema,
|
note: ComputedOnlyNoteSchema,
|
||||||
// proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedOnlySlotSchema,
|
propertySlot: ComputedOnlySlotSchema,
|
||||||
roll: ComputedOnlyRollSchema,
|
roll: ComputedOnlyRollSchema,
|
||||||
savingThrow: ComputedOnlySavingThrowSchema,
|
savingThrow: ComputedOnlySavingThrowSchema,
|
||||||
skill: ComputedOnlySkillSchema,
|
skill: ComputedOnlySkillSchema,
|
||||||
|
slotFiller: SlotFillerSchema,
|
||||||
spellList: ComputedOnlySpellListSchema,
|
spellList: ComputedOnlySpellListSchema,
|
||||||
spell: ComputedOnlySpellSchema,
|
spell: ComputedOnlySpellSchema,
|
||||||
toggle: ComputedOnlyToggleSchema,
|
toggle: ComputedOnlyToggleSchema,
|
||||||
container: ComputedOnlyContainerSchema,
|
|
||||||
item: ComputedOnlyItemSchema,
|
|
||||||
any: new SimpleSchema({}),
|
any: new SimpleSchema({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,38 +25,50 @@ Meteor.publish('libraries', function(){
|
|||||||
{owner: this.userId},
|
{owner: this.userId},
|
||||||
{writers: this.userId},
|
{writers: this.userId},
|
||||||
{readers: this.userId},
|
{readers: this.userId},
|
||||||
{_id: {$in: subs}},
|
{ _id: {$in: subs}, public: true },
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
sort: {name: 1}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish('library', function(libraryId){
|
||||||
|
if (!libraryId) return [];
|
||||||
|
libraryIdSchema.validate({libraryId});
|
||||||
|
this.autorun(function (){
|
||||||
|
let userId = this.userId;
|
||||||
|
let library = Libraries.findOne(libraryId);
|
||||||
|
try { assertViewPermission(library, userId) }
|
||||||
|
catch(e){
|
||||||
|
return this.error(e);
|
||||||
|
}
|
||||||
|
return Libraries.find({
|
||||||
|
_id: libraryId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let libraryIdSchema = new SimpleSchema({
|
let libraryIdSchema = new SimpleSchema({
|
||||||
libraryIds: {
|
libraryId:{
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
'libraryIds.$':{
|
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.publish('libraryNodes', function(libraryIds){
|
Meteor.publish('libraryNodes', function(libraryId){
|
||||||
libraryIdSchema.validate({libraryIds});
|
if (!libraryId) return [];
|
||||||
if (!libraryIds.length) return [];
|
libraryIdSchema.validate({libraryId});
|
||||||
this.autorun(function (){
|
this.autorun(function (){
|
||||||
let userId = this.userId;
|
let userId = this.userId;
|
||||||
for (let i in libraryIds){
|
let library = Libraries.findOne(libraryId);
|
||||||
let libraryId = libraryIds[i];
|
try { assertViewPermission(library, userId) }
|
||||||
let library = Libraries.findOne(libraryId);
|
catch(e){
|
||||||
try { assertViewPermission(library, userId) }
|
return this.error(e);
|
||||||
catch(e){
|
|
||||||
return this.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
LibraryNodes.find({
|
LibraryNodes.find({
|
||||||
'ancestors.id': {$in: libraryIds},
|
'ancestors.id': libraryId,
|
||||||
}, {
|
}, {
|
||||||
sort: {order: 1},
|
sort: {order: 1},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
right
|
right
|
||||||
clipped
|
clipped
|
||||||
>
|
>
|
||||||
<log-tab :creature-id="$route.params.id" />
|
<character-log :creature-id="$route.params.id" />
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LogTab from '/imports/ui/log/CharacterLog.vue';
|
import CharacterLog from '/imports/ui/log/CharacterLog.vue';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LogTab,
|
CharacterLog,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
drawer: {
|
drawer: {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default {
|
|||||||
}).map(slot => {
|
}).map(slot => {
|
||||||
if (
|
if (
|
||||||
!this.showHiddenSlots &&
|
!this.showHiddenSlots &&
|
||||||
slot.quantityExpected === 0 &&
|
slot.quantityExpectedResult === 0 &&
|
||||||
slot.hideWhenFull
|
slot.hideWhenFull
|
||||||
){
|
){
|
||||||
slot.children = []
|
slot.children = []
|
||||||
@@ -144,11 +144,12 @@ export default {
|
|||||||
}
|
}
|
||||||
return slot;
|
return slot;
|
||||||
}).filter(slot => !( // Hide full and ignored slots
|
}).filter(slot => !( // Hide full and ignored slots
|
||||||
!this.showHiddenSlots &&
|
!this.showHiddenSlots && (
|
||||||
slot.hideWhenFull &&
|
slot.hideWhenFull &&
|
||||||
slot.quantityExpected > 0 &&
|
slot.quantityExpectedResult > 0 &&
|
||||||
slot.totalFilled >= slot.quantityExpected ||
|
slot.spaceLeft <= 0 ||
|
||||||
slot.ignored
|
slot.ignored
|
||||||
|
)
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,13 +19,27 @@
|
|||||||
>
|
>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-switch
|
<v-switch
|
||||||
|
v-if="!libraryId || canEditLibrary"
|
||||||
v-model="organize"
|
v-model="organize"
|
||||||
label="Organize"
|
label="Organize"
|
||||||
class="mx-3"
|
class="mx-3"
|
||||||
style="flex-grow: 0; height: 32px;"
|
style="flex-grow: 0; height: 32px;"
|
||||||
/>
|
/>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
<div
|
||||||
|
v-if="libraryId"
|
||||||
|
style="width: 100%; height: 100%; overflow: auto;"
|
||||||
|
>
|
||||||
|
<library-contents-container
|
||||||
|
:library-id="libraryId"
|
||||||
|
:organize-mode="organize"
|
||||||
|
:selected-node-id="selected"
|
||||||
|
should-subscribe
|
||||||
|
@selected="clickNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<library-browser
|
<library-browser
|
||||||
|
v-else
|
||||||
edit-mode
|
edit-mode
|
||||||
:organize-mode="organize"
|
:organize-mode="organize"
|
||||||
:selected-node-id="selected"
|
:selected-node-id="selected"
|
||||||
@@ -53,17 +67,24 @@ import LibraryBrowser from '/imports/ui/library/LibraryBrowser.vue';
|
|||||||
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
|
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TreeDetailLayout,
|
TreeDetailLayout,
|
||||||
LibraryBrowser,
|
LibraryBrowser,
|
||||||
LibraryNodeDialog,
|
LibraryNodeDialog,
|
||||||
|
LibraryContentsContainer,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selection: Boolean,
|
selection: Boolean,
|
||||||
|
libraryId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
organize: false,
|
organize: false,
|
||||||
@@ -112,11 +133,34 @@ export default {
|
|||||||
getPropertyName,
|
getPropertyName,
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'library'(){
|
||||||
|
if (this.libraryId){
|
||||||
|
return [this.libraryId]
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
libraries(){
|
libraries(){
|
||||||
return Libraries.find({}, {
|
return Libraries.find({}, {
|
||||||
sort: {name: 1}
|
sort: {name: 1}
|
||||||
}).fetch();
|
}).fetch();
|
||||||
},
|
},
|
||||||
|
library(){
|
||||||
|
let libraryId = this.libraryId;
|
||||||
|
if (!libraryId) return;
|
||||||
|
return Libraries.findOne(libraryId);
|
||||||
|
},
|
||||||
|
canEditLibrary(){
|
||||||
|
if (!this.libraryId) return;
|
||||||
|
try {
|
||||||
|
assertEditPermission(this.library, Meteor.userId());
|
||||||
|
return true;
|
||||||
|
} catch (e){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
selectedNode(){
|
selectedNode(){
|
||||||
return LibraryNodes.findOne({
|
return LibraryNodes.findOne({
|
||||||
_id: this.selected,
|
_id: this.selected,
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-expansion-panel
|
<v-expansion-panel
|
||||||
v-model="expandedLibrary"
|
|
||||||
style="box-shadow: none;"
|
style="box-shadow: none;"
|
||||||
expand
|
expand
|
||||||
>
|
>
|
||||||
<v-expansion-panel-content
|
<v-expansion-panel-content
|
||||||
v-for="library in libraries"
|
v-for="(library, index) in libraries"
|
||||||
:key="library._id"
|
:key="library._id"
|
||||||
|
v-model="expandedLibrary[index]"
|
||||||
lazy
|
lazy
|
||||||
:data-id="library._id"
|
:data-id="library._id"
|
||||||
>
|
>
|
||||||
@@ -24,9 +24,10 @@
|
|||||||
<v-card flat>
|
<v-card flat>
|
||||||
<library-contents-container
|
<library-contents-container
|
||||||
:library-id="library._id"
|
:library-id="library._id"
|
||||||
:organize-mode="organizeMode"
|
:organize-mode="organizeMode && editPermission(library)"
|
||||||
:edit-mode="editMode"
|
:edit-mode="editMode"
|
||||||
:selected-node-id="selectedNodeId"
|
:selected-node-id="selectedNodeId"
|
||||||
|
:should-subscribe="expandedLibrary[index]"
|
||||||
@selected="e => $emit('selected', e)"
|
@selected="e => $emit('selected', e)"
|
||||||
/>
|
/>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -47,9 +48,9 @@
|
|||||||
small
|
small
|
||||||
icon
|
icon
|
||||||
:disabled="!editPermission(library)"
|
:disabled="!editPermission(library)"
|
||||||
@click="editLibrary(library._id)"
|
@click="$router.push(`/library/${library._id}`)"
|
||||||
>
|
>
|
||||||
<v-icon>create</v-icon>
|
<v-icon>arrow_forward</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -86,25 +87,28 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
organizeMode: Boolean,
|
organizeMode: Boolean,
|
||||||
editMode: Boolean,
|
editMode: Boolean,
|
||||||
selectedNodeId: String,
|
selectedNodeId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
expandedLibrary: null,
|
expandedLibrary: [],
|
||||||
|
expandedLibraryContent: [],
|
||||||
};},
|
};},
|
||||||
|
computed: {
|
||||||
|
noLibrariesExpanded(){
|
||||||
|
if (!this.expandedLibrary) return true;
|
||||||
|
let noneExpanded = true;
|
||||||
|
this.expandedLibrary.forEach(lib => {
|
||||||
|
if(lib) noneExpanded = false;
|
||||||
|
});
|
||||||
|
return noneExpanded;
|
||||||
|
},
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
'libraries': [],
|
'libraries': [],
|
||||||
'libraryNodes'(){
|
|
||||||
if (!this.expandedLibrary) return [[]];
|
|
||||||
let libraryIds = [];
|
|
||||||
this.expandedLibrary.forEach((expanded, index) => {
|
|
||||||
if (expanded){
|
|
||||||
let library = this.libraries[index];
|
|
||||||
if (library) libraryIds.push(library._id)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [libraryIds];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
libraries(){
|
libraries(){
|
||||||
return Libraries.find({}, {
|
return Libraries.find({}, {
|
||||||
@@ -116,17 +120,8 @@ export default {
|
|||||||
return tier && tier.paidBenefits;
|
return tier && tier.paidBenefits;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
noLibrariesExpanded(){
|
|
||||||
if (!this.expandedLibrary) return true;
|
|
||||||
let noneExpanded = true;
|
|
||||||
this.expandedLibrary.forEach(lib => {
|
|
||||||
if(lib) noneExpanded = false;
|
|
||||||
});
|
|
||||||
return noneExpanded;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
|
log: console.log,
|
||||||
insertLibrary(){
|
insertLibrary(){
|
||||||
if (this.paidBenefits){
|
if (this.paidBenefits){
|
||||||
this.$store.commit('pushDialogStack', {
|
this.$store.commit('pushDialogStack', {
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<tree-node-list
|
<v-fade-transition
|
||||||
group="library"
|
hide-on-leave
|
||||||
:children="libraryChildren"
|
>
|
||||||
:organize="organizeMode"
|
<tree-node-list
|
||||||
:selected-node-id="selectedNodeId"
|
v-if="slowShouldSubscribe && $subReady.libraryNodes"
|
||||||
@selected="e => $emit('selected', e)"
|
group="library"
|
||||||
@reordered="reordered"
|
:children="libraryChildren"
|
||||||
@reorganized="reorganized"
|
:organize="organizeMode"
|
||||||
/>
|
:selected-node-id="selectedNodeId"
|
||||||
|
@selected="e => $emit('selected', e)"
|
||||||
|
@reordered="reordered"
|
||||||
|
@reorganized="reorganized"
|
||||||
|
/>
|
||||||
|
<v-layout
|
||||||
|
v-else
|
||||||
|
row
|
||||||
|
align-center
|
||||||
|
justify-center
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<v-progress-circular
|
||||||
|
color="primary"
|
||||||
|
:indeterminate="slowShouldSubscribe"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
</v-fade-transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -25,8 +42,36 @@
|
|||||||
libraryId: String,
|
libraryId: String,
|
||||||
organizeMode: Boolean,
|
organizeMode: Boolean,
|
||||||
selectedNodeId: String,
|
selectedNodeId: String,
|
||||||
|
shouldSubscribe: Boolean,
|
||||||
},
|
},
|
||||||
|
data(){return {
|
||||||
|
slowShouldSubscribe: this.shouldSubscribe,
|
||||||
|
};},
|
||||||
|
watch:{
|
||||||
|
shouldSubscribe(newValue){
|
||||||
|
if (this.timeoutId){
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
delete this.timeoutId;
|
||||||
|
}
|
||||||
|
if (newValue){
|
||||||
|
this.slowShouldSubscribe = newValue
|
||||||
|
} else {
|
||||||
|
this.timeoutId = setTimeout(()=>{
|
||||||
|
this.slowShouldSubscribe = newValue
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'libraryNodes'(){
|
||||||
|
if (this.slowShouldSubscribe){
|
||||||
|
return [this.libraryId];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
library(){
|
library(){
|
||||||
return Libraries.findOne(this.libraryId);
|
return Libraries.findOne(this.libraryId);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<div
|
|
||||||
class="layout row"
|
|
||||||
style="background-color: inherit;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="layout column"
|
|
||||||
style="
|
|
||||||
background-color: inherit;
|
|
||||||
width: initial;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 320px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<v-toolbar
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<v-spacer />
|
|
||||||
<v-switch
|
|
||||||
v-model="organize"
|
|
||||||
label="Organize"
|
|
||||||
class="mx-3"
|
|
||||||
style="flex-grow: 0; height: 32px;"
|
|
||||||
/>
|
|
||||||
</v-toolbar>
|
|
||||||
<library-contents-container
|
|
||||||
:library-id="$route.params.id"
|
|
||||||
:organize-mode="organize"
|
|
||||||
:selected-node-id="selected"
|
|
||||||
@selected="e => selected = e"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<v-divider vertical />
|
|
||||||
<div
|
|
||||||
style="width: 100%; background-color: inherit;"
|
|
||||||
data-id="selected-node-card"
|
|
||||||
>
|
|
||||||
<v-toolbar
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<property-icon
|
|
||||||
:model="selectedNode"
|
|
||||||
class="mr-2"
|
|
||||||
/>
|
|
||||||
<div class="title">
|
|
||||||
{{ getPropertyName(selectedNode && selectedNode.type) }}
|
|
||||||
</div>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
v-if="selectedNode"
|
|
||||||
flat
|
|
||||||
icon
|
|
||||||
@click="editLibraryNode"
|
|
||||||
>
|
|
||||||
<v-icon>create</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-toolbar>
|
|
||||||
<v-card-text style="overflow-y: auto;">
|
|
||||||
<property-viewer :model="selectedNode" />
|
|
||||||
</v-card-text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
|
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
|
||||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
|
||||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
LibraryContentsContainer,
|
|
||||||
PropertyViewer,
|
|
||||||
PropertyIcon,
|
|
||||||
},
|
|
||||||
data(){ return {
|
|
||||||
organize: false,
|
|
||||||
selected: undefined,
|
|
||||||
};},
|
|
||||||
watch:{
|
|
||||||
selectedNode(val){
|
|
||||||
this.$emit('selected', val)
|
|
||||||
},
|
|
||||||
'library.name'(value){
|
|
||||||
this.$store.commit('setPageTitle', value || 'Library');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
editLibraryNode(){
|
|
||||||
this.$store.commit('pushDialogStack', {
|
|
||||||
component: 'library-node-edit-dialog',
|
|
||||||
elementId: 'selected-node-card',
|
|
||||||
data: {_id: this.selected},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getPropertyName,
|
|
||||||
},
|
|
||||||
meteor: {
|
|
||||||
$subscribe: {
|
|
||||||
'libraries': [],
|
|
||||||
},
|
|
||||||
library(){
|
|
||||||
return Libraries.findOne(this.$route.params.id);
|
|
||||||
},
|
|
||||||
selectedNode(){
|
|
||||||
return LibraryNodes.findOne({
|
|
||||||
_id: this.selected,
|
|
||||||
removed: {$ne: true}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
</style>
|
|
||||||
@@ -1,28 +1,53 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-toolbar-items>
|
<v-toolbar
|
||||||
<v-btn
|
app
|
||||||
v-if="showSubscribeButton"
|
color="secondary"
|
||||||
flat
|
dark
|
||||||
:loading="loading"
|
tabs
|
||||||
@click="subscribe(!subscribed)"
|
extended
|
||||||
>
|
dense
|
||||||
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
>
|
||||||
</v-btn>
|
<v-toolbar-side-icon @click="toggleDrawer" />
|
||||||
<v-btn
|
<v-toolbar-items>
|
||||||
v-if="canEdit"
|
<v-btn
|
||||||
flat
|
flat
|
||||||
icon
|
icon
|
||||||
data-id="library-edit-button"
|
@click="$router.push('/library')"
|
||||||
@click="editLibrary(library._id)"
|
>
|
||||||
>
|
<v-icon>arrow_back</v-icon>
|
||||||
<v-icon>create</v-icon>
|
</v-btn>
|
||||||
</v-btn>
|
</v-toolbar-items>
|
||||||
</v-toolbar-items>
|
<v-toolbar-title>
|
||||||
|
{{ library && library.name }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-toolbar-items>
|
||||||
|
<v-btn
|
||||||
|
v-if="showSubscribeButton"
|
||||||
|
flat
|
||||||
|
:loading="loading"
|
||||||
|
@click="subscribe(!subscribed)"
|
||||||
|
>
|
||||||
|
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-if="canEdit"
|
||||||
|
flat
|
||||||
|
icon
|
||||||
|
data-id="library-edit-button"
|
||||||
|
@click="editLibrary(library._id)"
|
||||||
|
>
|
||||||
|
<v-icon>settings</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar-items>
|
||||||
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data(){ return {
|
data(){ return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -33,8 +58,10 @@ export default {
|
|||||||
},
|
},
|
||||||
subscribed(){
|
subscribed(){
|
||||||
let libraryId = this.$route.params.id;
|
let libraryId = this.$route.params.id;
|
||||||
let subs = Meteor.user().subscribedLibraries;
|
let user = Meteor.user();
|
||||||
return subs.includes(libraryId);
|
if (!user) return false;
|
||||||
|
let subs = user.subscribedLibraries;
|
||||||
|
return subs && subs.includes(libraryId);
|
||||||
},
|
},
|
||||||
showSubscribeButton(){
|
showSubscribeButton(){
|
||||||
let userId = Meteor.userId();
|
let userId = Meteor.userId();
|
||||||
@@ -60,6 +87,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapMutations([
|
||||||
|
'toggleDrawer',
|
||||||
|
]),
|
||||||
subscribe(value){
|
subscribe(value){
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
Meteor.users.subscribeToLibrary.call({
|
Meteor.users.subscribeToLibrary.call({
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
style="height: 100%; overflow: hidden;"
|
style="height: 100%; overflow: hidden;"
|
||||||
class="character-log layout column justify-end"
|
class="character-log layout column justify-end"
|
||||||
>
|
>
|
||||||
<div
|
<v-slide-y-reverse-transition
|
||||||
class="log flex layout column reverse align-end pa-3"
|
group
|
||||||
|
hide-on-leave
|
||||||
|
class="log-entries flex layout column reverse align-end pa-3"
|
||||||
style="overflow: auto;"
|
style="overflow: auto;"
|
||||||
>
|
>
|
||||||
<log-entry
|
<log-entry
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
:key="log._id"
|
:key="log._id"
|
||||||
:model="log"
|
:model="log"
|
||||||
/>
|
/>
|
||||||
</div>
|
</v-slide-y-reverse-transition>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="input"
|
v-model="input"
|
||||||
@@ -120,4 +122,10 @@ export default {
|
|||||||
.log-tab p:last-child {
|
.log-tab p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
.theme--dark .log-entries {
|
||||||
|
background: #303030;
|
||||||
|
}
|
||||||
|
.log-entries {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<single-card-layout>
|
<single-card-layout>
|
||||||
<library-and-node />
|
<library-and-node
|
||||||
|
:library-id="$route.params.id"
|
||||||
|
/>
|
||||||
</single-card-layout>
|
</single-card-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<div>
|
|
||||||
<v-card class="ma-4">
|
|
||||||
<single-library />
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import SingleLibrary from '/imports/ui/library/SingleLibrary.vue';
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
SingleLibrary,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -6,8 +6,7 @@ import Home from '/imports/ui/pages/Home.vue';
|
|||||||
import About from '/imports/ui/pages/About.vue';
|
import About from '/imports/ui/pages/About.vue';
|
||||||
import CharacterList from '/imports/ui/pages/CharacterList.vue';
|
import CharacterList from '/imports/ui/pages/CharacterList.vue';
|
||||||
import Library from '/imports/ui/pages/Library.vue';
|
import Library from '/imports/ui/pages/Library.vue';
|
||||||
import SingleLibraryPage from '/imports/ui/pages/SingleLibraryPage.vue'
|
import SingleLibraryToolbar from '/imports/ui/library/SingleLibraryToolbar.vue';
|
||||||
import SingleLibraryToolbarItems from '/imports/ui/library/SingleLibraryToolbarItems.vue'
|
|
||||||
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
|
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
|
||||||
import CharacterSheetToolbar from '/imports/ui/creature/character/CharacterSheetToolbar.vue';
|
import CharacterSheetToolbar from '/imports/ui/creature/character/CharacterSheetToolbar.vue';
|
||||||
import CharacterSheetRightDrawer from '/imports/ui/creature/character/CharacterSheetRightDrawer.vue';
|
import CharacterSheetRightDrawer from '/imports/ui/creature/character/CharacterSheetRightDrawer.vue';
|
||||||
@@ -123,8 +122,8 @@ RouterFactory.configure(factory => {
|
|||||||
name: 'singleLibrary',
|
name: 'singleLibrary',
|
||||||
path: '/library/:id',
|
path: '/library/:id',
|
||||||
components: {
|
components: {
|
||||||
default: SingleLibraryPage,
|
default: Library,
|
||||||
toolbarItems: SingleLibraryToolbarItems,
|
toolbar: SingleLibraryToolbar,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Library',
|
title: 'Library',
|
||||||
|
|||||||
Reference in New Issue
Block a user