Finished implementing new slot fill dialog, including ability to test slots

This commit is contained in:
Stefan Zermatten
2021-08-10 14:35:27 +02:00
parent b041db22e4
commit 741a9b080a
4 changed files with 73 additions and 339 deletions

View File

@@ -1,310 +0,0 @@
<template lang="html">
<dialog-base
:color="model.color"
dark-body
>
<template slot="toolbar">
<v-toolbar-title>
{{ model.name }}
</v-toolbar-title>
<v-spacer />
<text-field
prepend-inner-icon="mdi-magnify"
regular
hide-details
:value="searchValue"
:debounce="300"
@change="searchChanged"
@keyup.enter="insert"
/>
</template>
<p
v-if="model.description"
class="description"
>
{{ model.description }}
</p>
<div
class="library-nodes"
>
<v-fade-transition mode="out-in">
<div v-if="libraryNodes && libraryNodes.length">
<v-container fluid>
<v-row
dense
align="center"
align-md="start"
>
<v-col
v-for="node in libraryNodes"
:key="node._id"
cols="12"
sm="6"
md="4"
>
<v-card
hover
ripple
class="slot-card layout column justify-end"
:class="{'selected': node._id === (selectedNode && selectedNode._id)}"
:dark="node._id === (selectedNode && selectedNode._id)"
@click="selectedNode = node"
>
<v-img
v-if="node.picture"
:src="node.picture"
:height="200"
contain
class="slot-card-image"
/>
<v-card-title primary-title>
<tree-node-view
class="mr-2 text-h6 mb-0"
:class="{'theme--dark': node._id === (selectedNode && selectedNode._id)}"
:hide-icon="node.picture"
:model="node"
:color="node.color"
/>
</v-card-title>
<v-card-text
v-if="node.description"
class="pt-0"
>
<property-description
class="slot-card-text line-clamp"
:string="node.description"
/>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
<div
v-else-if="countAll"
class="ma-4"
>
<h4 v-if="numFiltered">
Requirements of {{ numFiltered }} library properties were not met.
</h4>
<h4 v-else>
Nothing suitable was found in your libraries.
</h4>
</div>
<div
v-else-if="$subReady.slotFillers"
class="ma-4"
>
<h4>
Nothing suitable was found in your libraries
<span v-if="searchValue">
matching "{{ searchValue }}"
</span>
</h4>
<p>
This slot requires a {{ slotPropertyTypeName }}
<template v-if="model.slotTags.length == 1">
with the tag <code>{{ model.slotTags[0] }}</code>,
</template>
<template v-else-if="model.slotTags.length > 1">
with the following tags:
<span
v-for="(tag, index) in model.slotTags"
:key="index"
>
<code>{{ tag }}</code>,
</span>
</template>
<span v-if="model.spaceLeft">
that fills less than {{ model.spaceLeft }} {{ model.spaceLeft == 1 && 'slot' || 'slots' }}
</span>
</p>
</div>
</v-fade-transition>
<v-fade-transition mode="out-in">
<div
v-if="!$subReady.slotFillers"
key="character-loading"
class="fill-height layout justify-center align-center"
>
<v-progress-circular
indeterminate
color="primary"
size="64"
/>
</div>
</v-fade-transition>
<v-fade-transition mode="out-in">
<div
v-if="currentLimit < countAll"
class="layout justify-center align-stretch"
>
<v-btn
:loading="!$subReady.slotFillers"
class="primary"
@click="loadMore"
>
Load More
</v-btn>
</div>
</v-fade-transition>
</div>
<template slot="actions">
<v-spacer />
<v-btn
text
@click="$store.dispatch('popDialogStack')"
>
Cancel
</v-btn>
<v-btn
text
:disabled="!selectedNode"
@click="insert"
>
Insert
</v-btn>
</template>
</dialog-base>
</template>
<script lang="js">
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import PROPERTIES from '/imports/constants/PROPERTIES.js';
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
export default {
components: {
DialogBase,
TreeNodeView,
PropertyDescription,
},
props:{
slotId: {
type: String,
required: true,
},
creatureId: {
type: String,
required: true,
},
},
data(){return {
selectedNode: undefined,
searchValue: undefined,
numFiltered: 0,
}},
computed: {
slotPropertyTypeName(){
if (!this.model) return;
if (!this.model.slotType) return 'property';
let propName = getPropertyName(this.model.slotType);
return propName && propName.toLowerCase();
},
},
reactiveProvide: {
name: 'context',
include: ['creatureId'],
},
methods:{
getTitle(model){
if (!model) return;
if (model.name) return model.name;
let prop = PROPERTIES[model.type]
return prop && prop.name;
},
searchChanged(val, ack){
this._subs['slotFillers'].setData('searchTerm', val);
this._subs['slotFillers'].setData('limit', undefined);
this.selectedNode = undefined;
this.searchValue = val;
setTimeout(ack, 200);
},
loadMore(){
if (this.currentLimit >= this.countAll) return;
this._subs['slotFillers'].setData('limit', this.currentLimit + 50);
},
insert(){
if (!this.selectedNode) return;
this.$store.dispatch('popDialogStack', this.selectedNode);
}
},
meteor: {
$subscribe: {
'slotFillers'(){
return [this.slotId]
},
},
model(){
return CreatureProperties.findOne(this.slotId);
},
creature(){
return Creatures.findOne(this.creatureId);
},
currentLimit(){
return this._subs['slotFillers'].data('limit') || 50;
},
countAll(){
return this._subs['slotFillers'].data('countAll');
},
libraryNodes(){
let filter = {
removed: {$ne: true},
};
if (this.model.slotTags && this.model.slotTags.length){
filter.tags = {$all: this.model.slotTags};
}
if (this.model.slotType){
filter.$or = [{
type: this.model.slotType
},{
type: 'slotFiller',
slotFillerType: this.model.slotType,
}];
}
let nodes = LibraryNodes.find(filter, {
sort: {name: 1, order: 1}
}).fetch();
let totalNodes = nodes.length;
// Filter out slotFillers whose condition isn't met or are too big to fit
// the quantity to fill
nodes = nodes.filter(node => {
if (node.slotFillerCondition){
let {result} = evaluateString({
string: node.slotFillerCondition,
scope: this.creature.variables,
fn: 'reduce',
});
if (!result.value) return false;
}
if (
node.type === 'slotFiller' &&
this.model.spaceLeft > 0 &&
node.slotQuantityFilled > this.model.spaceLeft
){
return false;
}
return true;
});
this.numFiltered = totalNodes - nodes.length;
if (nodes.length === 1) this.selectedNode = nodes[0];
return nodes;
},
}
}
</script>
<style lang="css" scoped>
.slot-card-text.line-clamp {
-webkit-line-clamp: 5;
}
.slot-card.selected {
background: #8E1B1B;
}
</style>

