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

View File

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

View File

@@ -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',

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 { 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;

View File

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

View File

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

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 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: {

View File

@@ -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,

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

View File

@@ -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)"
/>
</div>
</v-expand-transition>

View File

@@ -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)"
/>
</draggable>
@@ -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});
}
}
},
},
};

View File

@@ -5,7 +5,8 @@
:children="libraryChildren"
:group="library && library._id"
:organize="organize"
@moved="moved"
@reordered="reordered"
@reorganized="reorganized"
/>
</v-card-text>
</template>
@@ -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,
});
},
},
};

View File

@@ -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){

View File

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

View File

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