New UX for inserting properties from libraries including text search and multi-add
This commit is contained in:
@@ -21,7 +21,11 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
||||
validate: new SimpleSchema({
|
||||
nodeId: {
|
||||
nodeIds: {
|
||||
type: Array,
|
||||
max: 20,
|
||||
},
|
||||
'nodeIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
@@ -38,7 +42,7 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({nodeId, parentRef, order}) {
|
||||
run({nodeIds, parentRef, order}) {
|
||||
// get the new ancestry for the properties
|
||||
let {parentDoc, ancestors} = getAncestry({parentRef});
|
||||
|
||||
@@ -53,54 +57,15 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Fetch the library node and its decendents, provided they have not been
|
||||
// removed
|
||||
// TODO: Check permission to read the library this node is in
|
||||
let node = LibraryNodes.findOne({
|
||||
_id: nodeId,
|
||||
removed: {$ne: true},
|
||||
});
|
||||
if (!node) throw `Node not found for nodeId: ${nodeId}`;
|
||||
let oldParent = node.parent;
|
||||
let nodes = LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
removed: {$ne: true},
|
||||
}).fetch();
|
||||
// {libraryId: hasViewPermission}
|
||||
//let libraryPermissionMemoir = {};
|
||||
let node;
|
||||
nodeIds.forEach(nodeId => {
|
||||
// TODO: Check library view permission for each node before starting
|
||||
node = insertPropertyFromNode(nodeId, ancestors, order);
|
||||
});
|
||||
|
||||
// Convert all references into actual nodes
|
||||
nodes = reifyNodeReferences(nodes);
|
||||
|
||||
// The root node is first in the array of nodes
|
||||
// It must get the first generated ID to prevent flickering
|
||||
nodes = [node, ...nodes];
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
newAncestry: ancestors,
|
||||
oldParent,
|
||||
});
|
||||
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({
|
||||
docArray: nodes,
|
||||
collectionMap: {'libraryNodes': 'creatureProperties'}
|
||||
});
|
||||
|
||||
// Order the root node
|
||||
if (order === undefined){
|
||||
setDocToLastOrder({
|
||||
collection: CreatureProperties,
|
||||
doc: node,
|
||||
});
|
||||
} else {
|
||||
node.order = order;
|
||||
}
|
||||
|
||||
// Insert the creature properties
|
||||
CreatureProperties.batchInsert(nodes);
|
||||
|
||||
// get the root inserted doc
|
||||
// get one of the root inserted docs
|
||||
let rootId = node._id;
|
||||
|
||||
// Tree structure changed by inserts, reorder the tree
|
||||
@@ -110,7 +75,7 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
});
|
||||
|
||||
// The library properties need to denormalise which of them are inactive
|
||||
recomputeInactiveProperties(rootId);
|
||||
recomputeInactiveProperties(rootCreature._id);
|
||||
// Some of the library properties may be items or containers
|
||||
recomputeInventory(rootCreature._id);
|
||||
// Inserting a creature property invalidates dependencies: full recompute
|
||||
@@ -120,6 +85,56 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
// Fetch the library node and its decendents, provided they have not been
|
||||
// removed
|
||||
// TODO: Check permission to read the library this node is in
|
||||
let node = LibraryNodes.findOne({
|
||||
_id: nodeId,
|
||||
removed: {$ne: true},
|
||||
});
|
||||
if (!node) throw `Node not found for nodeId: ${nodeId}`;
|
||||
let oldParent = node.parent;
|
||||
let nodes = LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
removed: {$ne: true},
|
||||
}).fetch();
|
||||
|
||||
// Convert all references into actual nodes
|
||||
nodes = reifyNodeReferences(nodes);
|
||||
|
||||
// The root node is first in the array of nodes
|
||||
// It must get the first generated ID to prevent flickering
|
||||
nodes = [node, ...nodes];
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
newAncestry: ancestors,
|
||||
oldParent,
|
||||
});
|
||||
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({
|
||||
docArray: nodes,
|
||||
collectionMap: {'libraryNodes': 'creatureProperties'}
|
||||
});
|
||||
|
||||
// Order the root node
|
||||
if (order === undefined){
|
||||
setDocToLastOrder({
|
||||
collection: CreatureProperties,
|
||||
doc: node,
|
||||
});
|
||||
} else {
|
||||
node.order = order;
|
||||
}
|
||||
|
||||
// Insert the creature properties
|
||||
CreatureProperties.batchInsert(nodes);
|
||||
return node;
|
||||
}
|
||||
|
||||
// Covert node references into actual nodes
|
||||
// TODO: check permissions for each library a reference node references
|
||||
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
@@ -194,7 +209,7 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
|
||||
// TODO: Force the referencedNode to take the old id of the reference
|
||||
// such that the reference's children can be kept
|
||||
|
||||
|
||||
// Give the new referenced sub-tree new ids
|
||||
renewDocIds({
|
||||
docArray: addedNodes,
|
||||
|
||||
@@ -57,7 +57,7 @@ const insertCreature = new ValidatedMethod({
|
||||
if (Meteor.isServer){
|
||||
// Insert the 5e ruleset as the default base
|
||||
insertPropertyFromLibraryNode.call({
|
||||
nodeId: 'iHbhfcg3AL5isSWbw',
|
||||
nodeIds: ['iHbhfcg3AL5isSWbw'],
|
||||
parentRef: {id: baseId, collection: 'creatureProperties'},
|
||||
order: 0.5,
|
||||
});
|
||||
|
||||
@@ -9,3 +9,4 @@ import '/imports/server/publications/tabletops.js';
|
||||
import '/imports/server/publications/slotFillers.js';
|
||||
import '/imports/server/publications/ownedDocuments.js';
|
||||
import '/imports/server/publications/archivedCreatures.js';
|
||||
import '/imports/server/publications/searchLibraryNodes.js';
|
||||
|
||||
@@ -2,13 +2,6 @@ import SimpleSchema from 'simpl-schema';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import { assertViewPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
const standardLibraryIds = [
|
||||
'SRDLibraryGA3XWsd',
|
||||
];
|
||||
|
||||
Meteor.publish('standardLibraries', function(){
|
||||
return Libraries.find({_id: {$in: standardLibraryIds}});
|
||||
});
|
||||
|
||||
Meteor.publish('libraries', function(){
|
||||
this.autorun(function (){
|
||||
@@ -75,3 +68,24 @@ Meteor.publish('libraryNodes', function(libraryId){
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('descendantLibraryNodes', function(nodeId){
|
||||
let node = LibraryNodes.findOne(nodeId);
|
||||
let libraryId = node?.ancestors[0]?.id;
|
||||
if (!libraryId) return [];
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
let library = Libraries.findOne(libraryId);
|
||||
try { assertViewPermission(library, userId) }
|
||||
catch(e){
|
||||
return this.error(e);
|
||||
}
|
||||
return [
|
||||
LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
}),
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
116
app/imports/server/publications/searchLibraryNodes.js
Normal file
116
app/imports/server/publications/searchLibraryNodes.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
|
||||
Meteor.publish('searchLibraryNodes', function(){
|
||||
let self = this;
|
||||
this.autorun(function (){
|
||||
let type = self.data('type');
|
||||
if (!type) return [];
|
||||
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all the ids of libraries the user can access
|
||||
const user = Meteor.users.findOne(userId, {
|
||||
fields: {subscribedLibraries: 1}
|
||||
});
|
||||
if (!user) return [];
|
||||
|
||||
const subs = user.subscribedLibraries || [];
|
||||
let libraries = Libraries.find({
|
||||
$or: [
|
||||
{owner: this.userId},
|
||||
{writers: this.userId},
|
||||
{readers: this.userId},
|
||||
{_id: {$in: subs}},
|
||||
]
|
||||
}, {
|
||||
fields: {_id: 1, name: 1},
|
||||
});
|
||||
let libraryIds = libraries.map(lib => lib._id);
|
||||
|
||||
// Build a filter for nodes in those libraries that match the type
|
||||
let filter = {
|
||||
'ancestors.id': {$in: libraryIds},
|
||||
removed: {$ne: true},
|
||||
tags: {$ne: []}, // Only tagged library nodes are considered
|
||||
};
|
||||
if (type){
|
||||
filter.$or = [{
|
||||
type,
|
||||
},{
|
||||
type: 'slotFiller',
|
||||
slotFillerType: type,
|
||||
}];
|
||||
}
|
||||
|
||||
this.autorun(function(){
|
||||
// Get the limit of the documents the user can fetch
|
||||
var limit = self.data('limit') || 32;
|
||||
check(limit, Number);
|
||||
|
||||
// Get the search term
|
||||
let searchTerm = self.data('searchTerm') || '';
|
||||
check(searchTerm, String);
|
||||
|
||||
let options = undefined;
|
||||
if (searchTerm){
|
||||
filter.$text = {$search: searchTerm};
|
||||
options = {
|
||||
// relevant documents have a higher score.
|
||||
fields: {
|
||||
score: { $meta: 'textScore' }
|
||||
},
|
||||
sort: {
|
||||
// `score` property specified in the projection fields above.
|
||||
score: { $meta: 'textScore' },
|
||||
'ancestors.0.id': 1,
|
||||
name: 1,
|
||||
order: 1,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete filter.$text
|
||||
options = {sort: {
|
||||
'ancestors.0.id': 1,
|
||||
name: 1,
|
||||
order: 1,
|
||||
}};
|
||||
}
|
||||
options.limit = limit;
|
||||
|
||||
this.autorun(function () {
|
||||
self.setData('countAll', LibraryNodes.find(filter).count());
|
||||
});
|
||||
|
||||
let cursor = LibraryNodes.find(filter, options);
|
||||
|
||||
Mongo.Collection._publishCursor(libraries, self, 'libraries');
|
||||
|
||||
let observeHandle = cursor.observeChanges({
|
||||
added: function (id, fields) {
|
||||
fields._searchResult = true;
|
||||
self.added('libraryNodes', id, fields);
|
||||
},
|
||||
changed: function (id, fields) {
|
||||
self.changed('libraryNodes', id, fields);
|
||||
},
|
||||
removed: function (id) {
|
||||
self.removed('libraryNodes', id);
|
||||
}
|
||||
},
|
||||
// Publications don't mutate the documents
|
||||
{ nonMutatingCallbacks: true }
|
||||
);
|
||||
|
||||
// register stop callback (expects lambda w/ no args).
|
||||
this.onStop(function () {
|
||||
observeHandle.stop();
|
||||
});
|
||||
// this.ready();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,22 +38,13 @@
|
||||
/>
|
||||
<template v-if="tabNumber === 5">
|
||||
<labeled-fab
|
||||
key="property"
|
||||
key="add-property"
|
||||
color="primary"
|
||||
data-id="insert-creature-property-btn"
|
||||
label="New Property"
|
||||
data-id="add-creature-property-btn"
|
||||
label="Add Property"
|
||||
icon="mdi-pencil"
|
||||
:disabled="!editPermission"
|
||||
@click="insertTreeProperty"
|
||||
/>
|
||||
<labeled-fab
|
||||
key="property"
|
||||
color="primary"
|
||||
data-id="insert-creature-property-from-library-btn"
|
||||
label="Property From Library"
|
||||
icon="mdi-library-shelves"
|
||||
:disabled="!editPermission"
|
||||
@click="propertyFromLibrary"
|
||||
@click="addProperty"
|
||||
/>
|
||||
</template>
|
||||
</v-speed-dial>
|
||||
@@ -234,11 +225,39 @@
|
||||
let nodeId = libraryNode._id;
|
||||
let {parentRef, order } = getParentAndOrderFromSelectedTreeNode(creatureId);
|
||||
|
||||
let id = insertPropertyFromLibraryNode.call({nodeId, parentRef, order});
|
||||
let id = insertPropertyFromLibraryNode.call({nodeIds: [nodeId], parentRef, order});
|
||||
return `tree-node-${id}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
addProperty(){
|
||||
let creatureId = this.creatureId;
|
||||
let fab = hideFab();
|
||||
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'add-creature-property-dialog',
|
||||
elementId: 'add-creature-property-btn',
|
||||
callback(result){
|
||||
revealFab(fab);
|
||||
if (!result){
|
||||
return 'insert-creature-property-fab';
|
||||
}
|
||||
let {parentRef, order } = getParentAndOrderFromSelectedTreeNode(creatureId);
|
||||
if (Array.isArray(result)){
|
||||
let nodeIds = result;
|
||||
let id = insertPropertyFromLibraryNode.call({nodeIds, parentRef, order});
|
||||
return `tree-node-${id}`;
|
||||
} else {
|
||||
let creatureProperty = result;
|
||||
// Get order and parent
|
||||
creatureProperty.order = order;
|
||||
// Insert the property
|
||||
let id = insertProperty.call({creatureProperty, parentRef});
|
||||
return `tree-node-${id}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<template lang="html">
|
||||
<selectable-property-dialog
|
||||
:value="type"
|
||||
no-library-only-props
|
||||
@input="e => type = e"
|
||||
>
|
||||
<dialog-base
|
||||
:override-back-button="back"
|
||||
>
|
||||
<template slot="toolbar">
|
||||
<v-toolbar-title class="mr-4">
|
||||
<template v-if="customProperty">
|
||||
New
|
||||
</template>{{ typeName }}
|
||||
</v-toolbar-title>
|
||||
<v-slide-x-transition>
|
||||
<text-field
|
||||
v-if="!customProperty"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
regular
|
||||
hide-details
|
||||
:value="searchValue"
|
||||
:debounce="400"
|
||||
@change="searchChanged"
|
||||
/>
|
||||
</v-slide-x-transition>
|
||||
<v-scale-transition>
|
||||
<v-btn
|
||||
v-if="!customProperty"
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
class="mr-2"
|
||||
color="accent"
|
||||
@click="customProperty = true"
|
||||
>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</v-scale-transition>
|
||||
</template>
|
||||
<v-slide-x-transition
|
||||
class="unwrapped-content"
|
||||
leave-absolute
|
||||
>
|
||||
<div
|
||||
v-if="customProperty"
|
||||
key="custom-property-form"
|
||||
>
|
||||
<component
|
||||
:is="type"
|
||||
v-if="type"
|
||||
class="creature-property-form"
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
@change="change"
|
||||
@push="push"
|
||||
@pull="pull"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
key="library-search"
|
||||
>
|
||||
<v-expansion-panels
|
||||
multiple
|
||||
inset
|
||||
>
|
||||
<v-expansion-panel
|
||||
v-for="libraryNode in libraryNodes"
|
||||
:key="libraryNode._id"
|
||||
:model="libraryNode"
|
||||
:data-id="libraryNode._id"
|
||||
>
|
||||
<v-expansion-panel-header>
|
||||
<template #default="{ open }">
|
||||
<v-checkbox
|
||||
v-model="selectedNodeIds"
|
||||
class="my-0 py-0 mr-2"
|
||||
hide-details
|
||||
:value="libraryNode._id"
|
||||
:disabled="!selectedNodeIds.includes(libraryNode._id) &&
|
||||
selectedNodeIds.length >= 20"
|
||||
@click.stop
|
||||
/>
|
||||
<v-layout column>
|
||||
<tree-node-view :model="libraryNode" />
|
||||
<div class="text-caption">
|
||||
{{ libraryNames[libraryNode.ancestors[0].id ] }}
|
||||
</div>
|
||||
</v-layout>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="open"
|
||||
icon
|
||||
class="flex-grow-0"
|
||||
@click.stop="openPropertyDetails(libraryNode._id)"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<library-node-expansion-content :model="libraryNode" />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<v-layout
|
||||
justify-center
|
||||
>
|
||||
<v-fade-transition mode="out-in">
|
||||
<div
|
||||
v-if="currentLimit < countAll"
|
||||
class="layout justify-center align-stretch"
|
||||
>
|
||||
<v-btn
|
||||
v-if="currentLimit < countAll"
|
||||
key="load-more-btn"
|
||||
:loading="!$subReady.searchLibraryNodes"
|
||||
color="accent"
|
||||
class="ma-4"
|
||||
@click="loadMore"
|
||||
>
|
||||
Load More
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
</v-layout>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
<template slot="actions">
|
||||
<v-btn
|
||||
text
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="customProperty"
|
||||
text
|
||||
color="primary"
|
||||
:disabled="!valid"
|
||||
@click="$store.dispatch('popDialogStack', model)"
|
||||
>
|
||||
create
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
text
|
||||
color="primary"
|
||||
:disabled="!selectedNodeIds.length"
|
||||
@click="$store.dispatch('popDialogStack', selectedNodeIds)"
|
||||
>
|
||||
<template v-if="selectedNodeIds.length >= 15">
|
||||
{{ selectedNodeIds.length }}/20
|
||||
</template>
|
||||
Insert
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
</selectable-property-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SelectablePropertyDialog from '/imports/ui/properties/shared/SelectablePropertyDialog.vue';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
||||
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
|
||||
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
|
||||
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
...propertyFormIndex,
|
||||
SelectablePropertyDialog,
|
||||
DialogBase,
|
||||
TreeNodeView,
|
||||
LibraryNodeExpansionContent,
|
||||
},
|
||||
mixins: [schemaFormMixin],
|
||||
props: {
|
||||
forcedType: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
suggestedType: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
parentDoc: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['debounceTime'],
|
||||
},
|
||||
data(){return {
|
||||
selectedNodeIds: [],
|
||||
type: this.forcedType || this.suggestedType,
|
||||
model: {
|
||||
type: this.type,
|
||||
},
|
||||
searchValue: undefined,
|
||||
customProperty: false,
|
||||
debounceTime: 0,
|
||||
};},
|
||||
computed: {
|
||||
typeName(){
|
||||
return getPropertyName(this.type) || 'Property';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
type(newType){
|
||||
this.changeType(newType);
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
this.changeType(this.type);
|
||||
},
|
||||
methods: {
|
||||
back(){
|
||||
if (this.customProperty){
|
||||
this.customProperty = false;
|
||||
} else if (this.forcedType){
|
||||
this.$store.dispatch('popDialogStack');
|
||||
} else {
|
||||
this.type = undefined;
|
||||
}
|
||||
},
|
||||
searchChanged(val, ack){
|
||||
this._subs.searchLibraryNodes.setData('searchTerm', val);
|
||||
this._subs.searchLibraryNodes.setData('limit', undefined);
|
||||
this.selectedNode = undefined;
|
||||
this.searchValue = val;
|
||||
setTimeout(ack, 200);
|
||||
},
|
||||
loadMore(){
|
||||
if (this.currentLimit >= this.countAll) return;
|
||||
this._subs.searchLibraryNodes.setData('limit', this.currentLimit + 32);
|
||||
},
|
||||
insert(){
|
||||
if (!this.selectedNodeIds.length) return;
|
||||
this.$store.dispatch('popDialogStack', this.selectedNodeIds);
|
||||
},
|
||||
changeType(type){
|
||||
this._subs.searchLibraryNodes.setData('type', type);
|
||||
if (!type) return;
|
||||
this.schema = propertySchemasIndex[type];
|
||||
this.validationContext = this.schema.newContext();
|
||||
let model = this.schema.clean({});
|
||||
model.type = type;
|
||||
this.model = model;
|
||||
},
|
||||
openPropertyDetails(id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-node-dialog',
|
||||
elementId: id,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
'$subscribe':{
|
||||
'searchLibraryNodes': [],
|
||||
},
|
||||
currentLimit(){
|
||||
return this._subs.searchLibraryNodes.data('limit') || 32;
|
||||
},
|
||||
countAll(){
|
||||
return this._subs.searchLibraryNodes.data('countAll');
|
||||
},
|
||||
libraryNodes(){
|
||||
return LibraryNodes.find({
|
||||
_searchResult: true
|
||||
},{
|
||||
sort: {
|
||||
'ancestors.0.id': 1,
|
||||
name: 1,
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
},
|
||||
libraryNames(){
|
||||
let names = {};
|
||||
Libraries.find().forEach(lib => names[lib._id] = lib.name)
|
||||
return names;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -46,44 +46,44 @@ import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
...propertyFormIndex,
|
||||
DialogBase,
|
||||
components: {
|
||||
...propertyFormIndex,
|
||||
DialogBase,
|
||||
ColorPicker,
|
||||
},
|
||||
mixins: [schemaFormMixin],
|
||||
props: {
|
||||
propertyName: String,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
mixins: [schemaFormMixin],
|
||||
props: {
|
||||
propertyName: String,
|
||||
type: String,
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['debounceTime'],
|
||||
},
|
||||
data(){return {
|
||||
model: {
|
||||
type: this.type,
|
||||
},
|
||||
schema: undefined,
|
||||
validationContext: undefined,
|
||||
data(){return {
|
||||
model: {
|
||||
type: this.type,
|
||||
},
|
||||
schema: undefined,
|
||||
validationContext: undefined,
|
||||
debounceTime: 0,
|
||||
};},
|
||||
watch: {
|
||||
type(newType){
|
||||
};},
|
||||
watch: {
|
||||
type(newType){
|
||||
this.changeType(newType);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
this.changeType(this.type);
|
||||
},
|
||||
methods:{
|
||||
changeType(type){
|
||||
if (!type) return;
|
||||
this.schema = propertySchemasIndex[type];
|
||||
this.validationContext = this.schema.newContext();
|
||||
let model = this.schema.clean({});
|
||||
model.type = type;
|
||||
this.model = model;
|
||||
this.schema = propertySchemasIndex[type];
|
||||
this.validationContext = this.schema.newContext();
|
||||
let model = this.schema.clean({});
|
||||
model.type = type;
|
||||
this.model = model;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<text-field
|
||||
prepend-inner-icon="mdi-search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
regular
|
||||
hide-details
|
||||
:value="searchValue"
|
||||
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
callback(node){
|
||||
if(!node) return;
|
||||
let newPropertyId = insertPropertyFromLibraryNode.call({
|
||||
nodeId: node._id,
|
||||
nodeIds: [node._id],
|
||||
parentRef: {
|
||||
'id': slotId,
|
||||
'collection': 'creatureProperties',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AddCreaturePropertyDialog from '/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue';
|
||||
import ArchiveDialog from '/imports/ui/creature/archive/ArchiveDialog.vue';
|
||||
import CastSpellWithSlotDialog from '/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue';
|
||||
import CreatureFormDialog from '/imports/ui/creature/CreatureFormDialog.vue';
|
||||
@@ -23,6 +24,7 @@ import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||
|
||||
export default {
|
||||
AddCreaturePropertyDialog,
|
||||
ArchiveDialog,
|
||||
CastSpellWithSlotDialog,
|
||||
CreatureFormDialog,
|
||||
|
||||
@@ -22,17 +22,17 @@ const dialogStackStore = {
|
||||
});
|
||||
updateHistory();
|
||||
},
|
||||
replaceDialog(state, {component, data, elementId, callback}){
|
||||
const _id = Random.id();
|
||||
replaceDialog(state, {component, data}){
|
||||
if (!state.dialogs.length){
|
||||
throw new Meteor.Error('can\'t replace dialog if no dialogs are open');
|
||||
}
|
||||
let currentDialog = state.dialogs[state.dialogs.length - 1]
|
||||
Vue.set(state.dialogs, state.dialogs.length - 1, {
|
||||
_id,
|
||||
_id: currentDialog._id,
|
||||
component,
|
||||
data,
|
||||
elementId,
|
||||
callback,
|
||||
elementId: currentDialog.elementId,
|
||||
callback: currentDialog.callback,
|
||||
});
|
||||
},
|
||||
popDialogStackMutation (state, result){
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<script lang="js">
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js'
|
||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js';
|
||||
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||
import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
|
||||
|
||||
61
app/imports/ui/library/LibraryNodeExpansionContent.vue
Normal file
61
app/imports/ui/library/LibraryNodeExpansionContent.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<component
|
||||
:is="model.type"
|
||||
:model="model"
|
||||
class="property-viewer"
|
||||
/>
|
||||
<tree-node-list
|
||||
group="library-node-expansion"
|
||||
:children="propertyChildren"
|
||||
@selected="clickChild"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js'
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
|
||||
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
...propertyViewerIndex,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickChild(id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-node-dialog',
|
||||
elementId: `tree-node-${id}`,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
descendantLibraryNodes(){
|
||||
return [this.model._id];
|
||||
},
|
||||
},
|
||||
propertyChildren(){
|
||||
return nodesToTree({
|
||||
collection: LibraryNodes,
|
||||
ancestorId: this.model._id
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user