View File

@@ -60,8 +60,8 @@
v-if="libraryNode._disabledByAlreadyAdded"
class="my-0 py-0"
hide-details
:value="true"
:disabled="true"
:input-value="true"
disabled
/>
<v-checkbox
v-else
@@ -160,26 +160,24 @@
<v-btn
text
color="primary"
:disabled="!selectedNodeIds.length"
:disabled="!dummySlot && !selectedNodeIds.length"
@click="$store.dispatch('popDialogStack', selectedNodeIds)"
>
<template v-if="model.spaceLeft">
{{ totalQuantitySelected }} / {{ model.spaceLeft }}
</template>
Insert
<template v-if="slotId">
Insert
</template>
<template v-else>
Close Test
</template>
</v-btn>
</template>
</dialog-base>
</template>
<script lang="js">
/**
* TODO
* Enforce unique in slot/unique in character selection rules
* Fix the dialog callback for multiple property inserting
* Show the dialog in library view to test slots
* Delete the old slot fill dialog
*/
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
@@ -192,6 +190,7 @@ import Libraries from '/imports/api/library/Libraries.js';
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import { clone } from 'lodash';
export default {
components: {
@@ -204,11 +203,15 @@ export default {
props:{
slotId: {
type: String,
required: true,
default: undefined,
},
creatureId: {
type: String,
required: true,
default: undefined,
},
dummySlot: {
type: Object,
default: undefined,
},
},
data(){return {
@@ -287,9 +290,17 @@ export default {
},
},
model(){
return CreatureProperties.findOne(this.slotId);
if (this.slotId){
return CreatureProperties.findOne(this.slotId);
} else if (this.dummySlot) {
let model = clone(this.dummySlot)
model.quantityExpectedResult = +model.quantityExpected;
model.spaceLeft = model.quantityExpectedResult;
return model;
}
},
creature(){
if (!this.creatureId) return {variables: {}};
return Creatures.findOne(this.creatureId);
},
currentLimit(){
@@ -300,7 +311,7 @@ export default {
},
alreadyAdded(){
let added = new Set();
if (this.model.unique) return added;
if (!this.model.unique) return added;
let ancestorId;
if (this.model.unique === 'uniqueInSlot'){
ancestorId = this.model._id;
@@ -310,6 +321,7 @@ export default {
CreatureProperties.find({
'ancestors.id': ancestorId,
libraryNodeId: {$exists: true},
removed: {$ne: true},
}, {
fields: {libraryNodeId: 1},
}).forEach(prop => {
@@ -382,7 +394,13 @@ export default {
}
});
this.disabledNodeCount = disabledNodeCount;
if (activeNodeCount === 1) this.selectedNodeIds = [lastActiveNodeId];
if (
activeNodeCount === 1 &&
this.$subReady.slotFillers &&
this.currentLimit >= this.countAll
) {
this.selectedNodeIds = [lastActiveNodeId]
}
return nodes;
},
}

View File

@@ -90,10 +90,10 @@ export default {
slotId,
creatureId,
},
callback(node){
if(!node) return;
callback(nodeIds){
if (!nodeIds || !nodeIds.length) return;
let newPropertyId = insertPropertyFromLibraryNode.call({
nodeIds: [node._id],
nodeIds,
parentRef: {
'id': slotId,
'collection': 'creatureProperties',

View File

@@ -92,6 +92,29 @@
/>
<calculation-error-list :errors="model.slotConditionErrors" />
<smart-select
label="Unique"
style="flex-basis: 300px;"
clearable
hint="Do the properties that fill this slot need to be unique?"
:items="uniqueOptions"
:value="model.unique"
:error-messages="errors.unique"
@change="change('unique', ...arguments)"
/>
<v-layout justify-center>
<v-btn
v-if="context.isLibraryForm"
color="accent"
class="ma-2 mb-4"
data-id="test-slot-button"
@click="testSlot"
>
Test Slot
</v-btn>
</v-layout>
<text-area
label="Description"
:value="model.description"
@@ -122,16 +145,6 @@
@change="change('ignored', ...arguments)"
/>
</div>
<smart-select
label="Unique"
style="flex-basis: 300px;"
clearable
hint="Do the properties that fill this slot need to be unique?"
:items="uniqueOptions"
:value="model.unique"
:error-messages="errors.unique"
@change="change('unique', ...arguments)"
/>
<smart-combobox
label="Tags"
hint="This slot's own tags which will be used to fill other slots"
@@ -158,6 +171,9 @@
CalculationErrorList,
},
mixins: [propertyFormMixin],
inject: {
context: { default: {} }
},
data(){
let slotTypes = [];
for (let key in PROPERTIES){
@@ -199,6 +215,16 @@
ack: this.acknowledgeAddResult,
});
},
testSlot(){
if (!this.context.isLibraryForm) return;
this.$store.commit('pushDialogStack', {
component: 'slot-fill-dialog',
elementId: 'test-slot-button',
data: {
dummySlot: this.model,
},
});
}
},
};
</script>