stat grouping is now everywhere
This lead to a complete refactor of the stats page Some things might break
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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({});
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user