Compare commits

...

8 Commits

Author SHA1 Message Date
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
20 changed files with 253 additions and 252 deletions

View File

@@ -7,6 +7,7 @@ export default class ComputationMemo {
constructor(props, creature){
this.statsByVariableName = {};
this.constantsByVariableName = {};
this.constantsById = {};
this.extraStatsByVariableName = {};
this.statsById = {};
this.originalPropsById = {};
@@ -77,11 +78,7 @@ export default class ComputationMemo {
}
addConstant(prop){
prop = this.registerProperty(prop);
if (
!this.constantsByVariableName[prop.variableName]
){
this.constantsByVariableName[prop.variableName] = prop
}
this.constantsById[prop._id] = prop;
}
registerProperty(prop){
this.originalPropsById[prop._id] = cloneDeep(prop);

View File

@@ -112,13 +112,14 @@ function combineSkill(stat, aggregator, memo){
let profBonus = profBonusStat && profBonusStat.value;
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;
if (level._id){
stat.dependencies = union(stat.dependencies, [level._id]);
if (levelProp._id){
stat.dependencies = union(stat.dependencies, [levelProp._id]);
}
if (level.dependencies){
stat.dependencies = union(stat.dependencies, level.dependencies);
if (levelProp.dependencies){
stat.dependencies = union(stat.dependencies, levelProp.dependencies);
}
} else {
stat.dependencies = union(

View File

@@ -3,4 +3,10 @@ import applyToggles from '/imports/api/creature/computation/engine/applyToggles.
export default function computeConstant(constant, memo){
// Apply any toggles
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 ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { union } from 'lodash';
export default function computeEndStepProperty(prop, memo){
applyToggles(prop, memo);
switch (prop.type){
case 'action':
case 'spell':

View File

@@ -1,4 +1,5 @@
import { forOwn, has, union } from 'lodash';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
export default function computeLevels(memo){
computeClassLevels(memo);
@@ -7,11 +8,13 @@ export default function computeLevels(memo){
function computeClassLevels(memo){
forOwn(memo.classLevelsById, classLevel => {
applyToggles(classLevel, memo);
// class levels are mutually dependent
classLevel.dependencies = union(
classLevel.dependencies,
Object.keys(memo.classLevelsById)
);
if (classLevel.deactivatedByToggle) return;
let name = classLevel.variableName;
let stat = memo.statsByVariableName[name];
if (!stat){
@@ -29,7 +32,7 @@ function computeClassLevels(memo){
function computeTotalLevel(memo){
let currentLevel = memo.statsByVariableName['level'];
if (!currentLevel){
if (!currentLevel || currentLevel.deactivatedByToggle){
currentLevel = {
value: 0,
dependencies: [],

View File

@@ -9,7 +9,7 @@ import computeConstant from '/imports/api/creature/computation/engine/computeCon
export default function computeMemo(memo){
// Compute all constants that could be used
forOwn(memo.constantsByVariableName, constant => {
forOwn(memo.constantsById, constant => {
computeConstant (constant, memo);
});
// Compute level

View File

@@ -22,28 +22,25 @@ export default function computeStat(stat, memo){
// Apply any toggles
applyToggles(stat, memo);
if (!stat.deactivatedByToggle){
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
if (effect._id){
stat.dependencies = union(
stat.dependencies,
[effect._id]
);
}
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
if (effect.deactivatedByToggle) return;
if (effect._id){
stat.dependencies = union(
stat.dependencies,
effect.dependencies
)
if (!effect.deactivatedByToggle){
aggregator.addEffect(effect);
}
});
// Conglomerate all the effects to compute the final stat values
combineStat(stat, aggregator, memo);
}
[effect._id]
);
}
stat.dependencies = union(
stat.dependencies,
effect.dependencies
)
aggregator.addEffect(effect);
});
// Conglomerate all the effects to compute the final stat values
combineStat(stat, aggregator, memo);
// Mark the attribute as computed
stat.computationDetails.computed = true;
stat.computationDetails.busyComputing = false;

View File

@@ -8,7 +8,10 @@ export default function writeAlteredProperties(memo){
// Loop through all properties on the memo
forOwn(memo.propsById, changed => {
let schema = propertySchemasIndex[changed.type];
if (!schema) return;
if (!schema){
console.warn('No schema for ' + changed.type);
return;
}
let extraIds = changed.computationDetails.idsOfSameName;
let ids;
if (extraIds && extraIds.length){

View File

@@ -50,7 +50,7 @@ const damagePropertiesByName = new ValidatedMethod({
damagePropertyWork({property, operation, value});
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 { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.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 { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.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 { 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 { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.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 { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
@@ -29,23 +30,25 @@ const propertySchemasIndex = {
attack: ComputedOnlyAttackSchema,
attribute: ComputedOnlyAttributeSchema,
buff: ComputedOnlyBuffSchema,
// classLevel: ClassLevelSchema,
classLevel: ClassLevelSchema,
constant: ConstantSchema,
container: ComputedOnlyContainerSchema,
damage: ComputedOnlyDamageSchema,
damageMultiplier: DamageMultiplierSchema,
effect: ComputedOnlyEffectSchema,
feature: ComputedOnlyFeatureSchema,
// folder: FolderSchema,
folder: FolderSchema,
item: ComputedOnlyItemSchema,
note: ComputedOnlyNoteSchema,
// proficiency: ProficiencySchema,
proficiency: ProficiencySchema,
propertySlot: ComputedOnlySlotSchema,
roll: ComputedOnlyRollSchema,
savingThrow: ComputedOnlySavingThrowSchema,
skill: ComputedOnlySkillSchema,
slotFiller: SlotFillerSchema,
spellList: ComputedOnlySpellListSchema,
spell: ComputedOnlySpellSchema,
toggle: ComputedOnlyToggleSchema,
container: ComputedOnlyContainerSchema,
item: ComputedOnlyItemSchema,
any: new SimpleSchema({}),
};

View File

@@ -25,38 +25,50 @@ Meteor.publish('libraries', function(){
{owner: this.userId},
{writers: 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({
libraryIds: {
type: Array,
},
'libraryIds.$':{
libraryId:{
type: String,
regEx: SimpleSchema.RegEx.Id,
},
});
Meteor.publish('libraryNodes', function(libraryIds){
libraryIdSchema.validate({libraryIds});
if (!libraryIds.length) return [];
Meteor.publish('libraryNodes', function(libraryId){
if (!libraryId) return [];
libraryIdSchema.validate({libraryId});
this.autorun(function (){
let userId = this.userId;
for (let i in libraryIds){
let libraryId = libraryIds[i];
let library = Libraries.findOne(libraryId);
try { assertViewPermission(library, userId) }
catch(e){
return this.error(e);
}
let library = Libraries.findOne(libraryId);
try { assertViewPermission(library, userId) }
catch(e){
return this.error(e);
}
return [
LibraryNodes.find({
'ancestors.id': {$in: libraryIds},
'ancestors.id': libraryId,
}, {
sort: {order: 1},
}),

View File

@@ -5,15 +5,15 @@
right
clipped
>
<log-tab :creature-id="$route.params.id" />
<character-log :creature-id="$route.params.id" />
</v-navigation-drawer>
</template>
<script>
import LogTab from '/imports/ui/log/CharacterLog.vue';
import CharacterLog from '/imports/ui/log/CharacterLog.vue';
export default {
components: {
LogTab,
CharacterLog,
},
computed: {
drawer: {

View File

@@ -19,13 +19,27 @@
>
<v-spacer />
<v-switch
v-if="!$route.params.id || canEditLibrary"
v-model="organize"
label="Organize"
class="mx-3"
style="flex-grow: 0; height: 32px;"
/>
</v-toolbar>
<div
v-if="$route.params.id"
style="width: 100%; height: 100%; overflow: auto;"
>
<library-contents-container
:library-id="$route.params.id"
:organize-mode="organize"
:selected-node-id="selected"
should-subscribe
@selected="clickNode"
/>
</div>
<library-browser
v-else
edit-mode
:organize-mode="organize"
:selected-node-id="selected"
@@ -53,14 +67,17 @@ import LibraryBrowser from '/imports/ui/library/LibraryBrowser.vue';
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
import LibraryNodes from '/imports/api/library/LibraryNodes.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 isDarkColor from '/imports/ui/utility/isDarkColor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
export default {
components: {
TreeDetailLayout,
LibraryBrowser,
LibraryNodeDialog,
LibraryContentsContainer,
},
props: {
selection: Boolean,
@@ -112,11 +129,34 @@ export default {
getPropertyName,
},
meteor: {
$subscribe: {
'library'(){
if (this.$route.params.id){
return [this.$route.params.id];
} else {
return [];
}
},
},
libraries(){
return Libraries.find({}, {
sort: {name: 1}
}).fetch();
},
library(){
let libraryId = this.$route.params.id;
if (!libraryId) return;
return Libraries.findOne(libraryId);
},
canEditLibrary(){
if (!this.$route.params.id) return;
try {
assertEditPermission(this.library, Meteor.userId());
return true;
} catch (e){
return false;
}
},
selectedNode(){
return LibraryNodes.findOne({
_id: this.selected,

View File

@@ -11,7 +11,7 @@
expand
>
<v-expansion-panel-content
v-for="library in libraries"
v-for="(library, index) in libraries"
:key="library._id"
lazy
:data-id="library._id"
@@ -24,9 +24,10 @@
<v-card flat>
<library-contents-container
:library-id="library._id"
:organize-mode="organizeMode"
:organize-mode="organizeMode && editPermission(library)"
:edit-mode="editMode"
:selected-node-id="selectedNodeId"
:should-subscribe="expandedLibrary[index]"
@selected="e => $emit('selected', e)"
/>
<v-card-actions>
@@ -47,9 +48,9 @@
small
icon
: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-card-actions>
</v-card>
@@ -89,22 +90,11 @@ export default {
selectedNodeId: String,
},
data(){ return {
expandedLibrary: null,
expandedLibrary: [],
};},
meteor: {
$subscribe: {
'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(){
return Libraries.find({}, {
@@ -127,6 +117,7 @@ export default {
},
},
methods: {
log: console.log,
insertLibrary(){
if (this.paidBenefits){
this.$store.commit('pushDialogStack', {

View File

@@ -1,13 +1,30 @@
<template lang="html">
<tree-node-list
group="library"
:children="libraryChildren"
:organize="organizeMode"
:selected-node-id="selectedNodeId"
@selected="e => $emit('selected', e)"
@reordered="reordered"
@reorganized="reorganized"
/>
<v-fade-transition
hide-on-leave
>
<tree-node-list
v-if="slowShouldSubscribe && $subReady.libraryNodes"
group="library"
:children="libraryChildren"
: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>
<script>
@@ -25,8 +42,36 @@
libraryId: String,
organizeMode: Boolean,
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: {
$subscribe: {
'libraryNodes'(){
if (this.slowShouldSubscribe){
return [this.libraryId];
} else {
return [];
}
}
},
library(){
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">
<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>create</v-icon>
</v-btn>
</v-toolbar-items>
<v-toolbar
app
color="secondary"
dark
tabs
extended
dense
>
<v-toolbar-side-icon @click="toggleDrawer" />
<v-toolbar-items>
<v-btn
flat
icon
@click="$router.push('/library')"
>
<v-icon>arrow_back</v-icon>
</v-btn>
</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>
<script>
import Libraries from '/imports/api/library/Libraries.js';
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { mapMutations } from 'vuex';
export default {
data(){ return {
loading: false,
@@ -33,8 +58,10 @@ export default {
},
subscribed(){
let libraryId = this.$route.params.id;
let subs = Meteor.user().subscribedLibraries;
return subs.includes(libraryId);
let user = Meteor.user();
if (!user) return false;
let subs = user.subscribedLibraries;
return subs && subs.includes(libraryId);
},
showSubscribeButton(){
let userId = Meteor.userId();
@@ -60,6 +87,9 @@ export default {
}
},
methods: {
...mapMutations([
'toggleDrawer',
]),
subscribe(value){
this.loading = true;
Meteor.users.subscribeToLibrary.call({

View File

@@ -3,8 +3,10 @@
style="height: 100%; overflow: hidden;"
class="character-log layout column justify-end"
>
<div
class="log flex layout column reverse align-end pa-3"
<v-slide-y-reverse-transition
group
hide-on-leave
class="log-entries flex layout column reverse align-end pa-3"
style="overflow: auto;"
>
<log-entry
@@ -12,7 +14,7 @@
:key="log._id"
:model="log"
/>
</div>
</v-slide-y-reverse-transition>
<v-card>
<v-text-field
v-model="input"
@@ -120,4 +122,10 @@ export default {
.log-tab p:last-child {
margin-bottom: 0;
}
.theme--dark .log-entries {
background: #303030;
}
.log-entries {
background: #fafafa;
}
</style>

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 CharacterList from '/imports/ui/pages/CharacterList.vue';
import Library from '/imports/ui/pages/Library.vue';
import SingleLibraryPage from '/imports/ui/pages/SingleLibraryPage.vue'
import SingleLibraryToolbarItems from '/imports/ui/library/SingleLibraryToolbarItems.vue'
import SingleLibraryToolbar from '/imports/ui/library/SingleLibraryToolbar.vue';
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
import CharacterSheetToolbar from '/imports/ui/creature/character/CharacterSheetToolbar.vue';
import CharacterSheetRightDrawer from '/imports/ui/creature/character/CharacterSheetRightDrawer.vue';
@@ -123,8 +122,8 @@ RouterFactory.configure(factory => {
name: 'singleLibrary',
path: '/library/:id',
components: {
default: SingleLibraryPage,
toolbarItems: SingleLibraryToolbarItems,
default: Library,
toolbar: SingleLibraryToolbar,
},
meta: {
title: 'Library',