Big UI overhaul
Moved tabs to bottom nav on mobile Added actions tab Conditional benefits on skills/saves show on stats tab
This commit is contained in:
@@ -21,14 +21,14 @@ let FolderSchema = new createPropertySchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
allowedValues: [
|
||||
'stats', 'features', 'inventory', 'spells', 'journal', 'build'
|
||||
'stats', 'features', 'actions', 'spells', 'inventory', 'journal', 'build'
|
||||
],
|
||||
},
|
||||
location: {
|
||||
type: String,
|
||||
optional: true,
|
||||
allowedValues: [
|
||||
'start', 'events', 'stats', 'skills', 'actions', 'proficiencies', 'end'
|
||||
'start', 'events', 'stats', 'skills', 'proficiencies', 'end'
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,11 +51,14 @@
|
||||
<features-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<inventory-tab :creature-id="creatureId" />
|
||||
<actions-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item v-if="!creature.settings.hideSpellsTab">
|
||||
<spells-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<inventory-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<character-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
@@ -68,6 +71,60 @@
|
||||
</v-tabs-items>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<character-sheet-fab
|
||||
v-if="$vuetify.breakpoint.xsOnly"
|
||||
direction="top"
|
||||
fixed
|
||||
bottom
|
||||
right
|
||||
class="character-sheet-bottom-fab"
|
||||
:edit-permission="editPermission"
|
||||
/>
|
||||
<v-bottom-navigation
|
||||
v-if="$vuetify.breakpoint.xsOnly && creature && creature.settings"
|
||||
app
|
||||
shift
|
||||
mandatory
|
||||
class="bottom-nav-btns"
|
||||
:value="$store.getters.tabById($route.params.id)"
|
||||
@change="e => $store.commit(
|
||||
'setTabForCharacterSheet',
|
||||
{id: $route.params.id, tab: e}
|
||||
)"
|
||||
>
|
||||
<v-btn>
|
||||
<span>Stats</span>
|
||||
<v-icon>mdi-chart-box</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span>Features</span>
|
||||
<v-icon>mdi-text</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span>Actions</span>
|
||||
<v-icon>mdi-lightning-bolt</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span v-if="!creature.settings.hideSpellsTab">Spells</span>
|
||||
<v-icon>mdi-fire</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span>Inventory</span>
|
||||
<v-icon>mdi-cube</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span>Journal</span>
|
||||
<v-icon>mdi-book-open-variant</v-icon>
|
||||
</v-btn>
|
||||
<v-btn>
|
||||
<span>Build</span>
|
||||
<v-icon>mdi-wrench</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-if="creature.settings.showTreeTab">
|
||||
<span>Tree</span>
|
||||
<v-icon>mdi-file-tree</v-icon>
|
||||
</v-btn>
|
||||
</v-bottom-navigation>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -85,16 +142,20 @@ import TreeTab from '/imports/client/ui/creature/character/characterSheetTabs/Tr
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CharacterSheetFab from '/imports/client/ui/creature/character/CharacterSheetFab.vue';
|
||||
import ActionsTab from '/imports/client/ui/creature/character/characterSheetTabs/ActionsTab.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StatsTab,
|
||||
FeaturesTab,
|
||||
InventoryTab,
|
||||
ActionsTab,
|
||||
SpellsTab,
|
||||
InventoryTab,
|
||||
CharacterTab,
|
||||
BuildTab,
|
||||
TreeTab,
|
||||
CharacterSheetFab,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
@@ -171,6 +232,19 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bottom-nav-btns > .v-btn{
|
||||
min-width: 0 !important;
|
||||
padding: 0 !important;
|
||||
flex: 1 1 auto !important;
|
||||
font-size: 0.6rem !important;
|
||||
}
|
||||
.character-sheet-bottom-fab {
|
||||
z-index: 5;
|
||||
bottom: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.character-sheet .v-window-item {
|
||||
min-height: calc(100vh - 96px);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="html">
|
||||
<v-speed-dial
|
||||
v-model="fab"
|
||||
direction="bottom"
|
||||
v-bind="$attrs"
|
||||
:style="!speedDials ? 'visibility: hidden;' : ''"
|
||||
>
|
||||
<template #activator>
|
||||
@@ -96,16 +96,6 @@
|
||||
}, 400);
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
'stats',
|
||||
'features',
|
||||
'inventory',
|
||||
'spells',
|
||||
'journal',
|
||||
'build',
|
||||
'tree',
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabeledFab,
|
||||
@@ -120,21 +110,18 @@
|
||||
creatureId(){
|
||||
return this.$route.params.id;
|
||||
},
|
||||
tabNumber(){
|
||||
let tabNumber = this.$store.getters.tabById(this.creatureId);
|
||||
if (this.hideSpellsTab && tabNumber > 2){
|
||||
tabNumber += 1;
|
||||
}
|
||||
return tabNumber;
|
||||
tabName(){
|
||||
return this.$store.getters.tabNameById(this.creatureId);
|
||||
},
|
||||
speedDials(){
|
||||
return this.speedDialsByTab[tabs[this.tabNumber]];
|
||||
return this.speedDialsByTab[this.tabName];
|
||||
},
|
||||
speedDialsByTab() { return {
|
||||
'stats': ['attribute', 'skill', 'action', 'buff'],
|
||||
'stats': ['attribute', 'skill', 'buff'],
|
||||
'features': ['feature'],
|
||||
'inventory': ['item', 'container'],
|
||||
'spells': ['spellList', 'spell'],
|
||||
'actions': ['action'],
|
||||
'inventory': ['item', 'container'],
|
||||
'journal': ['note'],
|
||||
'tree': [null],
|
||||
};},
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
:dark="isDark"
|
||||
:light="!isDark"
|
||||
clipped-right
|
||||
extended
|
||||
tabs
|
||||
:extended="$vuetify.breakpoint.smAndUp"
|
||||
:tabs="$vuetify.breakpoint.smAndUp"
|
||||
dense
|
||||
>
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
@@ -64,11 +64,14 @@
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-app-bar-nav-icon @click="toggleRightDrawer" />
|
||||
<v-app-bar-nav-icon @click="toggleRightDrawer">
|
||||
<v-icon>mdi-forum</v-icon>
|
||||
</v-app-bar-nav-icon>
|
||||
</template>
|
||||
</v-layout>
|
||||
</v-fade-transition>
|
||||
<v-fade-transition
|
||||
v-if="$vuetify.breakpoint.smAndUp"
|
||||
slot="extension"
|
||||
mode="out-in"
|
||||
>
|
||||
@@ -102,11 +105,14 @@
|
||||
Features
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Inventory
|
||||
Actions
|
||||
</v-tab>
|
||||
<v-tab v-if="!creature.settings.hideSpellsTab">
|
||||
Spells
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Inventory
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Journal
|
||||
</v-tab>
|
||||
@@ -119,7 +125,8 @@
|
||||
</v-tabs>
|
||||
<v-spacer />
|
||||
<character-sheet-fab
|
||||
class="character-sheet-fab"
|
||||
direction="bottom"
|
||||
class="character-sheet-extension-fab"
|
||||
:edit-permission="editPermission"
|
||||
/>
|
||||
</div>
|
||||
@@ -250,7 +257,7 @@ export default {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.character-sheet-fab {
|
||||
.character-sheet-extension-fab {
|
||||
bottom: -24px;
|
||||
right: 8px;
|
||||
margin-left: 16px;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="actions-tab ma-2"
|
||||
>
|
||||
<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="action in actions"
|
||||
:key="action._id"
|
||||
class="action"
|
||||
>
|
||||
<action-card
|
||||
:model="action"
|
||||
:data-id="action._id"
|
||||
@click="clickProperty({_id: action._id})"
|
||||
@sub-click="_id => clickTreeProperty({_id})"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<script lang="js">
|
||||
import ColumnLayout from '/imports/client/ui/components/ColumnLayout.vue';
|
||||
import ActionCard from '/imports/client/ui/properties/components/actions/ActionCard.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
ActionCard,
|
||||
},
|
||||
mixins: [tabFoldersMixin],
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() { return {
|
||||
tabName: 'actions',
|
||||
}},
|
||||
meteor: {
|
||||
actions() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'action',
|
||||
actionType: { $ne: 'event' },
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { actionType: 1, order: 1 },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -9,6 +9,16 @@
|
||||
@sub-click="_id => clickTreeProperty({_id})"
|
||||
@remove="softRemove"
|
||||
/>
|
||||
<div
|
||||
v-if="spellSlots && spellSlots.length || hasSpells"
|
||||
class="spell-slots"
|
||||
>
|
||||
<spell-slot-card
|
||||
:creature-id="creatureId"
|
||||
:spell-slots="spellSlots"
|
||||
:has-spells="hasSpells"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="spellsWithoutList.length">
|
||||
<v-card>
|
||||
<spell-list
|
||||
@@ -44,12 +54,14 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
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';
|
||||
import SpellSlotCard from '/imports/client/ui/properties/components/attributes/SpellSlotCard.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
SpellList,
|
||||
SpellListCard,
|
||||
SpellSlotCard,
|
||||
},
|
||||
mixins: [tabFoldersMixin],
|
||||
props: {
|
||||
@@ -65,6 +77,20 @@ export default {
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
spellSlots() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
inactive: { $ne: true },
|
||||
removed: { $ne: true },
|
||||
overridden: { $ne: true },
|
||||
type: 'attribute',
|
||||
attributeType: 'spellSlot',
|
||||
$nor: [
|
||||
{ hideWhenTotalZero: true, total: 0 },
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
});
|
||||
},
|
||||
spellLists() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
@@ -75,6 +101,14 @@ export default {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
hasSpells() {
|
||||
return !!CreatureProperties.findOne({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'spell',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
});
|
||||
},
|
||||
spellsWithoutList() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
|
||||
@@ -65,8 +65,8 @@
|
||||
/>
|
||||
|
||||
<damage-multiplier-card
|
||||
v-if="properties.multiplier && properties.multiplier.length"
|
||||
:multipliers="properties.multiplier"
|
||||
v-if="properties.damageMultiplier && properties.damageMultiplier.length"
|
||||
:multipliers="properties.damageMultiplier"
|
||||
@click-multiplier="clickProperty"
|
||||
/>
|
||||
|
||||
@@ -198,38 +198,13 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="properties.attribute.spellSlot && properties.attribute.spellSlot.length || hasSpells"
|
||||
v-if="properties.attribute.spellSlot && properties.attribute.spellSlot.length"
|
||||
class="spell-slots"
|
||||
>
|
||||
<v-card data-id="spell-slot-card">
|
||||
<v-list
|
||||
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 properties.attribute.spellSlot"
|
||||
:key="spellSlot._id"
|
||||
:model="spellSlot"
|
||||
:data-id="spellSlot._id"
|
||||
@click="clickProperty({_id: spellSlot._id})"
|
||||
/>
|
||||
</v-list>
|
||||
<div
|
||||
v-if="hasSpells"
|
||||
class="d-flex justify-end"
|
||||
>
|
||||
<v-btn
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
outlined
|
||||
@click="castSpell"
|
||||
>
|
||||
Cast a spell
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
<spell-slot-card
|
||||
:creature-id="creatureId"
|
||||
:spell-slots="properties.attribute.spellSlot"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<folder-group-card
|
||||
@@ -255,6 +230,19 @@
|
||||
:data-id="save._id"
|
||||
@click="clickProperty({_id: save._id})"
|
||||
/>
|
||||
<v-list-item
|
||||
v-for="(effect, index) in saveConditionals"
|
||||
:key="effect._id"
|
||||
:data-id="effect._id"
|
||||
:class="{'mt-2': !index}"
|
||||
@click="clickProperty({_id: effect._id})"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle style="white-space: unset;">
|
||||
{{ effect.text }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -273,6 +261,19 @@
|
||||
:data-id="skill._id"
|
||||
@click="clickProperty({_id: skill._id})"
|
||||
/>
|
||||
<v-list-item
|
||||
v-for="(effect, index) in skillConditionals"
|
||||
:key="effect._id"
|
||||
:data-id="effect._id"
|
||||
:class="{'mt-2': !index}"
|
||||
@click="clickProperty({_id: effect._id})"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle style="white-space: unset;">
|
||||
{{ effect.text }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -286,28 +287,6 @@
|
||||
@remove="softRemove"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-for="action in properties.action"
|
||||
:key="action._id"
|
||||
class="action"
|
||||
>
|
||||
<action-card
|
||||
:model="action"
|
||||
:data-id="action._id"
|
||||
@click="clickProperty({_id: action._id})"
|
||||
@sub-click="_id => clickTreeProperty({_id})"
|
||||
/>
|
||||
</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="properties.skill.weapon && properties.skill.weapon.length"
|
||||
class="weapon-proficiencies"
|
||||
@@ -422,17 +401,15 @@ import DamageMultiplierCard from '/imports/client/ui/properties/components/damag
|
||||
import HitDiceListTile from '/imports/client/ui/properties/components/attributes/HitDiceListTile.vue';
|
||||
import SkillListTile from '/imports/client/ui/properties/components/skills/SkillListTile.vue';
|
||||
import ResourceCard from '/imports/client/ui/properties/components/attributes/ResourceCard.vue';
|
||||
import SpellSlotListTile from '/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import ActionCard from '/imports/client/ui/properties/components/actions/ActionCard.vue';
|
||||
import RestButton from '/imports/client/ui/creature/RestButton.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ToggleCard from '/imports/client/ui/properties/components/toggles/ToggleCard.vue';
|
||||
import BuffListItem from '/imports/client/ui/properties/components/buffs/BuffListItem.vue';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import SpellSlotCard from '/imports/client/ui/properties/components/attributes/SpellSlotCard.vue';
|
||||
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 { get, set } from 'lodash';
|
||||
import { get, set, uniqBy } from 'lodash';
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'
|
||||
|
||||
function walkDown(forest, callback){
|
||||
@@ -487,7 +464,7 @@ const propertyHandlers = {
|
||||
if (prop.actionType === 'event') {
|
||||
return { propPath: 'event' };
|
||||
}
|
||||
return { propPath: 'action' };
|
||||
return { propPath: null };
|
||||
},
|
||||
}
|
||||
|
||||
@@ -503,8 +480,7 @@ export default {
|
||||
HitDiceListTile,
|
||||
SkillListTile,
|
||||
ResourceCard,
|
||||
SpellSlotListTile,
|
||||
ActionCard,
|
||||
SpellSlotCard,
|
||||
ToggleCard,
|
||||
EventButton,
|
||||
FolderGroupCard,
|
||||
@@ -520,6 +496,30 @@ export default {
|
||||
doCheckLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
saveConditionals(){
|
||||
const conditionals = [];
|
||||
this.properties.skill?.save?.forEach(prop => {
|
||||
prop?.effects?.forEach(effect => {
|
||||
if (effect.operation === 'conditional') {
|
||||
conditionals.push(effect);
|
||||
}
|
||||
});
|
||||
});
|
||||
return uniqBy(conditionals, '_id');
|
||||
},
|
||||
skillConditionals(){
|
||||
const conditionals = [];
|
||||
this.properties.skill?.skill?.forEach(prop => {
|
||||
prop?.effects?.forEach(effect => {
|
||||
if (effect.operation === 'conditional') {
|
||||
conditionals.push(effect);
|
||||
}
|
||||
});
|
||||
});
|
||||
return uniqBy(conditionals, '_id');
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
properties() {
|
||||
const creature = this.creature;
|
||||
@@ -531,11 +531,22 @@ export default {
|
||||
{ type: 'toggle' },
|
||||
],
|
||||
removed: { $ne: true },
|
||||
type: {
|
||||
$in: [
|
||||
'action',
|
||||
'attribute',
|
||||
'buff',
|
||||
'damageMultiplier',
|
||||
'folder',
|
||||
'skill',
|
||||
'toggle',
|
||||
]
|
||||
}
|
||||
};
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
}
|
||||
const allProps = CreatureProperties.find(filter);
|
||||
const allProps = CreatureProperties.find(filter, { sort: { order: 1 } });
|
||||
const forest = nodeArrayToTree(allProps);
|
||||
const properties = { folder: {}, attribute: {}, skill: {} };
|
||||
walkDown(forest, node => {
|
||||
@@ -555,6 +566,7 @@ export default {
|
||||
}
|
||||
return { skipChildren };
|
||||
});
|
||||
properties.damageMultiplier?.sort((a, b) => a.value - b.value);
|
||||
return properties;
|
||||
},
|
||||
creature() {
|
||||
@@ -572,9 +584,6 @@ export default {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
hasSpells() {
|
||||
return this.properties?.spell?.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty({ _id }) {
|
||||
@@ -611,30 +620,6 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
castSpell() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: 'spell-slot-card',
|
||||
data: {
|
||||
creatureId: this.creatureId,
|
||||
},
|
||||
callback({ spellId, slotId, advantage, ritual } = {}) {
|
||||
if (!spellId) return;
|
||||
doCastSpell.call({
|
||||
spellId,
|
||||
slotId,
|
||||
ritual,
|
||||
scope: {
|
||||
$attackAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<div class="d-flex">
|
||||
<creature-storage-stats />
|
||||
<archive-button />
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
class="creature-storage-stats"
|
||||
style="display: inline-block;"
|
||||
class="creature-storage-stats d-flex align-center"
|
||||
style="white-space: nowrap;"
|
||||
>
|
||||
{{ creatureCount }} /
|
||||
<div>
|
||||
{{ creatureCount }} /
|
||||
</div>
|
||||
<v-icon v-if="characterSlots === -1">
|
||||
mdi-infinity
|
||||
</v-icon>
|
||||
<template v-else>
|
||||
<div v-else>
|
||||
{{ characterSlots }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -102,9 +102,12 @@
|
||||
let stackLength = this.$store.state.dialogStack.dialogs.length - offset;
|
||||
if (stackLength){
|
||||
let topDialog = this.$refs[stackLength - 1][0];
|
||||
return topDialog.$el.querySelector(`[data-id='${elementId}']`);
|
||||
// First look in the active window, then look elsewhere
|
||||
return topDialog.$el.querySelector(`.v-window-item--active [data-id='${elementId}']`) ??
|
||||
topDialog.$el.querySelector(`[data-id='${elementId}']`);
|
||||
} else {
|
||||
return document.querySelector(`[data-id='${elementId}']`);
|
||||
return document.querySelector(`.v-window-item--active [data-id='${elementId}']`) ??
|
||||
document.querySelector(`[data-id='${elementId}']`);
|
||||
}
|
||||
},
|
||||
enter(target, done){
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
app
|
||||
color="secondary"
|
||||
dark
|
||||
tabs
|
||||
:extended="$vuetify.breakpoint.smAndUp"
|
||||
:tabs="$vuetify.breakpoint.smAndUp"
|
||||
dense
|
||||
>
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
@@ -25,11 +26,17 @@
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-fade-transition mode="out-in">
|
||||
<div :key="$route.meta.title">
|
||||
<div
|
||||
:key="$route.meta.title"
|
||||
style="
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;"
|
||||
>
|
||||
<router-view name="toolbarItems" />
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<v-fade-transition
|
||||
v-if="$vuetify.breakpoint.smAndUp"
|
||||
slot="extension"
|
||||
mode="out-in"
|
||||
>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
app
|
||||
color="secondary"
|
||||
dark
|
||||
tabs
|
||||
extended
|
||||
:extended="$vuetify.breakpoint.smAndUp"
|
||||
:tabs="$vuetify.breakpoint.smAndUp"
|
||||
dense
|
||||
>
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<v-card data-id="spell-slot-card">
|
||||
<v-list
|
||||
v-if="spellSlots.length"
|
||||
two-line
|
||||
subheader
|
||||
>
|
||||
<v-subheader>Spell Slots</v-subheader>
|
||||
<spell-slot-list-tile
|
||||
v-for="spellSlot in spellSlots"
|
||||
:key="spellSlot._id"
|
||||
:model="spellSlot"
|
||||
:data-id="`spell-slot-card-${spellSlot._id}`"
|
||||
@click="clickProperty({_id: spellSlot._id})"
|
||||
/>
|
||||
</v-list>
|
||||
<div
|
||||
v-if="hasSpells"
|
||||
class="d-flex justify-end"
|
||||
>
|
||||
<v-btn
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
outlined
|
||||
@click="castSpell"
|
||||
>
|
||||
Cast a spell
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SpellSlotListTile from '/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SpellSlotListTile,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hasSpells: Boolean,
|
||||
spellSlots: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
castSpell() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: 'spell-slot-card',
|
||||
data: {
|
||||
creatureId: this.creatureId,
|
||||
},
|
||||
callback({ spellId, slotId, advantage, ritual } = {}) {
|
||||
if (!spellId) return;
|
||||
doCastSpell.call({
|
||||
spellId,
|
||||
slotId,
|
||||
ritual,
|
||||
scope: {
|
||||
$attackAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
clickProperty({ _id }) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `spell-slot-card-${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -46,8 +46,9 @@
|
||||
:items="[
|
||||
{ text: 'Stats Tab', value: 'stats' },
|
||||
{ text: 'Features Tab', value: 'features' },
|
||||
{ text: 'Inventory Tab', value: 'inventory' },
|
||||
{ text: 'Actions Tab', value: 'actions' },
|
||||
{ text: 'Spells Tab', value: 'spells' },
|
||||
{ text: 'Inventory Tab', value: 'inventory' },
|
||||
{ text: 'Journal Tab', value: 'journal' },
|
||||
{ text: 'Build Tab', value: 'build' },
|
||||
]"
|
||||
@@ -90,7 +91,6 @@ export default {
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
@@ -2,8 +2,8 @@ import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import dialogStackStore from '/imports/client/ui/dialogStack/dialogStackStore.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
const tabs = ['stats', 'features', 'inventory', 'spells', 'journal', 'build', 'tree'];
|
||||
const tabsWithoutSpells = ['stats', 'features', 'inventory', 'journal', 'build', 'tree'];
|
||||
const tabs = ['stats', 'features', 'actions', 'spells', 'inventory', 'journal', 'build', 'tree'];
|
||||
const tabsWithoutSpells = ['stats', 'features', 'actions', 'inventory', 'journal', 'build', 'tree'];
|
||||
|
||||
Vue.use(Vuex);
|
||||
const store = new Vuex.Store({
|
||||
@@ -20,14 +20,10 @@ const store = new Vuex.Store({
|
||||
},
|
||||
getters: {
|
||||
tabById: (state) => (id) => {
|
||||
if (id in state.characterSheetTabs){
|
||||
return state.characterSheetTabs[id];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return state.characterSheetTabs[id] ?? 0;
|
||||
},
|
||||
tabNameById: (state) => (id) => {
|
||||
const tabNumber = state.characterSheetTabs[id];
|
||||
const tabNumber = state.characterSheetTabs[id] ?? 0;
|
||||
const creature = Creatures.findOne(id);
|
||||
if (creature?.settings?.hideSpellsTab) {
|
||||
return tabsWithoutSpells[tabNumber];
|
||||
@@ -37,26 +33,26 @@ const store = new Vuex.Store({
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
toggleDrawer (state) {
|
||||
toggleDrawer(state) {
|
||||
state.drawer = !state.drawer;
|
||||
},
|
||||
toggleRightDrawer (state) {
|
||||
toggleRightDrawer(state) {
|
||||
state.rightDrawer = !state.rightDrawer;
|
||||
},
|
||||
setDrawer (state, value) {
|
||||
setDrawer(state, value) {
|
||||
state.drawer = value;
|
||||
},
|
||||
setRightDrawer (state, value) {
|
||||
setRightDrawer(state, value) {
|
||||
state.rightDrawer = value;
|
||||
},
|
||||
setPageTitle (state, value) {
|
||||
setPageTitle(state, value) {
|
||||
state.pageTitle = value;
|
||||
document.title = value;
|
||||
},
|
||||
setTabForCharacterSheet(state, {tab, id}){
|
||||
setTabForCharacterSheet(state, { tab, id }) {
|
||||
Vue.set(state.characterSheetTabs, id, tab);
|
||||
},
|
||||
setShowDetailsDialog(state, value){
|
||||
setShowDetailsDialog(state, value) {
|
||||
state.showDetailsDialog = value;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user