First implementation on Slots UI
This commit is contained in:
@@ -33,11 +33,6 @@ let SlotSchema = new SimpleSchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// How many properties have been selected to fill this slot
|
||||
quantityFilled: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySlotSchema = new SimpleSchema({
|
||||
|
||||
@@ -6,3 +6,4 @@ import '/imports/server/publications/experiences.js';
|
||||
import '/imports/server/publications/users.js';
|
||||
import '/imports/server/publications/icons.js';
|
||||
import '/imports/server/publications/tabletops.js';
|
||||
import '/imports/server/publications/slotFillers.js'
|
||||
|
||||
47
app/imports/server/publications/slotFillers.js
Normal file
47
app/imports/server/publications/slotFillers.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
|
||||
Meteor.publish('slotFillers', function(slotId){
|
||||
this.autorun(function (){
|
||||
if (!this.userId) {
|
||||
return this.ready();
|
||||
}
|
||||
// Get the slot
|
||||
console.log({slotId});
|
||||
let slot = CreatureProperties.findOne(slotId);
|
||||
if (!slot){
|
||||
return this.ready()
|
||||
}
|
||||
|
||||
// Get all the ids of libraries the user can access
|
||||
const user = Meteor.users.findOne(this.userId, {
|
||||
fields: {subscribedLibraries: 1}
|
||||
});
|
||||
const subs = user && user.subscribedLibraries || [];
|
||||
let libraryIds = Libraries.find({
|
||||
$or: [
|
||||
{owner: this.userId},
|
||||
{writers: this.userId},
|
||||
{readers: this.userId},
|
||||
{_id: {$in: subs}},
|
||||
]
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).map(lib => lib._id);
|
||||
|
||||
console.log(libraryIds);
|
||||
|
||||
// Build a filter for nodes in those libraries that match the slot
|
||||
let filter = {
|
||||
'ancestors.id': {$in: libraryIds},
|
||||
'tags': {$all: slot.slotTags},
|
||||
};
|
||||
if (slot.slotType){
|
||||
filter.type = slot.slotType;
|
||||
}
|
||||
console.log(filter);
|
||||
console.log(LibraryNodes.find(filter).fetch());
|
||||
return LibraryNodes.find(filter);
|
||||
});
|
||||
});
|
||||
@@ -83,8 +83,8 @@
|
||||
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
||||
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
||||
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
||||
import PersonaTab from '/imports/ui/creature/character/characterSheetTabs/PersonaTab.vue';
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/CharacterTab.vue';
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
FeaturesTab,
|
||||
InventoryTab,
|
||||
SpellsTab,
|
||||
PersonaTab,
|
||||
CharacterTab,
|
||||
TreeTab,
|
||||
},
|
||||
props: {
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
Spells
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Persona
|
||||
Character
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Tree
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
Spells
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Persona
|
||||
Character
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
Tree
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<div>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<slots :creature-id="creatureId" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<div>
|
||||
<v-card class="class-details">
|
||||
<v-card-title
|
||||
@@ -101,12 +108,14 @@ import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js'
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||
import Slots from '/imports/ui/creature/slots/Slots.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
NoteCard,
|
||||
Slots,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
92
app/imports/ui/creature/slots/SlotFillDialog.vue
Normal file
92
app/imports/ui/creature/slots/SlotFillDialog.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template lang="html">
|
||||
<dialog-base :color="model.color">
|
||||
<template slot="toolbar">
|
||||
<v-toolbar-title>
|
||||
{{ model.name }}
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<div class="library-nodes">
|
||||
<v-list>
|
||||
<v-list-tile
|
||||
v-for="node in libraryNodes"
|
||||
:key="node._id"
|
||||
:class="node._id === (selectedNode && selectedNode._id) && 'primary--text'"
|
||||
@click="selectedNode = node"
|
||||
>
|
||||
<tree-node-view
|
||||
:model="node"
|
||||
:selected="node._id === (selectedNode && selectedNode._id)"
|
||||
/>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
<h4
|
||||
v-if="!libraryNodes.length"
|
||||
class="ma-4"
|
||||
>
|
||||
Nothing suitable was found in your libraries
|
||||
</h4>
|
||||
</div>
|
||||
<template slot="actions">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
flat
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
flat
|
||||
:disabled="!selectedNode"
|
||||
@click="$store.dispatch('popDialogStack', selectedNode)"
|
||||
>
|
||||
Insert
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
TreeNodeView,
|
||||
},
|
||||
props:{
|
||||
slotId: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data(){return {
|
||||
selectedNode: undefined,
|
||||
}},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'slotFillers'(){
|
||||
return [this.slotId]
|
||||
},
|
||||
},
|
||||
model(){
|
||||
return CreatureProperties.findOne(this.slotId);
|
||||
},
|
||||
libraryNodes(){
|
||||
let filter = {
|
||||
tags: {$all: this.model.slotTags},
|
||||
};
|
||||
if (this.model.slotType){
|
||||
filter.type = this.model.slotType;
|
||||
}
|
||||
console.log(filter);
|
||||
return LibraryNodes.find(filter);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
115
app/imports/ui/creature/slots/Slots.vue
Normal file
115
app/imports/ui/creature/slots/Slots.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template lang="html">
|
||||
<div class="slots">
|
||||
<div
|
||||
v-for="slot in slots"
|
||||
:key="slot._id"
|
||||
class="slot"
|
||||
>
|
||||
<h3 class="layout row align-center">
|
||||
{{ slot.name }}
|
||||
<v-spacer />
|
||||
<span v-if="slot.quantityExpected > 1">
|
||||
{{ slot.children.length }} / {{ slot.quantityExpected }}
|
||||
</span>
|
||||
</h3>
|
||||
<div
|
||||
v-for="child in slot.children"
|
||||
:key="child._id"
|
||||
class="layout row"
|
||||
:data-id="`slot-child-${child._id}`"
|
||||
>
|
||||
<tree-node-view
|
||||
class="slotChild"
|
||||
:model="child"
|
||||
/>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
icon
|
||||
flat
|
||||
small
|
||||
@click="remove(child._id)"
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
v-if="slot.quantityExpected > slot.children.length"
|
||||
icon
|
||||
:data-id="`slot-add-button-${slot._id}`"
|
||||
@click="fillSlot(slot._id)"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import { softRemoveProperty, insertPropertyFromLibraryNode } from '/imports/api/creature/CreatureProperties.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeView,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fillSlot(slotId){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'slot-fill-dialog',
|
||||
elementId: `slot-add-button-${slotId}`,
|
||||
data: {slotId},
|
||||
callback(node){
|
||||
if(!node) return;
|
||||
let newPropertyId = insertPropertyFromLibraryNode.call({
|
||||
nodeId: node._id,
|
||||
parentRef: {
|
||||
'id': slotId,
|
||||
'collection': 'creatureProperties',
|
||||
},
|
||||
});
|
||||
return `slot-child-${newPropertyId}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
remove(_id){
|
||||
softRemoveProperty.call({_id});
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
slots(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'propertySlot',
|
||||
$or: [
|
||||
{slotConditionResult: true},
|
||||
{slotConditionResult: {$exists: false}},
|
||||
]
|
||||
}, {
|
||||
sort: {
|
||||
order: 1,
|
||||
name: 1,
|
||||
},
|
||||
}).map(slot => {
|
||||
slot.children = CreatureProperties.find({
|
||||
'parent.id': slot._id,
|
||||
removed: {$ne: true},
|
||||
}).fetch();
|
||||
return slot;
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@ import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
||||
import MoveLibraryNodeDialog from '/imports/ui/library/MoveLibraryNodeDialog.vue'
|
||||
import SelectCreaturesDialog from '/imports/ui/tabletop/SelectCreaturesDialog.vue';
|
||||
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
||||
import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue';
|
||||
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
MoveLibraryNodeDialog,
|
||||
SelectCreaturesDialog,
|
||||
ShareDialog,
|
||||
SlotFillDialog,
|
||||
TierTooLowDialog,
|
||||
UsernameDialog,
|
||||
};
|
||||
|
||||
@@ -9,14 +9,31 @@
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<form-section
|
||||
name="Advanced"
|
||||
standalone
|
||||
>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import FormSection from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSection,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -53,15 +53,6 @@
|
||||
name="Advanced"
|
||||
standalone
|
||||
>
|
||||
<text-field
|
||||
label="Quantity filled"
|
||||
type="number"
|
||||
min="0"
|
||||
hint="How many properties have already been selected to fill this slot"
|
||||
:value="model.quantityFilled"
|
||||
:error-messages="errors.quantityFilled"
|
||||
@change="change('quantityFilled', ...arguments)"
|
||||
/>
|
||||
<div class="layout row wrap justify-space-between">
|
||||
<smart-switch
|
||||
label="Ignored"
|
||||
|
||||
Reference in New Issue
Block a user