302 lines
8.3 KiB
Vue
302 lines
8.3 KiB
Vue
<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-search"
|
|
regular
|
|
hide-details
|
|
:value="searchValue"
|
|
:debounce="300"
|
|
@change="searchChanged"
|
|
@keyup.enter="insert"
|
|
/>
|
|
</template>
|
|
<div
|
|
class="library-nodes"
|
|
>
|
|
<v-fade-transition mode="out-in">
|
|
<div v-if="libraryNodes && libraryNodes.length">
|
|
<section
|
|
class="layout wrap justify-between"
|
|
>
|
|
<v-card
|
|
v-for="node in libraryNodes"
|
|
:key="node._id"
|
|
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>
|
|
</section>
|
|
</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 + 20);
|
|
},
|
|
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') || 20;
|
|
},
|
|
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 {
|
|
max-width: 500px;
|
|
width: 300px;
|
|
flex-grow: 1;
|
|
flex-shrink: 1;
|
|
margin: 4px;
|
|
}
|
|
.slot-card-text.line-clamp {
|
|
-webkit-line-clamp: 5;
|
|
}
|
|
.slot-card.selected {
|
|
background: #8E1B1B;
|
|
}
|
|
</style>
|