Began making generic tree re-arranging methods, still buggy

This commit is contained in:
Stefan Zermatten
2019-07-30 16:47:21 +02:00
parent 4b7ff2146f
commit d0304da4fd
18 changed files with 176 additions and 60 deletions

View File

@@ -8,7 +8,7 @@ import getModifierFields from '/imports/api/getModifierFields.js';
let CreatureProperties = new Mongo.Collection('creatureProperties'); let CreatureProperties = new Mongo.Collection('creatureProperties');
let CreaturePropertySchema = new SimpleSchema({ let CreaturePropertySchema = new SimpleSchema({
creaturePropertyType: { type: {
type: String, type: String,
allowedValues: Object.keys(propertySchemas), allowedValues: Object.keys(propertySchemas),
}, },
@@ -26,7 +26,7 @@ for (let key in propertySchemas){
schema.extend(CreaturePropertySchema); schema.extend(CreaturePropertySchema);
schema.extend(ChildSchema); schema.extend(ChildSchema);
CreatureProperties.attachSchema(schema, { CreatureProperties.attachSchema(schema, {
selector: {creaturePropertyType: key} selector: {type: key}
}); });
} }

View File

@@ -1,6 +1,6 @@
import { import {
updateChildren, updateChildren,
updateDecendents, updateDescendants,
} from '/imports/api/parenting/parenting.js'; } from '/imports/api/parenting/parenting.js';
import { inheritedFields } from '/imports/api/parenting/ChildSchema.js'; import { inheritedFields } from '/imports/api/parenting/ChildSchema.js';
import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.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 // It should have neglible performance impact for updates that aren't inherited
function propagateInheritanceUpdate({_id, update}){ function propagateInheritanceUpdate({_id, update}){
let childModifier = {}; let childModifier = {};
let decendentModifier = {}; let descendantModifier = {};
// For each operator // For each operator
for (let operator of MONGO_OPERATORS){ for (let operator of MONGO_OPERATORS){
// If the operator is in the update, for each field // 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 that field is updated and inherited
if (inheritedFields.has(modifiedField)){ if (inheritedFields.has(modifiedField)){
// Perform the same update on the decendents // Perform the same update on the descendants
if (!childModifier[operator]) childModifier[operator] = {}; if (!childModifier[operator]) childModifier[operator] = {};
if (!decendentModifier[operator]) decendentModifier[operator] = {}; if (!descendantModifier[operator]) descendantModifier[operator] = {};
childModifier[operator][`parent.${field}`] = update[operator][field]; 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, modifier: childModifier,
}); });
// Update the ancestors object of its decendents // Update the ancestors object of its descendants
updateDecendents({ updateDescendants({
ancestorId: _id, ancestorId: _id,
modifier: decendentModifier, modifier: descendantModifier,
}); });
} }

View File

@@ -8,7 +8,7 @@ import getModifierFields from '/imports/api/getModifierFields.js';
let LibraryNodes = new Mongo.Collection('libraryNodes'); let LibraryNodes = new Mongo.Collection('libraryNodes');
let LibraryNodeSchema = new SimpleSchema({ let LibraryNodeSchema = new SimpleSchema({
libraryNodeType: { type: {
type: String, type: String,
allowedValues: Object.keys(librarySchemas), allowedValues: Object.keys(librarySchemas),
}, },
@@ -20,7 +20,7 @@ for (let key in librarySchemas){
schema.extend(LibraryNodeSchema); schema.extend(LibraryNodeSchema);
schema.extend(ChildSchema); schema.extend(ChildSchema);
LibraryNodes.attachSchema(schema, { LibraryNodes.attachSchema(schema, {
selector: {libraryNodeType: key} selector: {type: key}
}); });
} }
@@ -50,7 +50,7 @@ const updateNode = new ValidatedMethod({
validate({_id, update}){ validate({_id, update}){
let fields = getModifierFields(update); let fields = getModifierFields(update);
return !fields.hasAny([ return !fields.hasAny([
'libraryNodeType', 'type',
'order', 'order',
'parent', 'parent',
'ancestors', 'ancestors',

View File

@@ -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 { ActionSchema } from '/imports/api/properties/Actions.js';
import { AttributeSchema } from '/imports/api/properties/Attributes.js'; import { AttributeSchema } from '/imports/api/properties/Attributes.js';
import { StoredBuffSchema } from '/imports/api/properties/Buffs.js'; import { StoredBuffSchema } from '/imports/api/properties/Buffs.js';
@@ -20,7 +20,6 @@ import { ItemSchema } from '/imports/api/properties/Items.js';
const librarySchemas = { const librarySchemas = {
creature: CreatureSchema,
action: ActionSchema, action: ActionSchema,
attribute: AttributeSchema, attribute: AttributeSchema,
buff: StoredBuffSchema, buff: StoredBuffSchema,
@@ -39,6 +38,7 @@ const librarySchemas = {
spell: SpellSchema, spell: SpellSchema,
container: ContainerSchema, container: ContainerSchema,
item: ItemSchema, item: ItemSchema,
any: new SimpleSchema({}),
}; };
export default librarySchemas; export default librarySchemas;

View File

@@ -30,3 +30,4 @@ let ChildSchema = new SimpleSchema({
}); });
export default ChildSchema; export default ChildSchema;
export { RefSchema };

View File

@@ -72,30 +72,31 @@ export function updateOrder({docRef, order}){
return; return;
} else { } else {
// Move the document to its new order // 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; let inBetweenSelector, increment;
if (order > currentOrder){ if (order > currentOrder){
// Move in-between docs backward // Move in-between docs backward
inBetweenSelector = [ inBetweenSelector = {
{$gt: currentOrder}, $gt: currentOrder,
{$lte: order}, $lte: order
]; };
increment = -1; increment = -1;
} else if (order < currentOrder){ } else if (order < currentOrder){
// Move in-between docs forward // Move in-between docs forward
inBetweenSelector = [ inBetweenSelector = {
{$lt: currentOrder}, $lt: currentOrder,
{$gte: order}, $gte: order
]; };
increment = 1; increment = 1;
} }
collection.update({ collection.update({
'parent.id': doc.parent.id, 'parent.id': doc.parent.id,
order: {$and: inBetweenSelector}, order: inBetweenSelector,
}, { }, {
$inc: {order: increment}, $inc: {order: increment},
}, { }, {
multi: true, multi: true,
selector: {type: 'any'},
}); });
} }
} }

View File

@@ -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})
};

View File

@@ -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 };

View File

@@ -1,6 +1,5 @@
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
import SimpleSchema from 'simpl-schema';
export function fetchParent({id, collection}){ export function fetchParent({id, collection}){
return fetchDocByRef({id, collection}); return fetchDocByRef({id, collection});
@@ -23,20 +22,20 @@ export function updateChildren({collection, parentId, filter = {}, modifier, opt
collection.update(filter, modifier, options); collection.update(filter, modifier, options);
} }
export function fetchDecendents({ collection, ancestorId, filter = {}, options}){ export function fetchDescendants({ collection, ancestorId, filter = {}, options}){
filter["ancestors.id"] = ancestorId; filter["ancestors.id"] = ancestorId;
let decendents = []; let descendants = [];
decendents.push(...collection.find(filter, options).fetch()); descendants.push(...collection.find(filter, options).fetch());
return decendents; return descendants;
} }
export function updateDecendents({collection, ancestorId, filter = {}, modifier, options={}}){ export function updateDescendants({collection, ancestorId, filter = {}, modifier, options={}}){
filter["ancestors.id"] = ancestorId; filter["ancestors.id"] = ancestorId;
options.multi = true; options.multi = true;
collection.update(filter, modifier, options); 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; filter["ancestors.id"] = ancestorId;
collection.find(filter, options).forEach(callback); collection.find(filter, options).forEach(callback);
} }
@@ -75,16 +74,16 @@ export function updateParent({docRef, parentRef}){
let {parent, ancestors} = getAncestry({parentRef}); let {parent, ancestors} = getAncestry({parentRef});
collection.update(docRef.id, {$set: {parent, ancestors}}); collection.update(docRef.id, {$set: {parent, ancestors}});
// Remove the old ancestors from the decendents // Remove the old ancestors from the descendants
updateDecendents({ updateDescendants({
ancestorId: docRef.id, ancestorId: docRef.id,
modifier: {$pullAll: { modifier: {$pullAll: {
ancestors: oldDoc.ancestors, ancestors: oldDoc.ancestors,
}}, }},
}); });
// Add the new ancestors to the decendents // Add the new ancestors to the descendants
updateDecendents({ updateDescendants({
ancestorId: docRef.id, ancestorId: docRef.id,
modifier: {$push: { modifier: {$push: {
ancestors: { ancestors: {

View File

@@ -1,5 +1,5 @@
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; 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 // 1 + n database hits
export function softRemove({_id, collection}){ export function softRemove({_id, collection}){
@@ -12,9 +12,9 @@ export function softRemove({_id, collection}){
}, $unset: { }, $unset: {
removedWith: 1, 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 // removed with this document
updateDecendents({ updateDescendants({
ancestorId: _id, ancestorId: _id,
filter: {removed: {$ne: true}}, filter: {removed: {$ne: true}},
modifier: {$set: { modifier: {$set: {
@@ -41,7 +41,7 @@ export function restore({_id, collection}){
removedAt: 1, removedAt: 1,
}}); }});
if (numUpdated === 0) restoreError(); if (numUpdated === 0) restoreError();
updateDecendents({ updateDescendants({
ancestorId: _id, ancestorId: _id,
filter: { filter: {
removedWith: _id, removedWith: _id,

View File

@@ -80,4 +80,4 @@ let ActionSchema = new SimpleSchema({
}, },
}); });
export default { ActionSchema }; export { ActionSchema };

View File

@@ -1,4 +1,5 @@
import { _ } from 'meteor/underscore'; import { _ } from 'meteor/underscore';
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
function assertIdValid(userId){ function assertIdValid(userId){
if (!userId || typeof userId !== 'string'){ 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) { export function assertEditPermission(doc, userId) {
assertIdValid(userId); assertIdValid(userId);
assertdocExists(doc); 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) { export function assertViewPermission(doc, userId) {
assertIdValid(userId); assertIdValid(userId);
assertdocExists(doc); assertdocExists(doc);
@@ -51,3 +74,14 @@ export function assertViewPermission(doc, userId) {
`You do not have permission to view this character`); `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);
}

View File

@@ -22,7 +22,8 @@
:children="computedChildren" :children="computedChildren"
:group="group" :group="group"
:show-empty="organize" :show-empty="organize"
@moved="e => $emit('moved', e)" @reordered="e => $emit('reordered', e)"
@reorganized="e => $emit('reorganized', e)"
/> />
</div> </div>
</v-expand-transition> </v-expand-transition>

View File

@@ -18,7 +18,8 @@
:organize="organize" :organize="organize"
:lazy="lazy" :lazy="lazy"
class="item" 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)" @dragstart.native="e => e.dataTransfer.setData('cow', child.node && child.node.name)"
/> />
</draggable> </draggable>
@@ -54,10 +55,17 @@
}, },
}, },
methods: { methods: {
change({added, removed, moved}){ change({added, moved}){
if (removed) return; let event = moved || added;
let newIndex = (added || moved).newIndex; if (event){
this.$emit('moved', {parent: this.node, newIndex}); 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});
}
}
}, },
}, },
}; };

View File

@@ -5,7 +5,8 @@
:children="libraryChildren" :children="libraryChildren"
:group="library && library._id" :group="library && library._id"
:organize="organize" :organize="organize"
@moved="moved" @reordered="reordered"
@reorganized="reorganized"
/> />
</v-card-text> </v-card-text>
</template> </template>
@@ -14,6 +15,7 @@
import Libraries from '/imports/api/library/Libraries.js'; import Libraries from '/imports/api/library/Libraries.js';
import LibraryNodes, { libraryNodesToTree } from '/imports/api/library/LibraryNodes.js'; import LibraryNodes, { libraryNodesToTree } from '/imports/api/library/LibraryNodes.js';
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue'; import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js';
export default { export default {
components: { components: {
@@ -36,8 +38,36 @@
}, },
}, },
methods: { methods: {
moved(e){ reordered({doc, newIndex}){
console.log(e) 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,
});
}, },
}, },
}; };

View File

@@ -38,7 +38,7 @@ export default {
mixins: [schemaFormMixin], mixins: [schemaFormMixin],
data(){return { data(){return {
model: { model: {
libraryNodeType: this.type, type: this.type,
}, },
schema: undefined, schema: undefined,
validationContext: undefined, validationContext: undefined,
@@ -52,7 +52,7 @@ export default {
this.schema = librarySchemas[newType]; this.schema = librarySchemas[newType];
this.validationContext = this.schema.newContext(); this.validationContext = this.schema.newContext();
let model = this.schema.clean({}); let model = this.schema.clean({});
model.libraryNodeType = newType; model.type = newType;
this.model = model; this.model = model;
}, },
model(newModel){ model(newModel){

View File

@@ -44,8 +44,8 @@
elementId: 'insert-library-node-fab', elementId: 'insert-library-node-fab',
callback(libraryNode){ callback(libraryNode){
if (!libraryNode) return; if (!libraryNode) return;
libraryNode.parent = {collection: "library", id: that.library._id}; libraryNode.parent = {collection: "libraries", id: that.library._id};
libraryNode.ancestors = [ {collection: "library", id: that.library._id}]; libraryNode.ancestors = [ {collection: "libraries", id: that.library._id}];
setDocToLastOrder({collection: LibraryNodes, doc: libraryNode}); setDocToLastOrder({collection: LibraryNodes, doc: libraryNode});
console.log(libraryNode); console.log(libraryNode);
let libraryNodeId = insertNode.call(libraryNode); let libraryNodeId = insertNode.call(libraryNode);

View File

@@ -3,3 +3,4 @@ import "/imports/api/creature/creatureComputation.js";
import "/imports/api/parenting/deleteRemovedDocuments.js"; import "/imports/api/parenting/deleteRemovedDocuments.js";
import "/imports/server/config/accountsMeldConfig.js"; import "/imports/server/config/accountsMeldConfig.js";
import "/imports/server/config/simpleSchemaDebug.js"; import "/imports/server/config/simpleSchemaDebug.js";
import "/imports/api/parenting/organizeMethods.js";