Completed folder stat grouping UI

This commit is contained in:
Stefan Zermatten
2022-11-09 14:58:52 +02:00
parent 8377231254
commit 3d31d62860
15 changed files with 559 additions and 294 deletions

View File

@@ -28,8 +28,7 @@ let AttributeSchema = createPropertySchema({
'stat', // Speed, Armor Class 'stat', // Speed, Armor Class
'modifier', // Proficiency Bonus, displayed as +x 'modifier', // Proficiency Bonus, displayed as +x
'hitDice', // d12 hit dice 'hitDice', // d12 hit dice
'healthBar', // Hitpoints, Temporary Hitpoints, can take damage 'healthBar', // Hitpoints, Temporary Hitpoints
'bar', // Displayed as a health bar, can't take damage
'resource', // Rages, sorcery points 'resource', // Rages, sorcery points
'spellSlot', // Level 1, 2, 3... spell slots 'spellSlot', // Level 1, 2, 3... spell slots
'utility', // Aren't displayed, Jump height, Carry capacity 'utility', // Aren't displayed, Jump height, Carry capacity

View File

@@ -1,14 +1,29 @@
<template lang="html"> <template lang="html">
<div class="stats-tab ma-2"> <div class="stats-tab ma-2">
<health-bar-card-container :creature-id="creatureId" /> <div
v-if="healthBars.length"
class="px-2 pt-2"
>
<v-card class="pa-2">
<health-bar
v-for="healthBar in healthBars"
:key="healthBar._id"
:model="healthBar"
@change="({ type, value }) => incrementChange(healthBar._id, { type, value: -value })"
@click="clickProperty({_id: healthBar._id})"
/>
</v-card>
</div>
<column-layout> <column-layout>
<div <folder-group-card
v-for="folder in folders" v-for="folder in folders"
:key="folder._id" :key="folder._id"
> :model="folder"
<folder-group-card :model="folder" /> @click-property="clickProperty"
</div> @sub-click="_id => clickTreeProperty({_id})"
@remove="softRemove"
/>
<div <div
v-if="!creature.settings.hideRestButtons || (events && events.length)" v-if="!creature.settings.hideRestButtons || (events && events.length)"
class="character-buttons" class="character-buttons"
@@ -50,26 +65,14 @@
<v-card> <v-card>
<v-list> <v-list>
<v-subheader>Buffs and conditions</v-subheader> <v-subheader>Buffs and conditions</v-subheader>
<v-list-item <buff-list-item
v-for="buff in appliedBuffs" v-for="buff in appliedBuffs"
:key="buff._id" :key="buff._id"
:data-id="buff._id" :data-id="buff._id"
:model="buff"
@click="clickProperty({_id: buff._id})" @click="clickProperty({_id: buff._id})"
> @remove="softRemove(buff._id)"
<v-list-item-content> />
<v-list-item-title>
{{ buff.name }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="!buff.hideRemoveButton">
<v-btn
icon
@click.stop="softRemove(buff._id)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list> </v-list>
</v-card> </v-card>
</div> </div>
@@ -199,7 +202,6 @@
:model="spellSlot" :model="spellSlot"
:data-id="spellSlot._id" :data-id="spellSlot._id"
@click="clickProperty({_id: spellSlot._id})" @click="clickProperty({_id: spellSlot._id})"
@cast="castSpellWithSlot(spellSlot._id)"
/> />
</v-list> </v-list>
<div <div
@@ -355,11 +357,11 @@
import Creatures from '/imports/api/creature/creatures/Creatures.js'; import Creatures from '/imports/api/creature/creatures/Creatures.js';
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js'; import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue'; import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue'; import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue'; import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue'; import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue';
import HealthBarCardContainer from '/imports/ui/properties/components/attributes/HealthBarCardContainer.vue';
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue'; import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue'; import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue'; import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
@@ -368,13 +370,14 @@ import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue
import RestButton from '/imports/ui/creature/RestButton.vue'; import RestButton from '/imports/ui/creature/RestButton.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue'; import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
import BuffListItem from '/imports/ui/properties/components/buffs/BuffListItem.vue';
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js'; import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
import EventButton from '/imports/ui/properties/components/actions/EventButton.vue'; import EventButton from '/imports/ui/properties/components/actions/EventButton.vue';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js'; import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
import FolderGroupCard from '/imports/ui/properties/components/folders/FolderGroupCard.vue'; import FolderGroupCard from '/imports/ui/properties/components/folders/FolderGroupCard.vue';
import { uniqBy } from 'lodash'; import { uniqBy } from 'lodash';
const getProperties = function (creature, filter, options = { const getProperties = function (creature, folderIds, filter, options = {
sort: { order: 1 } sort: { order: 1 }
}) { }) {
if (!creature) return; if (!creature) return;
@@ -382,6 +385,7 @@ const getProperties = function (creature, filter, options = {
filter.hide = { $ne: true }; filter.hide = { $ne: true };
} }
filter['ancestors.id'] = creature._id; filter['ancestors.id'] = creature._id;
filter['parent.id'] = {$nin: folderIds},
filter.removed = { $ne: true }; filter.removed = { $ne: true };
filter.inactive = { $ne: true }; filter.inactive = { $ne: true };
filter.overridden = { $ne: true }; filter.overridden = { $ne: true };
@@ -393,15 +397,15 @@ const getProperties = function (creature, filter, options = {
return CreatureProperties.find(filter, options); return CreatureProperties.find(filter, options);
}; };
const getAttributeOfType = function (creature, type) { const getAttributeOfType = function (creature, folderIds, type) {
return getProperties(creature, { return getProperties(creature, folderIds, {
type: 'attribute', type: 'attribute',
attributeType: type, attributeType: type,
}); });
}; };
const getSkillOfType = function (creature, type) { const getSkillOfType = function (creature, folderIds, type) {
return getProperties(creature, { return getProperties(creature, folderIds, {
type: 'skill', type: 'skill',
skillType: type, skillType: type,
}); });
@@ -409,12 +413,13 @@ const getSkillOfType = function (creature, type) {
export default { export default {
components: { components: {
HealthBar,
RestButton, RestButton,
BuffListItem,
AbilityListTile, AbilityListTile,
AttributeCard, AttributeCard,
ColumnLayout, ColumnLayout,
DamageMultiplierCard, DamageMultiplierCard,
HealthBarCardContainer,
HitDiceListTile, HitDiceListTile,
SkillListTile, SkillListTile,
ResourceCard, ResourceCard,
@@ -439,16 +444,27 @@ export default {
creature() { creature() {
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } }); 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() { abilities() {
return getAttributeOfType(this.creature, 'ability'); return getAttributeOfType(this.creature, this.folderIds, 'ability');
}, },
stats() { stats() {
return getAttributeOfType(this.creature, 'stat'); return getAttributeOfType(this.creature, this.folderIds, 'stat');
}, },
toggles() { toggles() {
return CreatureProperties.find({ return CreatureProperties.find({
'ancestors.id': this.creatureId,
type: 'toggle', type: 'toggle',
'ancestors.id': this.creatureId,
'parent.id': { $nin: this.folderIds },
removed: { $ne: true }, removed: { $ne: true },
deactivatedByAncestor: { $ne: true }, deactivatedByAncestor: { $ne: true },
showUI: true, showUI: true,
@@ -457,64 +473,61 @@ export default {
}); });
}, },
modifiers() { modifiers() {
return getAttributeOfType(this.creature, 'modifier'); return getAttributeOfType(this.creature, this.folderIds, 'modifier');
}, },
resources() { resources() {
return getAttributeOfType(this.creature, 'resource'); return getAttributeOfType(this.creature, this.folderIds, 'resource');
}, },
spellSlots() { spellSlots() {
return getAttributeOfType(this.creature, 'spellSlot'); return getAttributeOfType(this.creature, this.folderIds, 'spellSlot');
}, },
hasSpells() { hasSpells() {
const cursor = getProperties(this.creature, { const cursor = getProperties(this.creature, this.folderIds, {
type: 'spell', type: 'spell',
}) })
return cursor && cursor.count(); return cursor && cursor.count();
}, },
hitDice() { hitDice() {
return getAttributeOfType(this.creature, 'hitDice'); return getAttributeOfType(this.creature, this.folderIds, 'hitDice');
}, },
checks() { checks() {
return getSkillOfType(this.creature, 'check'); return getSkillOfType(this.creature, this.folderIds, 'check');
}, },
savingThrows() { savingThrows() {
return getSkillOfType(this.creature, 'save'); return getSkillOfType(this.creature, this.folderIds, 'save');
}, },
skills() { skills() {
return getSkillOfType(this.creature, 'skill'); return getSkillOfType(this.creature, this.folderIds, 'skill');
}, },
tools() { tools() {
return getSkillOfType(this.creature, 'tool'); return getSkillOfType(this.creature, this.folderIds, 'tool');
}, },
weapons() { weapons() {
return getSkillOfType(this.creature, 'weapon'); return getSkillOfType(this.creature, this.folderIds, 'weapon');
}, },
armors() { armors() {
return getSkillOfType(this.creature, 'armor'); return getSkillOfType(this.creature, this.folderIds, 'armor');
}, },
languages() { languages() {
return getSkillOfType(this.creature, 'language'); return getSkillOfType(this.creature, this.folderIds, 'language');
}, },
events() { events() {
const events = getProperties(this.creature, { type: 'action', actionType: 'event' }); const events = getProperties(this.creature, this.folderIds, { type: 'action', actionType: 'event' });
return uniqBy(events.fetch(), e => e.variableName); return uniqBy(events.fetch(), e => e.variableName);
}, },
actions() { actions() {
return getProperties(this.creature, { type: 'action', actionType: { $ne: 'event' } }); return getProperties(this.creature, this.folderIds, { type: 'action', actionType: { $ne: 'event' } });
}, },
appliedBuffs() { appliedBuffs() {
return getProperties(this.creature, { type: 'buff' }); return getProperties(this.creature, this.folderIds, { type: 'buff' });
}, },
multipliers() { multipliers() {
return getProperties(this.creature, { return getProperties(this.creature, this.folderIds, {
type: 'damageMultiplier' type: 'damageMultiplier'
}, { }, {
sort: { value: 1, order: 1 } sort: { value: 1, order: 1 }
}); });
}, },
folders() {
return getProperties(this.creature, { type: 'folder', groupStats: true });
},
}, },
methods: { methods: {
clickProperty({ _id }) { clickProperty({ _id }) {
@@ -532,13 +545,23 @@ export default {
}); });
}, },
incrementChange(_id, { type, value }) { incrementChange(_id, { type, value }) {
if (type === 'increment') { damageProperty.call({
damageProperty.call({ _id, operation: 'increment', value: -value }); _id,
} operation: type,
value: -value
}, error => {
if (error) {
snackbar({ text: error.reason || error.message || error.toString() });
console.error(error);
}
});
}, },
softRemove(_id) { softRemove(_id) {
softRemoveProperty.call({ _id }, error => { softRemoveProperty.call({ _id }, error => {
if (error) console.error(error); if (error) {
snackbar({ text: error.reason || error.message || error.toString() });
console.error(error);
}
}); });
}, },
castSpell() { castSpell() {

View File

@@ -5,54 +5,19 @@
@mouseover="hasClickListener ? hovering = true : undefined" @mouseover="hasClickListener ? hovering = true : undefined"
@mouseleave="hasClickListener ? hovering = false : undefined" @mouseleave="hasClickListener ? hovering = false : undefined"
> >
<div class="layout align-center"> <attribute-card-content :model="model" />
<roll-popup
v-if="model.attributeType === 'modifier' || model.type === 'skill'"
button-class="px-0"
text
height="70"
min-width="72"
:roll-text="computedValue && computedValue.toString()"
:name="model.name"
:advantage="model.advantage"
:loading="checkLoading"
:disabled="!context.editPermission"
@roll="check"
>
<v-card-title class="value text-h4 flex-shrink-0">
{{ computedValue }}
</v-card-title>
</roll-popup>
<v-card-title
v-else
class="value text-h4 flex-shrink-0"
>
{{ computedValue }}
</v-card-title>
<v-card-title class="name text-subtitle-1 text-truncate d-block pl-0">
{{ model.name }}
</v-card-title>
</div>
<card-highlight :active="hasClickListener && hovering" /> <card-highlight :active="hasClickListener && hovering" />
</v-card> </v-card>
</template> </template>
<script lang="js"> <script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import CardHighlight from '/imports/ui/components/CardHighlight.vue'; import CardHighlight from '/imports/ui/components/CardHighlight.vue';
import AttributeCardContent from '/imports/ui/properties/components/attributes/AttributeCardContent.vue';
export default { export default {
components: { components: {
RollPopup,
CardHighlight, CardHighlight,
}, AttributeCardContent,
inject: {
context: {
default: {},
},
}, },
props: { props: {
model: { model: {
@@ -68,41 +33,11 @@
hasClickListener(){ hasClickListener(){
return this.$listeners && !!this.$listeners.click return this.$listeners && !!this.$listeners.click
}, },
computedValue(){
if (this.model.attributeType === 'modifier' || this.model.type === 'skill'){
return numberToSignedString(this.model.value);
} else {
return this.model.value
}
}
}, },
methods: { methods: {
signed: numberToSignedString,
click(e){ click(e){
this.$emit('click', e); this.$emit('click', e);
}, },
check({advantage}){
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
scope: {
$checkAdvantage: advantage,
},
}, error => {
this.checkLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},
}, },
} }
</script> </script>
<style lang="css" scoped>
.value {
min-width: 72px;
justify-content: center;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div
class="layout align-center"
@click="$emit('click')"
@mouseover="$emit('mouseover')"
@mouseleave="$emit('mouseleave')"
>
<roll-popup
v-if="model.attributeType === 'modifier' || model.type === 'skill'"
button-class="px-0"
text
height="70"
min-width="72"
:roll-text="computedValue && computedValue.toString()"
:name="model.name"
:advantage="model.advantage"
:loading="checkLoading"
:disabled="!context.editPermission"
@roll="check"
>
<v-card-title class="value text-h4 flex-shrink-0">
{{ computedValue }}
</v-card-title>
</roll-popup>
<v-card-title
v-else
class="value text-h4 flex-shrink-0"
>
{{ computedValue }}
</v-card-title>
<v-card-title class="name text-subtitle-1 text-truncate d-block pl-0">
{{ model.name }}
</v-card-title>
</div>
</template>
<script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
RollPopup,
},
inject: {
context: {
default: {},
},
},
props: {
model: {
type: Object,
required: true,
},
},
data(){return {
checkLoading: false,
hovering: false,
}},
computed: {
computedValue(){
if (this.model.attributeType === 'modifier' || this.model.type === 'skill'){
return numberToSignedString(this.model.value);
} else {
return this.model.value
}
}
},
methods: {
signed: numberToSignedString,
check({advantage}){
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
scope: {
$checkAdvantage: advantage,
},
}, error => {
this.checkLoading = false;
if (error){
console.error(error);
snackbar({text: error.reason});
}
});
},
},
}
</script>
<style lang="css" scoped>
.value {
min-width: 72px;
justify-content: center;
}
</style>

View File

@@ -6,7 +6,7 @@
style="min-height: 42px;" style="min-height: 42px;"
:class="{ hover }" :class="{ hover }"
class="my-1 health-bar" class="my-1 health-bar"
:data-id="_id" :data-id="model._id"
> >
<div <div
class="subheading text-truncate pa-2 name" class="subheading text-truncate pa-2 name"
@@ -14,14 +14,10 @@
@mouseleave="hover = false" @mouseleave="hover = false"
@click="$emit('click')" @click="$emit('click')"
> >
{{ name }} {{ model.name }}
</div> </div>
<v-flex <v-flex
style=" style="height: 24px; flex-basis: 300px; flex-grow: 100;"
height: 24px;
flex-basis: 300px;
flex-grow: 100;
"
> >
<div <div
column column
@@ -50,8 +46,7 @@
'white--text': isTextLight, 'white--text': isTextLight,
'black--text': !isTextLight, 'black--text': !isTextLight,
}" }"
style=" style="font-size: 15px;
font-size: 15px;
line-height: 24px; line-height: 24px;
font-weight: 600; font-weight: 600;
position: absolute; position: absolute;
@@ -59,30 +54,30 @@
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
text-align: center; text-align: center;"
"
> >
{{ value }} / {{ maxValue }} {{ model.value }} / {{ model.total }}
</div> </div>
</div> </div>
</div> </div>
<transition name="transition"> <v-menu
v-model="editing"
absolute
transition="scale-transition"
origin="center center"
content-class="no-menu-shadow"
:position-x="x"
:position-y="y"
:min-width="305"
:close-on-content-click="false"
>
<increment-menu <increment-menu
v-show="editing" :value="model.value"
:value="value"
:open="editing" :open="editing"
@change="changeIncrementMenu" @change="changeIncrementMenu"
@close="cancelEdit" @close="cancelEdit"
/> />
</transition> </v-menu>
<transition name="background-transition">
<div
v-if="editing"
class="page-tint"
@click="cancelEdit"
/>
</transition>
</v-flex> </v-flex>
</v-layout> </v-layout>
</template> </template>
@@ -104,31 +99,9 @@ export default {
}, },
}, },
props: { props: {
value: { model: {
type: Number, type: Object,
default: undefined, required: true,
},
maxValue: {
type: Number,
default: undefined,
},
name: {
type: String,
default: undefined,
},
color: {
type: String,
default() {
return this.$vuetify.theme.currentTheme.primary
},
},
midColor: {
type: String,
default: undefined,
},
lowColor: {
type: String,
default: undefined,
}, },
_id: String, _id: String,
}, },
@@ -136,24 +109,29 @@ export default {
return { return {
editing: false, editing: false,
hover: false, hover: false,
x: 0,
y: 0,
}; };
}, },
computed: { computed: {
fillFraction() { fillFraction() {
let fraction = this.value / this.maxValue; let fraction = this.model.value / this.model.total;
if (fraction < 0) fraction = 0; if (fraction < 0) fraction = 0;
if (fraction > 1) fraction = 1; if (fraction > 1) fraction = 1;
return fraction; return fraction;
}, },
color() {
return this.model.color || this.$vuetify.theme.currentTheme.primary
},
barColor() { barColor() {
const fraction = this.value / this.maxValue; const fraction = this.model.value / this.model.total;
if (!Number.isFinite(fraction)) return this.color; if (!Number.isFinite(fraction)) return this.color;
if (fraction > 0.5) { if (fraction > 0.5) {
return this.color; return this.color;
} else if (this.midColor && this.lowColor) { } else if (this.model.healthBarColorMid && this.model.healthBarColorLow) {
return chroma.mix(this.lowColor, this.midColor, fraction * 2).hex(); return chroma.mix(this.model.healthBarColorLow, this.model.healthBarColorMid, fraction * 2).hex();
} else if (this.midColor) { } else if (this.model.healthBarColorMid) {
return this.midColor; return this.model.healthBarColorMid;
} }
return this.color; return this.color;
}, },
@@ -166,7 +144,7 @@ export default {
isTextLight() { isTextLight() {
return isDarkColor(this.barBackgroundColor); return isDarkColor(this.barBackgroundColor);
/* Change color at the halfway mark /* Change color at the halfway mark
const fraction = this.value / this.maxValue; const fraction = this.model.value / this.model.total;
if (fraction >= 0.5){ if (fraction >= 0.5){
return isDarkColor(this.barColor); return isDarkColor(this.barColor);
} else { } else {
@@ -176,8 +154,14 @@ export default {
} }
}, },
methods: { methods: {
edit() { edit(e) {
this.editing = true; e.preventDefault()
this.editing = false;
this.x = e.clientX - 165;
this.y = e.clientY - 24;
this.$nextTick(() => {
this.editing = true
});
}, },
cancelEdit() { cancelEdit() {
this.editing = false; this.editing = false;
@@ -199,6 +183,10 @@ export default {
z-index: 7; z-index: 7;
position: relative; position: relative;
} }
.no-menu-shadow {
box-shadow: none !important;
}
</style> </style>
<style scoped> <style scoped>

View File

@@ -3,13 +3,7 @@
<health-bar <health-bar
v-for="attribute in attributes" v-for="attribute in attributes"
:key="attribute._id" :key="attribute._id"
:value="attribute.value" :model="attribute"
:max-value="attribute.total"
:name="attribute.name"
:color="attribute.color"
:mid-color="attribute.healthBarColorMid"
:low-color="attribute.healthBarColorLow"
:_id="attribute._id"
@change="e => $emit('change', {_id: attribute._id, change: e})" @change="e => $emit('change', {_id: attribute._id, change: e})"
@click="e => $emit('click', {_id: attribute._id})" @click="e => $emit('click', {_id: attribute._id})"
/> />

View File

@@ -3,57 +3,26 @@
class="resource-card" class="resource-card"
:class="hover ? 'elevation-8': ''" :class="hover ? 'elevation-8': ''"
> >
<v-layout> <resource-card-content
<div class="buttons layout column justify-center pl-3"> :model="model"
<v-btn :hover="hover"
icon @mouseover="hover = true"
small @mouseleave="hover = false"
:disabled="(model.value >= model.total && !model.ignoreUpperLimit) || context.editPermission === false" @click="$emit('click')"
@click="increment(1)" @change="e => $emit('change', e)"
> />
<v-icon>mdi-chevron-up</v-icon>
</v-btn>
<v-btn
icon
small
:disabled="(model.value <= 0 && !model.ignoreLowerLimit) || context.editPermission === false"
@click="increment(-1)"
>
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</div>
<div class="layout align-center value pl-2 pr-3">
<div class="text-h4">
{{ model.value }}
</div>
<div
v-if="model.total !== 0"
class="text-h6 ml-2 max-value"
>
/{{ model.total }}
</div>
</div>
<div
class="content layout align-center pr-3"
@click="click"
@mouseover="hover = true"
@mouseleave="hover = false"
>
<div class="text-truncate ">
{{ model.name }}
</div>
</div>
</v-layout>
<card-highlight :active="hover" /> <card-highlight :active="hover" />
</v-card> </v-card>
</template> </template>
<script lang="js"> <script lang="js">
import CardHighlight from '/imports/ui/components/CardHighlight.vue'; import CardHighlight from '/imports/ui/components/CardHighlight.vue';
import ResourceCardContent from '/imports/ui/properties/components/attributes/ResourceCardContent.vue';
export default { export default {
components: { components: {
CardHighlight, CardHighlight,
ResourceCardContent,
}, },
inject: { inject: {
context: { default: {} } context: { default: {} }
@@ -69,46 +38,16 @@ export default {
hover: false, hover: false,
} }
}, },
methods: {
click(e) {
this.$emit('click', e);
},
increment(value) {
this.$emit('change', { type: 'increment', value })
},
},
}; };
</script> </script>
<style lang="css" scoped> <style lang="css">
.resource-card { .resource-card {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1); transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
} }
.resource-card>div { .resource-card > div {
padding-top: 16px; padding-top: 16px;
padding-bottom: 16px; padding-bottom: 16px;
} }
.buttons,
.value {
flex-shrink: 0;
flex-grow: 0;
}
.buttons>.v-btn {
margin: 0;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0, 0, 0, .54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
</style> </style>

View File

@@ -0,0 +1,89 @@
<template>
<v-layout>
<div class="buttons layout column justify-center pl-3">
<v-btn
icon
small
:disabled="(model.value >= model.total && !model.ignoreUpperLimit) || context.editPermission === false"
@click="increment(1)"
>
<v-icon>mdi-chevron-up</v-icon>
</v-btn>
<v-btn
icon
small
:disabled="(model.value <= 0 && !model.ignoreLowerLimit) || context.editPermission === false"
@click="increment(-1)"
>
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</div>
<div class="layout align-center value pl-2 pr-3">
<div class="text-h4">
{{ model.value }}
</div>
<div
v-if="model.total !== 0"
class="text-h6 ml-2 max-value"
>
/{{ model.total }}
</div>
</div>
<div
class="content layout align-center pr-3"
@click="click"
@mouseover="$emit('mouseover')"
@mouseleave="$emit('mouseleave')"
>
<div class="text-truncate ">
{{ model.name }}
</div>
</div>
</v-layout>
</template>
<script lang="js">
export default {
inject: {
context: { default: {} }
},
props: {
model: {
type: Object,
required: true,
},
hover: {
type: Boolean,
}
},
methods: {
click(e) {
this.$emit('click', e);
},
increment(value) {
this.$emit('change', { type: 'increment', value })
},
},
};
</script>
<style lang="css" scoped>
.buttons,
.value {
flex-shrink: 0;
flex-grow: 0;
}
.buttons>.v-btn {
margin: 0;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0, 0, 0, .54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
</style>

View File

@@ -61,7 +61,6 @@ export default {
required: true, required: true,
}, },
dark: Boolean, dark: Boolean,
hideCastButton: Boolean,
disabled: Boolean, disabled: Boolean,
}, },
computed: { computed: {

View File

@@ -0,0 +1,30 @@
<template>
<v-list-item
@click="$emit('click')"
>
<v-list-item-content>
<v-list-item-title>
{{ model.name }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="!model.hideRemoveButton">
<v-btn
icon
@click.stop="$emit('remove')"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</template>
<script lang="js">
export default {
props: {
model: {
type: Object,
required: true,
},
},
}
</script>

View File

@@ -1,15 +1,25 @@
<template> <template>
<v-card> <div
<v-subheader v-if="model.name"> v-if="model.name || (properties && properties.length)"
{{ model.name }} >
</v-subheader> <v-card
<component class="folder-group-card pb-2"
:is="prop.type" >
v-for="prop in properties" <v-subheader v-if="model.name">
:key="prop._id" {{ model.name }}
:model="prop" </v-subheader>
/> <component
</v-card> :is="prop.type"
v-for="prop in properties"
:key="prop._id"
:model="prop"
:data-id="prop._id"
@click="$emit('click-property', {_id: prop._id})"
@sub-click="_id => $emit('sub-click', _id)"
@remove="$emit('remove', prop._id)"
/>
</v-card>
</div>
</template> </template>
<script lang="js"> <script lang="js">
@@ -28,12 +38,44 @@ export default {
}, },
meteor: { meteor: {
properties() { properties() {
return CreatureProperties.find({ const props = [];
'ancestors.id': this.model._id CreatureProperties.find({
'parent.id': this.model._id,
removed: { $ne: true },
overridden: { $ne: true },
$or: [
{
type: 'toggle',
showUI: true,
deactivatedByAncestor: { $ne: true },
},
{
inactive: { $ne: true }
},
],
$nor: [
{ hideWhenTotalZero: true, total: 0 },
{ hideWhenValueZero: true, value: 0 },
],
}, { }, {
sort: { order: 1 }, sort: { order: 1 },
}).forEach(prop => {
if (this.$options.components[prop.type]) {
props.push(prop);
}
}); });
return props;
}, },
}, },
} }
</script> </script>
<style>
.folder-group-card .v-card {
box-shadow: none !important;
border-radius: 0 !important;
}
.folder-group-card .drag-handle {
display: none !important;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<div
v-if="model.actionType === 'event'"
class="d-flex justify-center"
>
<event-button
class="ma-1"
:model="model"
/>
</div>
<action-card
v-else
:model="model"
@click="$emit('click')"
@sub-click="_id => $emit('sub-click', _id)"
/>
</template>
<script lang="js">
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
import EventButton from '/imports/ui/properties/components/actions/EventButton.vue';
export default {
components: {
ActionCard,
EventButton,
},
props: {
model: {
type: Object,
required: true,
},
},
}
</script>

View File

@@ -0,0 +1,95 @@
<template>
<div class="attribute">
<ability-list-tile
v-if="model.attributeType === 'ability'"
:model="model"
@click="$emit('click')"
/>
<hit-dice-list-tile
v-else-if="model.attributeType === 'hitDice'"
:model="model"
@click="$emit('click')"
@change="({ type, value }) => damageProperty({type, value: -value})"
/>
<health-bar
v-else-if="model.attributeType === 'healthBar'"
:model="model"
@change="damageProperty"
@click="$emit('click')"
/>
<spell-slot-list-tile
v-else-if="model.attributeType === 'spellSlot'"
:model="model"
@click="$emit('click')"
/>
<resource-card-content
v-else-if="model.attributeType === 'resource'"
:model="model"
@click="$emit('click')"
@change="({ type, value }) => damageProperty({type, value: -value})"
@mouseover="hover = true"
@mouseleave="hover = false"
/>
<attribute-card-content
v-else-if="model.attributeType !== 'utility'"
class="pointer"
:model="model"
@click="$emit('click')"
@mouseover="hover = true"
@mouseleave="hover = false"
/>
<card-highlight :active="hover" />
</div>
</template>
<script lang="js">
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
import ResourceCardContent from '/imports/ui/properties/components/attributes/ResourceCardContent.vue';
import AttributeCardContent from '/imports/ui/properties/components/attributes/AttributeCardContent.vue';
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
export default {
components: {
AbilityListTile,
HitDiceListTile,
HealthBar,
SpellSlotListTile,
ResourceCardContent,
AttributeCardContent,
CardHighlight,
},
props: {
model: {
type: Object,
required: true,
},
},
data() {
return {
hover: false,
}},
methods: {
damageProperty(change) {
damageProperty.call({
_id: this.model._id,
operation: change.type,
value: change.value
});
},
}
}
</script>
<style lang="css" scoped>
.attribute {
position: relative;
}
.pointer {
cursor: pointer;
}
</style>

View File

@@ -1,27 +1,27 @@
import action from '/imports/ui/properties/components/actions/EventButton.vue'; import action from '/imports/ui/properties/components/folders/folderGroupComponents/ActionGroupComponent.vue';
//import adjustment from ''; //import adjustment from '';
import attribute from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue'; import attribute from './folderGroupComponents/AttributeGroupComponent.vue';
//import buff from ''; import buff from '/imports/ui/properties/components/buffs/BuffListItem.vue';
//import buffRemover from ''; //import buffRemover from '';
//import branch from ''; //import branch from '';
//import constant from ''; //import constant from '';
//import container from ''; import container from '/imports/ui/properties/components/inventory/ContainerCard.vue';
//import classComponent from ''; //import classComponent from '';
//import classLevel from ''; //import classLevel from '';
//import damage from ''; //import damage from '';
//import damageMultiplier from ''; //import damageMultiplier from '';
//import effect from ''; //import effect from '';
//import feature from ''; import feature from '/imports/ui/properties/components/features/FeatureCard.vue';
//import folder from ''; // import folder from '';
//import item from ''; import item from '/imports/ui/properties/components/inventory/ItemListTile.vue';
//import note from ''; import note from '/imports/ui/properties/components/persona/NoteCard.vue';
//import pointBuy from ''; //import pointBuy from '';
//import proficiency from ''; //import proficiency from '';
//import propertySlot from ''; //import propertySlot from '';
//import reference from ''; //import reference from '';
//import roll from ''; //import roll from '';
//import savingThrow from ''; //import savingThrow from '';
//import skill from ''; import skill from '/imports/ui/properties/components/skills/SkillListTile.vue';
//import slotFiller from ''; //import slotFiller from '';
//import spellList from ''; //import spellList from '';
//import spell from ''; //import spell from '';
@@ -32,27 +32,27 @@ export default {
action, action,
//adjustment, //adjustment,
attribute, attribute,
//buff, buff,
//buffRemover, //buffRemover,
//branch, //branch,
//constant, //constant,
//container, container,
//class: classComponent, //class: classComponent,
//classLevel, //classLevel,
//damage, //damage,
//damageMultiplier, //damageMultiplier,
//effect, //effect,
//feature, feature,
//folder, //folder,
//item, item,
//note, note,
//pointBuy, //pointBuy,
//proficiency, //proficiency,
//propertySlot, //propertySlot,
//reference, //reference,
//roll, //roll,
//savingThrow, //savingThrow,
//skill, skill,
//slotFiller, //slotFiller,
//spellList, //spellList,
//spell, //spell,

View File

@@ -32,7 +32,7 @@
@change="changeQuantity" @change="changeQuantity"
/> />
</v-list-item-action> </v-list-item-action>
<v-list-item-action> <v-list-item-action class="drag-handle">
<v-icon <v-icon
:disabled="context.editPermission === false" :disabled="context.editPermission === false"
style="height: 100%; width: 40px; cursor: move;" style="height: 100%; width: 40px; cursor: move;"