Compare commits

..

10 Commits

Author SHA1 Message Date
Stefan Zermatten
11a2851ac4 Fixed slots with computed expected quantity not hiding when full 2021-03-10 14:51:38 +02:00
Stefan Zermatten
313382fb82 Fixed library subscription issues 2021-03-10 14:40:14 +02:00
Stefan Zermatten
b9ae337a64 Merge branch 'version-2-dev' of https://github.com/ThaumRystra/DiceCloud into version-2-dev 2021-03-02 14:32:08 +02:00
Stefan Zermatten
4dc0a6159b Animated log entries 2021-03-02 14:32:05 +02:00
Stefan Zermatten
e00dfe1532 Changed the color of the log background 2021-03-02 14:31:35 +02:00
Stefan Zermatten
28e1fcabd5 Fixed damage properties by name failing if no properties were found 2021-03-02 14:10:14 +02:00
Stefan Zermatten
2c0496b44b Fixed properties not being made inactive by toggles 2021-03-02 13:56:53 +02:00
Stefan Zermatten
89adda60ec Reworked single page libraries to be more in line with the library view 2021-03-02 13:05:38 +02:00
Stefan Zermatten
8c3710cda3 Started work on single page libraries 2021-03-02 00:24:54 +02:00
Stefan Zermatten
b501b9d830 Fixed crash in skill calculation when level is overridden by an attribute 2021-03-01 18:40:55 +02:00
22 changed files with 283 additions and 271 deletions

View File

@@ -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);

View File

@@ -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(

View File

@@ -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
}
} }

View File

@@ -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':

View File

@@ -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: [],

View File

@@ -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

View File

@@ -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;

View File

@@ -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){

View File

@@ -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);
} }
}); });

View File

@@ -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({}),
}; };

View File

@@ -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},
}), }),

View File

@@ -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: {

View File

@@ -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
)
)); ));
}, },
}, },

View File

@@ -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,

View File

@@ -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', {

View File

@@ -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);
}, },

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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',