Improved spell slot casting dialog with search, filters, spell details

This commit is contained in:
Stefan Zermatten
2021-01-22 14:09:23 +02:00
parent 100c93b5ae
commit c780c11e3f
5 changed files with 149 additions and 7 deletions

View File

@@ -26,6 +26,10 @@ export default {
value: [String, Number, Date, Array, Object, Boolean],
errorMessages: [String, Array],
disabled: Boolean,
debounce: {
type: Number,
default: undefined,
},
},
watch: {
focused(newFocus){
@@ -113,7 +117,9 @@ export default {
return this.context.editPermission === false || this.disabled;
},
debounceTime() {
if (Number.isFinite(this.context.debounceTime)){
if (Number.isFinite(this.debounce)){
return this.debounce;
} else if (Number.isFinite(this.context.debounceTime)){
return this.context.debounceTime;
} else {
return 750;

View File

@@ -6,7 +6,7 @@
:error-messages="errors"
:value="safeValue"
:disabled="isDisabled"
box
:box="!regular"
@input="input"
@focus="focused = true"
@blur="focused = false"
@@ -18,5 +18,8 @@
export default {
mixins: [SmartInput],
props: {
regular: Boolean,
},
};
</script>

View File

@@ -5,7 +5,66 @@
Cast a Spell
</v-toolbar-title>
<v-spacer />
<v-input icon="search" />
<text-field
ref="focusFirst"
label="Name"
prepend-inner-icon="search"
regular
hide-details
:value="searchValue"
:error-messages="searchError"
:debounce="200"
@change="searchChanged"
/>
<v-menu
v-model="filterMenuOpen"
left
:close-on-content-click="false"
>
<template #activator="{ on }">
<v-btn
icon
flat
:class="{'primary--text': filtersApplied}"
v-on="on"
>
<v-icon>filter_list</v-icon>
</v-btn>
</template>
<v-list>
<v-list-tile
v-for="filter in booleanFilters"
:key="filter.name"
style="height: 52px;"
>
<v-checkbox
v-model="filter.enabled"
style="flex-grow: 0; margin-right: 8px;"
/>
<v-switch
v-model="filter.value"
:disabled="!filter.enabled"
:label="filter.name"
/>
</v-list-tile>
<div class="layout row">
<v-btn
flat
@click="clearBooleanFilters"
>
Clear
</v-btn>
<v-spacer />
<v-btn
flat
class="primary--text"
@click="filterMenuOpen = false"
>
Done
</v-btn>
</div>
</v-list>
</v-menu>
</template>
<split-list-layout>
<template slot="left">
@@ -56,9 +115,11 @@
v-else
:key="spell._id"
hide-handle
show-info-button
:class="{ 'primary--text': selectedSpellId === spell._id}"
:model="spell"
@click="selectedSpellId = spell._id"
@show-info="spellDialog(spell._id)"
/>
</template>
</template>
@@ -119,6 +180,16 @@ export default {
searchString: undefined,
selectedSlotId: this.slotId,
selectedSpellId: this.spellId,
searchValue: undefined,
searchError: undefined,
filterMenuOpen: false,
booleanFilters: {
verbal: {name: 'Verbal', enabled: false, value: false},
somatic: {name: 'Somatic', enabled: false, value: false},
material: {name: 'Material', enabled: false, value: false},
concentration: {name: 'Concentration', enabled: false, value: false},
ritual: {name: 'Ritual', enabled: false, value: false},
},
}},
computed: {
computedSpells(){
@@ -135,7 +206,15 @@ export default {
} else {
return slot.spellSlotLevelValue >= spell.level;
}
}
},
filtersApplied(){
for (let key in this.booleanFilters){
if (this.booleanFilters[key].enabled){
return true;
}
}
return false;
},
},
watch: {
selectedSpell(spell){
@@ -152,16 +231,53 @@ export default {
}
},
},
methods: {
clearBooleanFilters(){
for (let key in this.booleanFilters){
this.booleanFilters[key].enabled = false;
}
},
spellDialog(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `spell-info-btn-${_id}`,
data: {_id},
});
},
searchChanged(val, ack){
this.searchValue = val;
setTimeout(ack, 200);
},
},
meteor: {
spells(){
let slotLevel = this.selectedSlot && this.selectedSlot.spellSlotLevelValue || 0;
return CreatureProperties.find({
let filter = {
'ancestors.id': this.creatureId,
removed: {$ne: true},
inactive: {$ne: true},
prepared: true,
level: {$lte: slotLevel},
}, {
};
// Apply the filters from the filter menu
for (let key in this.booleanFilters){
if (this.booleanFilters[key].enabled){
let value = this.booleanFilters[key].value;
if (key === 'material'){
filter[key] = {$exists: this.booleanFilters[key].value};
} else {
filter[key] = value ? true: {$ne: true};
}
}
}
// Apply the search string to the name field
if (this.searchValue){
filter.name = {
$regex: this.searchValue.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'),
$options: 'i'
};
}
return CreatureProperties.find(filter, {
sort: {order: 1}
});
},
@@ -186,7 +302,7 @@ export default {
},
selectedSpell(){
return CreatureProperties.findOne(this.selectedSpellId);
}
},
},
}
</script>

View File

@@ -33,6 +33,16 @@
>
drag_indicator
</v-icon>
<v-btn
v-else-if="showInfoButton"
icon
flat
class="info-icon"
:data-id="`spell-info-btn-${model._id}`"
@click.stop="$emit('show-info')"
>
<v-icon>info</v-icon>
</v-btn>
</v-list-tile-action>
</v-list-tile>
</template>
@@ -46,6 +56,7 @@ export default {
props: {
preparingSpells: Boolean,
hideHandle: Boolean,
showInfoButton: Boolean,
},
computed: {
hasClickListener(){
@@ -86,4 +97,7 @@ export default {
.primary--text .v-icon, .primary--text .v-list__tile__sub-title {
color: #b71c1c
}
.theme--light.info-icon{
color: rgba(0,0,0,.54) !important;
}
</style>

View File

@@ -0,0 +1,3 @@
RegExp.escape = function(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
};