stat grouping is now everywhere

This lead to a complete refactor of the stats page
Some things might break
This commit is contained in:
Stefan Zermatten
2022-11-22 00:56:10 +02:00
parent d2649fd66e
commit a3355dd988
10 changed files with 461 additions and 176 deletions

View File

@@ -1,10 +1,10 @@
import { union, difference, sortBy, findLast } from 'lodash';
export function nodeArrayToTree(nodes){
export function nodeArrayToTree(nodes) {
// Store a dict and list of all the nodes
let nodeIndex = {};
let nodeList = [];
nodes.forEach( node => {
nodes.forEach(node => {
let treeNode = {
node: node,
children: [],
@@ -20,7 +20,7 @@ export function nodeArrayToTree(nodes){
treeNode.node.ancestors,
ancestor => !!nodeIndex[ancestor.id]
);
if (ancestorInForest){
if (ancestorInForest) {
nodeIndex[ancestorInForest.id].children.push(treeNode);
} else {
forest.push(treeNode);
@@ -33,13 +33,13 @@ export function nodeArrayToTree(nodes){
export default function nodesToTree({
collection, ancestorId, filter, options = {},
includeFilteredDocAncestors = false, includeFilteredDocDescendants = false
}){
}) {
// Setup the filter
let collectionFilter = {
'ancestors.id': ancestorId,
'removed': {$ne: true},
'removed': { $ne: true },
};
if (filter){
if (filter) {
collectionFilter = {
...collectionFilter,
...filter,
@@ -49,7 +49,7 @@ export default function nodesToTree({
let collectionSort = {
order: 1
};
if (options && options.sort){
if (options && options.sort) {
collectionSort = {
...collectionSort,
...options.sort,
@@ -58,7 +58,7 @@ export default function nodesToTree({
let collectionOptions = {
sort: collectionSort,
}
if (options){
if (options) {
collectionOptions = {
...collectionOptions,
...options,
@@ -74,10 +74,10 @@ export default function nodesToTree({
let ancestors = [];
let ancestorIds = [];
let docIds = [];
if (filter && (includeFilteredDocAncestors || includeFilteredDocDescendants)){
if (filter && (includeFilteredDocAncestors || includeFilteredDocDescendants)) {
docIds = docs.map(doc => doc._id)
}
if (filter && includeFilteredDocAncestors){
if (filter && includeFilteredDocAncestors) {
// Add all ancestor ids to an array
docs.forEach(doc => {
ancestorIds = union(ancestorIds, doc.ancestors.map(ref => ref.id));
@@ -86,19 +86,19 @@ export default function nodesToTree({
ancestorIds = difference(ancestorIds, docIds);
// Get the docs from the collection, don't worry about `removed` docs,
// if their descendant was not removed, neither are they
ancestors = collection.find({_id: {$in: ancestorIds}}).map(doc => {
ancestors = collection.find({ _id: { $in: ancestorIds } }).map(doc => {
// Mark that the nodes are ancestors of the found nodes
doc._ancestorOfMatchedDocument = true;
return doc;
});
}
let descendants = [];
if (filter && includeFilteredDocDescendants){
if (filter && includeFilteredDocDescendants) {
let exludeIds = union(ancestorIds, docIds);
descendants = collection.find({
'_id': {$nin: exludeIds},
'ancestors.id': {$in: docIds},
'removed': {$ne: true},
'_id': { $nin: exludeIds },
'ancestors.id': { $in: docIds },
'removed': { $ne: true },
}).map(doc => {
// Mark that the nodes are descendants of the found nodes
doc._descendantOfMatchedDocument = true;

View File

@@ -13,6 +13,24 @@ let FolderSchema = new createPropertySchema({
type: Boolean,
optional: true,
},
hideStatsGroup: {
type: Boolean,
optional: true,
},
tab: {
type: String,
optional: true,
allowedValues: [
'stats', 'features', 'inventory', 'spells', 'journal', 'build'
],
},
location: {
type: String,
optional: true,
allowedValues: [
'start', 'events', 'stats', 'skills', 'actions', 'proficiencies', 'end'
],
},
});
const ComputedOnlyFolderSchema = new createPropertySchema({});

View File

@@ -13,9 +13,19 @@
</v-row>
<v-row dense>
<v-col
cols="12"
md="8"
lg="6"
v-for="folder in startFolders"
:key="folder._id"
v-bind="cols"
>
<folder-group-card
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</v-col>
<v-col
v-bind="cols"
>
<v-card class="pb-4">
<v-card-title style="height: 68px;">
@@ -81,9 +91,7 @@
</v-card>
</v-col>
<v-col
cols="12"
md="4"
lg="6"
v-bind="cols"
>
<v-card class="class-details mb-2">
<v-card-title
@@ -174,6 +182,18 @@
</v-list>
</v-card>
</v-col>
<v-col
v-for="folder in endFolders"
:key="folder._id"
v-bind="cols"
>
<folder-group-card
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</v-col>
</v-row>
</v-container>
</template>
@@ -190,6 +210,7 @@ import CharacterErrors from '/imports/client/ui/creature/character/errors/Charac
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle.js';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
function traverse(tree, callback, parents = []){
tree.forEach(node => {
@@ -204,12 +225,23 @@ export default {
BuildTreeNodeList,
SlotCardsToFill,
},
mixins: [tabFoldersMixin],
props: {
creatureId: {
type: String,
required: true,
},
},
data() {
return {
tabName: 'build',
cols: {
cols: '12',
md: '6',
xl: '4',
}
};
},
computed: {
highestLevels(){
let highestLevels = {};

View File

@@ -1,6 +1,14 @@
<template lang="html">
<div class="features">
<column-layout wide-columns>
<folder-group-card
v-for="folder in startFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div
v-for="feature in features"
:key="feature._id"
@@ -11,6 +19,14 @@
@click="featureClicked(feature)"
/>
</div>
<folder-group-card
v-for="folder in endFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</column-layout>
</div>
</template>
@@ -19,18 +35,25 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import ColumnLayout from '/imports/client/ui/components/ColumnLayout.vue';
import FeatureCard from '/imports/client/ui/properties/components/features/FeatureCard.vue';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
export default {
components: {
ColumnLayout,
FeatureCard,
},
mixins: [tabFoldersMixin],
props: {
creatureId: {
type: String,
required: true,
},
},
data() {
return {
tabName: 'features',
};
},
meteor: {
features() {
return CreatureProperties.find({

View File

@@ -1,6 +1,14 @@
<template lang="html">
<div class="inventory">
<column-layout wide-columns>
<folder-group-card
v-for="folder in startFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div>
<v-card>
<v-list>
@@ -85,6 +93,14 @@
>
<container-card :model="container" />
</div>
<folder-group-card
v-for="folder in endFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</column-layout>
</div>
</template>
@@ -101,6 +117,7 @@ import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
import CoinValue from '/imports/client/ui/components/CoinValue.vue';
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
export default {
components: {
@@ -110,6 +127,7 @@ export default {
ItemList,
CoinValue,
},
mixins: [tabFoldersMixin],
props: {
creatureId: {
type: String,
@@ -119,7 +137,8 @@ export default {
data() {
return {
organize: false,
}
tabName: 'inventory',
};
},
meteor: {
containers() {
@@ -216,15 +235,6 @@ export default {
);
},
},
methods: {
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `tree-node-${_id}`,
data: { _id },
});
},
},
}
</script>

View File

@@ -1,6 +1,14 @@
<template>
<div class="build-tab">
<column-layout wide-columns>
<folder-group-card
v-for="folder in startFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div>
<creature-summary :creature="creature" />
</div>
@@ -12,6 +20,14 @@
:model="note"
/>
</div>
<folder-group-card
v-for="folder in endFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</column-layout>
</div>
</template>
@@ -22,6 +38,7 @@ import Creatures from '/imports/api/creature/creatures/Creatures.js';
import NoteCard from '/imports/client/ui/properties/components/persona/NoteCard.vue';
import CreatureSummary from '/imports/client/ui/creature/character/CreatureSummary.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
export default {
components: {
@@ -29,12 +46,18 @@ export default {
CreatureSummary,
NoteCard,
},
mixins: [tabFoldersMixin],
props: {
creatureId: {
type: String,
required: true,
},
},
data() {
return {
tabName: 'journal',
};
},
meteor: {
notes(){
return CreatureProperties.find({

View File

@@ -1,6 +1,14 @@
<template lang="html">
<div class="spells">
<column-layout wide-columns>
<folder-group-card
v-for="folder in startFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div v-if="spellsWithoutList.length">
<v-card>
<spell-list
@@ -18,6 +26,14 @@
:organize="organize"
/>
</div>
<folder-group-card
v-for="folder in endFolders"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</column-layout>
</div>
</template>
@@ -27,6 +43,7 @@ import ColumnLayout from '/imports/client/ui/components/ColumnLayout.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import SpellListCard from '/imports/client/ui/properties/components/spells/SpellListCard.vue';
import SpellList from '/imports/client/ui/properties/components/spells/SpellList.vue';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
export default {
components: {
@@ -34,6 +51,7 @@ export default {
SpellList,
SpellListCard,
},
mixins: [tabFoldersMixin],
props: {
creatureId: {
type: String,
@@ -43,6 +61,7 @@ export default {
data() {
return {
organize: false,
tabName: 'spells',
}
},
meteor: {
@@ -92,15 +111,6 @@ export default {
return this.spellLists.map(spellList => spellList._id);
},
},
methods: {
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `spell-list-tile-${_id}`,
data: { _id },
});
},
},
}
</script>

View File

@@ -1,12 +1,15 @@
<template lang="html">
<div class="stats-tab ma-2">
<div
v-if="properties"
class="stats-tab ma-2"
>
<div
v-if="healthBars.length"
v-if="properties.attribute.healthBar && properties.attribute.healthBar.length"
class="px-2 pt-2"
>
<v-card class="pa-2">
<health-bar
v-for="healthBar in healthBars"
v-for="healthBar in properties.attribute.healthBar"
:key="healthBar._id"
:model="healthBar"
@change="({ type, value }) => incrementChange(healthBar._id, { type, value: -value })"
@@ -17,7 +20,7 @@
<column-layout>
<folder-group-card
v-for="folder in folders"
v-for="folder in properties.folder.start"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@@ -25,7 +28,7 @@
@remove="softRemove"
/>
<div
v-if="!creature.settings.hideRestButtons || (events && events.length)"
v-if="!creature.settings.hideRestButtons || (properties.action.event && properties.action.event.length)"
class="character-buttons"
>
<v-card>
@@ -43,7 +46,7 @@
class="ma-1"
/>
<event-button
v-for="event in events"
v-for="event in properties.event"
:key="event._id"
:model="event"
class="ma-1"
@@ -52,21 +55,30 @@
</v-card>
</div>
<folder-group-card
v-for="folder in properties.folder.events"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<damage-multiplier-card
v-if="multipliers && multipliers.length"
:multipliers="multipliers"
v-if="properties.multiplier && properties.multiplier.length"
:multipliers="properties.multiplier"
@click-multiplier="clickProperty"
/>
<div
v-if="appliedBuffs.length"
v-if="properties.buff && properties.buff.length"
class="buffs"
>
<v-card>
<v-list>
<v-subheader>Buffs and conditions</v-subheader>
<buff-list-item
v-for="buff in appliedBuffs"
v-for="buff in properties.buff"
:key="buff._id"
:data-id="buff._id"
:model="buff"
@@ -78,12 +90,12 @@
</div>
<div
v-if="abilities.length"
v-if="properties.attribute.ability && properties.attribute.ability.length"
class="ability-scores"
>
<v-card>
<v-list>
<template v-for="(ability, index) in abilities">
<template v-for="(ability, index) in properties.attribute.ability">
<v-divider
v-if="index !== 0"
:key="index"
@@ -100,7 +112,7 @@
</div>
<div
v-for="toggle in toggles"
v-for="toggle in properties.toggle"
:key="toggle._id"
class="toggle"
>
@@ -112,7 +124,7 @@
</div>
<div
v-for="stat in stats"
v-for="stat in properties.attribute.stat"
:key="stat._id"
class="stat"
>
@@ -124,7 +136,7 @@
</div>
<div
v-for="modifier in modifiers"
v-for="modifier in properties.attribute.modifier"
:key="modifier._id"
class="modifier"
>
@@ -136,7 +148,7 @@
</div>
<div
v-for="check in checks"
v-for="check in properties.skill.check"
:key="check._id"
class="check"
>
@@ -149,7 +161,7 @@
</div>
<div
v-if="hitDice.length"
v-if="properties.hitDice && properties.hitDice.length"
class="hit-dice"
>
<v-card>
@@ -173,7 +185,7 @@
</div>
<div
v-for="resource in resources"
v-for="resource in properties.attribute.resource"
:key="resource._id"
class="resource"
>
@@ -186,18 +198,18 @@
</div>
<div
v-if="spellSlots && spellSlots.length || hasSpells"
v-if="properties.attribute.spellSlot && properties.attribute.spellSlot.length || hasSpells"
class="spell-slots"
>
<v-card data-id="spell-slot-card">
<v-list
v-if="spellSlots && spellSlots.length"
v-if="properties.attribute.spellSlot && properties.attribute.spellSlot.length"
two-line
subheader
>
<v-subheader>Spell Slots</v-subheader>
<spell-slot-list-tile
v-for="spellSlot in spellSlots"
v-for="spellSlot in properties.attribute.spellSlot"
:key="spellSlot._id"
:model="spellSlot"
:data-id="spellSlot._id"
@@ -220,15 +232,24 @@
</v-card>
</div>
<folder-group-card
v-for="folder in properties.folder.stats"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div
v-if="savingThrows.length"
v-if="properties.skill.save && properties.skill.save.length"
class="saving-throws"
>
<v-card>
<v-list>
<v-subheader>Saving Throws</v-subheader>
<skill-list-tile
v-for="save in savingThrows"
v-for="save in properties.skill.save"
:key="save._id"
:model="save"
:data-id="save._id"
@@ -239,14 +260,14 @@
</div>
<div
v-if="skills.length"
v-if="properties.skill.skill && properties.skill.skill.length"
class="skills"
>
<v-card>
<v-list>
<v-subheader>Skills</v-subheader>
<skill-list-tile
v-for="skill in skills"
v-for="skill in properties.skill.skill"
:key="skill._id"
:model="skill"
:data-id="skill._id"
@@ -256,8 +277,17 @@
</v-card>
</div>
<folder-group-card
v-for="folder in properties.folder.skills"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div
v-for="action in actions"
v-for="action in properties.action"
:key="action._id"
class="action"
>
@@ -269,8 +299,17 @@
/>
</div>
<folder-group-card
v-for="folder in properties.folder.actions"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div
v-if="weapons && weapons.length"
v-if="properties.skill.weapon && properties.skill.weapon.length"
class="weapon-proficiencies"
>
<v-card>
@@ -279,7 +318,7 @@
Weapons
</v-subheader>
<skill-list-tile
v-for="weapon in weapons"
v-for="weapon in properties.skill.weapon"
:key="weapon._id"
hide-modifier
:model="weapon"
@@ -290,7 +329,7 @@
</v-card>
</div>
<div
v-if="armors && armors.length"
v-if="properties.skill.armor && properties.skill.armor.length"
class="armor-proficiencies"
>
<v-card>
@@ -299,7 +338,7 @@
Armor
</v-subheader>
<skill-list-tile
v-for="armor in armors"
v-for="armor in properties.skill.armor"
:key="armor._id"
hide-modifier
:model="armor"
@@ -310,7 +349,7 @@
</v-card>
</div>
<div
v-if="tools && tools.length"
v-if="properties.skill.tool && properties.skill.tool.length"
class="tool-proficiencies"
>
<v-card>
@@ -319,7 +358,7 @@
Tools
</v-subheader>
<skill-list-tile
v-for="tool in tools"
v-for="tool in properties.skill.tool"
:key="tool._id"
hide-modifier
:model="tool"
@@ -330,7 +369,7 @@
</v-card>
</div>
<div
v-if="languages && languages.length"
v-if="properties.skill.language && properties.skill.language.length"
class="language-proficiencies"
>
<v-card>
@@ -339,7 +378,7 @@
Languages
</v-subheader>
<skill-list-tile
v-for="language in languages"
v-for="language in properties.skill.language"
:key="language._id"
hide-modifier
:model="language"
@@ -349,6 +388,24 @@
</v-list>
</v-card>
</div>
<folder-group-card
v-for="folder in properties.folder.proficiencies"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<folder-group-card
v-for="folder in properties.folder.end"
:key="folder._id"
:model="folder"
@click-property="clickProperty"
@sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
</column-layout>
</div>
</template>
@@ -375,40 +432,63 @@ import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
import EventButton from '/imports/client/ui/properties/components/actions/EventButton.vue';
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
import FolderGroupCard from '/imports/client/ui/properties/components/folders/FolderGroupCard.vue';
import { uniqBy } from 'lodash';
import { get, set } from 'lodash';
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'
const getProperties = function (creature, folderIds, filter, options = {
sort: { order: 1 }
}) {
if (!creature) return;
if (creature.settings.hideUnusedStats) {
filter.hide = { $ne: true };
function walkDown(forest, callback){
let stack = [...forest];
while(stack.length){
let node = stack.pop();
const { skipChildren } = callback(node) ?? { skipChildren: false };
if (!skipChildren) {
stack.push(...node.children);
}
}
filter['ancestors.id'] = creature._id;
filter['parent.id'] = {$nin: folderIds},
filter.removed = { $ne: true };
filter.inactive = { $ne: true };
filter.overridden = { $ne: true };
filter.$nor = [
{ hideWhenTotalZero: true, total: 0 },
{ hideWhenValueZero: true, value: 0 },
];
}
return CreatureProperties.find(filter, options);
};
const getAttributeOfType = function (creature, folderIds, type) {
return getProperties(creature, folderIds, {
type: 'attribute',
attributeType: type,
});
};
const getSkillOfType = function (creature, folderIds, type) {
return getProperties(creature, folderIds, {
type: 'skill',
skillType: type,
});
const propertyHandlers = {
folder(prop) {
let skipChildren;
let propPath = null;
if (prop.hideStatsGroup) {
return { skipChildren: true}
}
if (prop.tab === 'stats') {
propPath = ['folder', prop.location]
}
return { skipChildren, propPath }
},
attribute(prop) {
if (
prop.attributeType === 'utility' ||
prop.overridden ||
(prop.hideWhenTotalZero && prop.total === 0) ||
(prop.hideWhenValueZero && prop.value === 0)
) return { propPath: null };
return {
propPath: ['attribute', prop.attributeType],
}
},
skill(prop) {
if (
prop.skillType === 'utility'
) return { propPath: null };
return {
propPath: ['skill', prop.skillType],
}
},
toggle(prop) {
if (
prop.deactivatedByAncestor || !prop.showUI
) return { propPath: null };
return { propPath: 'toggle' };
},
action(prop) {
if (prop.actionType === 'event') {
return { propPath: 'event' };
}
return { propPath: 'action' };
},
}
export default {
@@ -441,30 +521,50 @@ export default {
}
},
meteor: {
properties() {
const creature = this.creature;
if (!creature) return;
const filter = {
'ancestors.id': this.creatureId,
$or: [
{ inactive: { $ne: true } },
{ type: 'toggle' },
],
removed: { $ne: true },
};
if (creature.settings.hideUnusedStats) {
filter.hide = { $ne: true };
}
const allProps = CreatureProperties.find(filter);
const forest = nodeArrayToTree(allProps);
const properties = { folder: {}, attribute: {}, skill: {} };
walkDown(forest, node => {
const prop = node.node;
const { propPath, skipChildren } = propertyHandlers[prop.type]?.(prop) ||
{ propPath: prop.type };
if (propPath) {
let propArray = get(properties, propPath);
if (!propArray) {
propArray = [];
set(properties, propPath, propArray);
}
if (!propArray?.push) {
console.log({propArray});
}
propArray.push(prop);
}
return { skipChildren };
});
return properties;
},
creature() {
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
},
folders() {
return getProperties(this.creature, [], { type: 'folder', groupStats: true });
},
folderIds() {
return this.folders.map(f => f._id);
},
healthBars() {
return getAttributeOfType(this.creature, this.folderIds, 'healthBar');
},
abilities() {
return getAttributeOfType(this.creature, this.folderIds, 'ability');
},
stats() {
return getAttributeOfType(this.creature, this.folderIds, 'stat');
},
toggles() {
return CreatureProperties.find({
type: 'toggle',
'ancestors.id': this.creatureId,
'parent.id': { $nin: this.folderIds },
removed: { $ne: true },
deactivatedByAncestor: { $ne: true },
showUI: true,
@@ -472,61 +572,8 @@ export default {
sort: { order: 1 }
});
},
modifiers() {
return getAttributeOfType(this.creature, this.folderIds, 'modifier');
},
resources() {
return getAttributeOfType(this.creature, this.folderIds, 'resource');
},
spellSlots() {
return getAttributeOfType(this.creature, this.folderIds, 'spellSlot');
},
hasSpells() {
const cursor = getProperties(this.creature, this.folderIds, {
type: 'spell',
})
return cursor && cursor.count();
},
hitDice() {
return getAttributeOfType(this.creature, this.folderIds, 'hitDice');
},
checks() {
return getSkillOfType(this.creature, this.folderIds, 'check');
},
savingThrows() {
return getSkillOfType(this.creature, this.folderIds, 'save');
},
skills() {
return getSkillOfType(this.creature, this.folderIds, 'skill');
},
tools() {
return getSkillOfType(this.creature, this.folderIds, 'tool');
},
weapons() {
return getSkillOfType(this.creature, this.folderIds, 'weapon');
},
armors() {
return getSkillOfType(this.creature, this.folderIds, 'armor');
},
languages() {
return getSkillOfType(this.creature, this.folderIds, 'language');
},
events() {
const events = getProperties(this.creature, this.folderIds, { type: 'action', actionType: 'event' });
return uniqBy(events.fetch(), e => e.variableName);
},
actions() {
return getProperties(this.creature, this.folderIds, { type: 'action', actionType: { $ne: 'event' } });
},
appliedBuffs() {
return getProperties(this.creature, this.folderIds, { type: 'buff' });
},
multipliers() {
return getProperties(this.creature, this.folderIds, {
type: 'damageMultiplier'
}, {
sort: { value: 1, order: 1 }
});
return this.properties?.spell?.length
},
},
methods: {

View File

@@ -0,0 +1,53 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'
import FolderGroupCard from '/imports/client/ui/properties/components/folders/FolderGroupCard.vue';
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
function getFolders(creatureId, tab, location) {
return CreatureProperties.find({
'ancestors.id': creatureId,
groupStats: true,
inactive: { $ne: true },
removed: { $ne: true },
tab,
location,
});
}
export default {
components: {
FolderGroupCard,
},
meteor: {
startFolders() {
return getFolders(this.creatureId, this.tabName, 'start');
},
endFolders() {
return getFolders(this.creatureId, this.tabName, 'end');
},
},
methods: {
clickProperty({ _id }) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: { _id },
});
},
clickTreeProperty({ _id }) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `tree-node-${_id}`,
data: { _id },
});
},
softRemove(_id) {
softRemoveProperty.call({ _id }, error => {
if (error) {
snackbar({ text: error.reason || error.message || error.toString() });
console.error(error);
}
});
},
}
};

View File

@@ -27,11 +27,46 @@
@change="change('tags', ...arguments)"
/>
<smart-switch
label="Group children on stats tab"
label="Group children on a card"
:value="model.groupStats"
:error-messages="errors.groupStats"
@change="change('groupStats', ...arguments)"
/>
<v-expand-transition>
<div v-if="model.groupStats">
<smart-switch
label="Hide children from stats tab"
:value="model.hideStatsGroup"
:error-messages="errors.hideStatsGroup"
@change="change('hideStatsGroup', ...arguments)"
/>
<smart-select
clearable
label="Tab"
:items="[
{ text: 'Stats Tab', value: 'stats' },
{ text: 'Features Tab', value: 'features' },
{ text: 'Inventory Tab', value: 'inventory' },
{ text: 'Spells Tab', value: 'spells' },
{ text: 'Journal Tab', value: 'journal' },
{ text: 'Build Tab', value: 'build' },
]"
:value="model.tab"
:error-messages="errors.tab"
:menu-props="{auto: true, lazy: true}"
@change="changeTab('tab', ...arguments)"
/>
<smart-select
clearable
label="Location"
:items="locationItems"
:value="model.location"
:error-messages="errors.location"
:menu-props="{auto: true, lazy: true}"
@change="change('location', ...arguments)"
/>
</div>
</v-expand-transition>
</form-section>
</form-sections>
</div>
@@ -47,6 +82,40 @@ export default {
FormSection,
},
mixins: [propertyFormMixin],
computed: {
locationItems() {
if (this.model.tab === 'stats') {
return [
{ text: 'Start', value: 'start' },
{ text: 'After events', value: 'events' },
{ text: 'After stats', value: 'stats' },
{ text: 'After skills', value: 'skills' },
{ text: 'After actions', value: 'actions' },
{ text: 'After proficiencies', value: 'proficiencies' },
{ text: 'End', value: 'end' },
];
} else {
return [
{ text: 'Start', value: 'start' },
{ text: 'End', value: 'end' },
];
}
}
},
methods: {
changeTab(path, value, ack) {
if (!Array.isArray(path)){
path = [path];
}
if (
value !== 'stats' &&
(this.model.location !== 'start' && this.model.location !== 'end')
|| (!this.model.location && value)
)
this.$emit('change', {path: ['location'], value: 'start'});
this.$emit('change', {path, value, ack});
},
}
};
</script>