From d0304da4fdb057dd953ab4c61f2a528bbf5b4bc6 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 30 Jul 2019 16:47:21 +0200 Subject: [PATCH] Began making generic tree re-arranging methods, still buggy --- .../api/creature/CreatureProperties.js | 4 +- .../mixins/propagateInheritanceUpdateMixin.js | 16 +++---- app/imports/api/library/LibraryNodes.js | 6 +-- app/imports/api/library/librarySchemas.js | 4 +- app/imports/api/parenting/ChildSchema.js | 1 + app/imports/api/parenting/order.js | 21 +++++---- app/imports/api/parenting/organizeDoc.js | 6 --- app/imports/api/parenting/organizeMethods.js | 47 +++++++++++++++++++ app/imports/api/parenting/parenting.js | 21 ++++----- app/imports/api/parenting/softRemove.js | 8 ++-- app/imports/api/properties/Actions.js | 2 +- app/imports/api/sharing/sharingPermissions.js | 34 ++++++++++++++ app/imports/ui/components/tree/TreeNode.vue | 3 +- .../ui/components/tree/TreeNodeList.vue | 18 +++++-- .../ui/library/LibraryContentsContainer.vue | 36 ++++++++++++-- .../ui/library/LibraryNodeInsertForm.vue | 4 +- app/imports/ui/pages/Library.vue | 4 +- app/server/main.js | 1 + 18 files changed, 176 insertions(+), 60 deletions(-) delete mode 100644 app/imports/api/parenting/organizeDoc.js create mode 100644 app/imports/api/parenting/organizeMethods.js diff --git a/app/imports/api/creature/CreatureProperties.js b/app/imports/api/creature/CreatureProperties.js index 2952a413..5e74aeec 100644 --- a/app/imports/api/creature/CreatureProperties.js +++ b/app/imports/api/creature/CreatureProperties.js @@ -8,7 +8,7 @@ import getModifierFields from '/imports/api/getModifierFields.js'; let CreatureProperties = new Mongo.Collection('creatureProperties'); let CreaturePropertySchema = new SimpleSchema({ - creaturePropertyType: { + type: { type: String, allowedValues: Object.keys(propertySchemas), }, @@ -26,7 +26,7 @@ for (let key in propertySchemas){ schema.extend(CreaturePropertySchema); schema.extend(ChildSchema); CreatureProperties.attachSchema(schema, { - selector: {creaturePropertyType: key} + selector: {type: key} }); } diff --git a/app/imports/api/creature/mixins/propagateInheritanceUpdateMixin.js b/app/imports/api/creature/mixins/propagateInheritanceUpdateMixin.js index fb0ef8c4..0ed6ee9f 100644 --- a/app/imports/api/creature/mixins/propagateInheritanceUpdateMixin.js +++ b/app/imports/api/creature/mixins/propagateInheritanceUpdateMixin.js @@ -1,6 +1,6 @@ import { updateChildren, - updateDecendents, + updateDescendants, } from '/imports/api/parenting/parenting.js'; import { inheritedFields } from '/imports/api/parenting/ChildSchema.js'; import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js'; @@ -11,7 +11,7 @@ import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js'; // It should have neglible performance impact for updates that aren't inherited function propagateInheritanceUpdate({_id, update}){ let childModifier = {}; - let decendentModifier = {}; + let descendantModifier = {}; // For each operator for (let operator of MONGO_OPERATORS){ // If the operator is in the update, for each field @@ -26,11 +26,11 @@ function propagateInheritanceUpdate({_id, update}){ } // If that field is updated and inherited if (inheritedFields.has(modifiedField)){ - // Perform the same update on the decendents + // Perform the same update on the descendants if (!childModifier[operator]) childModifier[operator] = {}; - if (!decendentModifier[operator]) decendentModifier[operator] = {}; + if (!descendantModifier[operator]) descendantModifier[operator] = {}; childModifier[operator][`parent.${field}`] = update[operator][field]; - decendentModifier[operator][`ancestors.$.${field}`] = update[operator][field]; + descendantModifier[operator][`ancestors.$.${field}`] = update[operator][field]; } } } @@ -41,10 +41,10 @@ function propagateInheritanceUpdate({_id, update}){ modifier: childModifier, }); - // Update the ancestors object of its decendents - updateDecendents({ + // Update the ancestors object of its descendants + updateDescendants({ ancestorId: _id, - modifier: decendentModifier, + modifier: descendantModifier, }); } diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index 2a7eb426..e5bc5606 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -8,7 +8,7 @@ import getModifierFields from '/imports/api/getModifierFields.js'; let LibraryNodes = new Mongo.Collection('libraryNodes'); let LibraryNodeSchema = new SimpleSchema({ - libraryNodeType: { + type: { type: String, allowedValues: Object.keys(librarySchemas), }, @@ -20,7 +20,7 @@ for (let key in librarySchemas){ schema.extend(LibraryNodeSchema); schema.extend(ChildSchema); LibraryNodes.attachSchema(schema, { - selector: {libraryNodeType: key} + selector: {type: key} }); } @@ -50,7 +50,7 @@ const updateNode = new ValidatedMethod({ validate({_id, update}){ let fields = getModifierFields(update); return !fields.hasAny([ - 'libraryNodeType', + 'type', 'order', 'parent', 'ancestors', diff --git a/app/imports/api/library/librarySchemas.js b/app/imports/api/library/librarySchemas.js index 24190c3a..86b289e4 100644 --- a/app/imports/api/library/librarySchemas.js +++ b/app/imports/api/library/librarySchemas.js @@ -1,4 +1,4 @@ -import { CreatureSchema } from '/imports/api/creature/Creatures.js'; +import SimpleSchema from 'simpl-schema'; import { ActionSchema } from '/imports/api/properties/Actions.js'; import { AttributeSchema } from '/imports/api/properties/Attributes.js'; import { StoredBuffSchema } from '/imports/api/properties/Buffs.js'; @@ -20,7 +20,6 @@ import { ItemSchema } from '/imports/api/properties/Items.js'; const librarySchemas = { - creature: CreatureSchema, action: ActionSchema, attribute: AttributeSchema, buff: StoredBuffSchema, @@ -39,6 +38,7 @@ const librarySchemas = { spell: SpellSchema, container: ContainerSchema, item: ItemSchema, + any: new SimpleSchema({}), }; export default librarySchemas; diff --git a/app/imports/api/parenting/ChildSchema.js b/app/imports/api/parenting/ChildSchema.js index 9d922c7e..b4cfbc3e 100644 --- a/app/imports/api/parenting/ChildSchema.js +++ b/app/imports/api/parenting/ChildSchema.js @@ -30,3 +30,4 @@ let ChildSchema = new SimpleSchema({ }); export default ChildSchema; +export { RefSchema }; diff --git a/app/imports/api/parenting/order.js b/app/imports/api/parenting/order.js index 3442ea04..6bb58e43 100644 --- a/app/imports/api/parenting/order.js +++ b/app/imports/api/parenting/order.js @@ -72,30 +72,31 @@ export function updateOrder({docRef, order}){ return; } else { // Move the document to its new order - docRef.collection.update(doc._id, {$set: {order}}); + collection.update(doc._id, {$set: {order}}, {selector: {type: 'any'}}); let inBetweenSelector, increment; if (order > currentOrder){ // Move in-between docs backward - inBetweenSelector = [ - {$gt: currentOrder}, - {$lte: order}, - ]; + inBetweenSelector = { + $gt: currentOrder, + $lte: order + }; increment = -1; } else if (order < currentOrder){ // Move in-between docs forward - inBetweenSelector = [ - {$lt: currentOrder}, - {$gte: order}, - ]; + inBetweenSelector = { + $lt: currentOrder, + $gte: order + }; increment = 1; } collection.update({ 'parent.id': doc.parent.id, - order: {$and: inBetweenSelector}, + order: inBetweenSelector, }, { $inc: {order: increment}, }, { multi: true, + selector: {type: 'any'}, }); } } diff --git a/app/imports/api/parenting/organizeDoc.js b/app/imports/api/parenting/organizeDoc.js deleted file mode 100644 index 427c9627..00000000 --- a/app/imports/api/parenting/organizeDoc.js +++ /dev/null @@ -1,6 +0,0 @@ -import { updateParent } from '/imports/api/parenting/parenting.js'; - -export default function organizeDoc({docRef, parentRef, order}){ - updateParent({docRef, parentRef}); - updateOrder({docRef, order}) -}; diff --git a/app/imports/api/parenting/organizeMethods.js b/app/imports/api/parenting/organizeMethods.js new file mode 100644 index 00000000..7bc233c7 --- /dev/null +++ b/app/imports/api/parenting/organizeMethods.js @@ -0,0 +1,47 @@ +import SimpleSchema from 'simpl-schema'; +import { updateParent } from '/imports/api/parenting/parenting.js'; +import { updateOrder } from '/imports/api/parenting/order.js'; +import { RefSchema } from '/imports/api/parenting/ChildSchema.js'; +import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js'; +import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; + +const organizeDoc = new ValidatedMethod({ + name: 'organize.methods.organizeDoc', + validate: new SimpleSchema({ + docRef: RefSchema, + parentRef: RefSchema, + order: { + type: Number, + min: 0, + }, + }).validator(), + run({docRef, parentRef, order}) { + let doc = fetchDocByRef(docRef); + + // The user must be able to edit both the doc and its parent to move it + // successfully + assertDocEditPermission(doc, this.userId); + let parent = fetchDocByRef(parentRef); + assertDocEditPermission(parent, this.userId); + updateParent({docRef, parentRef}); + updateOrder({docRef, order}) + }, +}); + +const reorderDoc = new ValidatedMethod({ + name: 'organize.methods.reorderDoc', + validate: new SimpleSchema({ + docRef: RefSchema, + order: { + type: Number, + min: 0, + }, + }).validator(), + run({docRef, order}) { + let doc = fetchDocByRef(docRef); + assertDocEditPermission(doc, this.userId); + updateOrder({docRef, order}) + }, +}); + +export { organizeDoc, reorderDoc }; diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index 24e422b3..4f17df34 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -1,6 +1,5 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; -import SimpleSchema from 'simpl-schema'; export function fetchParent({id, collection}){ return fetchDocByRef({id, collection}); @@ -23,20 +22,20 @@ export function updateChildren({collection, parentId, filter = {}, modifier, opt collection.update(filter, modifier, options); } -export function fetchDecendents({ collection, ancestorId, filter = {}, options}){ +export function fetchDescendants({ collection, ancestorId, filter = {}, options}){ filter["ancestors.id"] = ancestorId; - let decendents = []; - decendents.push(...collection.find(filter, options).fetch()); - return decendents; + let descendants = []; + descendants.push(...collection.find(filter, options).fetch()); + return descendants; } -export function updateDecendents({collection, ancestorId, filter = {}, modifier, options={}}){ +export function updateDescendants({collection, ancestorId, filter = {}, modifier, options={}}){ filter["ancestors.id"] = ancestorId; options.multi = true; collection.update(filter, modifier, options); } -export function forEachDecendent({collection, ancestorId, filter = {}, options}, callback){ +export function forEachDescendant({collection, ancestorId, filter = {}, options}, callback){ filter["ancestors.id"] = ancestorId; collection.find(filter, options).forEach(callback); } @@ -75,16 +74,16 @@ export function updateParent({docRef, parentRef}){ let {parent, ancestors} = getAncestry({parentRef}); collection.update(docRef.id, {$set: {parent, ancestors}}); - // Remove the old ancestors from the decendents - updateDecendents({ + // Remove the old ancestors from the descendants + updateDescendants({ ancestorId: docRef.id, modifier: {$pullAll: { ancestors: oldDoc.ancestors, }}, }); - // Add the new ancestors to the decendents - updateDecendents({ + // Add the new ancestors to the descendants + updateDescendants({ ancestorId: docRef.id, modifier: {$push: { ancestors: { diff --git a/app/imports/api/parenting/softRemove.js b/app/imports/api/parenting/softRemove.js index 71eb3b18..e6642317 100644 --- a/app/imports/api/parenting/softRemove.js +++ b/app/imports/api/parenting/softRemove.js @@ -1,5 +1,5 @@ import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; -import updateDecendents from '/imports/api/parenting/parenting.js'; +import updateDescendants from '/imports/api/parenting/parenting.js'; // 1 + n database hits export function softRemove({_id, collection}){ @@ -12,9 +12,9 @@ export function softRemove({_id, collection}){ }, $unset: { removedWith: 1, }}); - // Remove all the decendents that have not yet been removed, and set them to be + // Remove all the descendants that have not yet been removed, and set them to be // removed with this document - updateDecendents({ + updateDescendants({ ancestorId: _id, filter: {removed: {$ne: true}}, modifier: {$set: { @@ -41,7 +41,7 @@ export function restore({_id, collection}){ removedAt: 1, }}); if (numUpdated === 0) restoreError(); - updateDecendents({ + updateDescendants({ ancestorId: _id, filter: { removedWith: _id, diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index b4589131..b4e21efe 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -80,4 +80,4 @@ let ActionSchema = new SimpleSchema({ }, }); -export default { ActionSchema }; +export { ActionSchema }; diff --git a/app/imports/api/sharing/sharingPermissions.js b/app/imports/api/sharing/sharingPermissions.js index 645bfb23..aa5560ea 100644 --- a/app/imports/api/sharing/sharingPermissions.js +++ b/app/imports/api/sharing/sharingPermissions.js @@ -1,4 +1,5 @@ import { _ } from 'meteor/underscore'; +import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; function assertIdValid(userId){ if (!userId || typeof userId !== 'string'){ @@ -25,6 +26,12 @@ export function assertOwnership(doc, userId){ } } +/** + * Assert that the user can edit the root document which manages its own sharing + * permissions. + * + * Warning: the doc and userId must be set by a trusted source + */ export function assertEditPermission(doc, userId) { assertIdValid(userId); assertdocExists(doc); @@ -36,6 +43,22 @@ export function assertEditPermission(doc, userId) { } } +function getRoot(doc){ + assertdocExists(doc); + return fetchDocByRef(doc.ancestors && doc.ancestors.length && doc.ancestors[0] || doc); +} + +/** + * Assert that the user can edit a descendant document whose root ancestor + * implements sharing permissions. + * + * Warning: the doc and userId must be set by a trusted source + */ +export function assertDocEditPermission(doc, userId){ + let root = getRoot(doc); + assertEditPermission(root, userId); +} + export function assertViewPermission(doc, userId) { assertIdValid(userId); assertdocExists(doc); @@ -51,3 +74,14 @@ export function assertViewPermission(doc, userId) { `You do not have permission to view this character`); } } + +/** + * Assert that the user can view a descendant document whose root ancestor + * implements sharing permissions. + * + * Warning: the doc and userId must be set by a trusted source + */ +export function assertDocViewPermission(doc, userId){ + let root = getRoot(doc); + assertViewPermission(root, userId); +} diff --git a/app/imports/ui/components/tree/TreeNode.vue b/app/imports/ui/components/tree/TreeNode.vue index 8551484d..531f88fc 100644 --- a/app/imports/ui/components/tree/TreeNode.vue +++ b/app/imports/ui/components/tree/TreeNode.vue @@ -22,7 +22,8 @@ :children="computedChildren" :group="group" :show-empty="organize" - @moved="e => $emit('moved', e)" + @reordered="e => $emit('reordered', e)" + @reorganized="e => $emit('reorganized', e)" /> diff --git a/app/imports/ui/components/tree/TreeNodeList.vue b/app/imports/ui/components/tree/TreeNodeList.vue index 2031cbae..3fdc12cf 100644 --- a/app/imports/ui/components/tree/TreeNodeList.vue +++ b/app/imports/ui/components/tree/TreeNodeList.vue @@ -18,7 +18,8 @@ :organize="organize" :lazy="lazy" class="item" - @moved="e => $emit('moved', e)" + @reordered="e => $emit('reordered', e)" + @reorganized="e => $emit('reorganized', e)" @dragstart.native="e => e.dataTransfer.setData('cow', child.node && child.node.name)" /> @@ -54,10 +55,17 @@ }, }, methods: { - change({added, removed, moved}){ - if (removed) return; - let newIndex = (added || moved).newIndex; - this.$emit('moved', {parent: this.node, newIndex}); + change({added, moved}){ + let event = moved || added; + if (event){ + let newIndex = event.newIndex; + let doc = event.element.node; + if (moved){ + this.$emit('reordered', {doc, newIndex}); + } else if (added){ + this.$emit('reorganized', {doc, parent: this.node, newIndex}); + } + } }, }, }; diff --git a/app/imports/ui/library/LibraryContentsContainer.vue b/app/imports/ui/library/LibraryContentsContainer.vue index 47d5a06a..59d46c9a 100644 --- a/app/imports/ui/library/LibraryContentsContainer.vue +++ b/app/imports/ui/library/LibraryContentsContainer.vue @@ -5,7 +5,8 @@ :children="libraryChildren" :group="library && library._id" :organize="organize" - @moved="moved" + @reordered="reordered" + @reorganized="reorganized" /> @@ -14,6 +15,7 @@ import Libraries from '/imports/api/library/Libraries.js'; import LibraryNodes, { libraryNodesToTree } from '/imports/api/library/LibraryNodes.js'; import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue'; + import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js'; export default { components: { @@ -36,8 +38,36 @@ }, }, methods: { - moved(e){ - console.log(e) + reordered({doc, newIndex}){ + reorderDoc.call({ + docRef: { + id: doc._id, + collection: 'libraryNodes', + }, + order: newIndex, + }); + }, + reorganized({doc, parent, newIndex}){ + let parentRef; + if (parent){ + parentRef = { + id: this.libraryId, + collection: 'libraries', + }; + } else { + parentRef = { + id: parent._id, + collection: 'libraryNodes', + }; + } + organizeDoc.call({ + docRef: { + id: doc._id, + collection: 'libraryNodes', + }, + parentRef, + order: newIndex, + }); }, }, }; diff --git a/app/imports/ui/library/LibraryNodeInsertForm.vue b/app/imports/ui/library/LibraryNodeInsertForm.vue index 5a176e78..487fbdfb 100644 --- a/app/imports/ui/library/LibraryNodeInsertForm.vue +++ b/app/imports/ui/library/LibraryNodeInsertForm.vue @@ -38,7 +38,7 @@ export default { mixins: [schemaFormMixin], data(){return { model: { - libraryNodeType: this.type, + type: this.type, }, schema: undefined, validationContext: undefined, @@ -52,7 +52,7 @@ export default { this.schema = librarySchemas[newType]; this.validationContext = this.schema.newContext(); let model = this.schema.clean({}); - model.libraryNodeType = newType; + model.type = newType; this.model = model; }, model(newModel){ diff --git a/app/imports/ui/pages/Library.vue b/app/imports/ui/pages/Library.vue index dc9e9caa..86c907d7 100644 --- a/app/imports/ui/pages/Library.vue +++ b/app/imports/ui/pages/Library.vue @@ -44,8 +44,8 @@ elementId: 'insert-library-node-fab', callback(libraryNode){ if (!libraryNode) return; - libraryNode.parent = {collection: "library", id: that.library._id}; - libraryNode.ancestors = [ {collection: "library", id: that.library._id}]; + libraryNode.parent = {collection: "libraries", id: that.library._id}; + libraryNode.ancestors = [ {collection: "libraries", id: that.library._id}]; setDocToLastOrder({collection: LibraryNodes, doc: libraryNode}); console.log(libraryNode); let libraryNodeId = insertNode.call(libraryNode); diff --git a/app/server/main.js b/app/server/main.js index 89d78f45..d5714fe9 100644 --- a/app/server/main.js +++ b/app/server/main.js @@ -3,3 +3,4 @@ import "/imports/api/creature/creatureComputation.js"; import "/imports/api/parenting/deleteRemovedDocuments.js"; import "/imports/server/config/accountsMeldConfig.js"; import "/imports/server/config/simpleSchemaDebug.js"; +import "/imports/api/parenting/organizeMethods.js";