Files
DiceCloud/app/imports/api/parenting/order.js

199 lines
5.3 KiB
JavaScript

import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
// Docs keep track of their order amongst their siblings and keep a copy of the
// order of their ancestors. Order is first compared between oldest non-shared
// ancestors, then by ancestors before children, then between order of siblings.
export function compareOrder(docA, docB){
// < 0 if A comes before B
// = 0 if A and B are the same order
// > 0 if B comes before A
// Documents are equal order to themselves
if (docA._id && docB._id && docA._id === docB._id){
return 0;
}
// If they are siblings, just compare order
if (docA.parent.id === docB.parent.id){
return docA.order - docB.order;
}
// They must share a root ancestor to be meaningfully sorted
if (docA.ancestors[0].id !== docB.ancestors[0].id){
return 0;
}
// Go through their ancestors after the root, and find the first order
// difference
// TODO ancestors don't store order yet
let i, difference;
const length = Math.min(docA.ancestors.length, docB.ancestors.length);
for (i = 1; i < length; i++){
difference = docA.ancestors[i].order - docB.ancestors[i].order;
if (difference){
return difference;
} else if (docA.ancestors[i].id !== docB.ancestors[i].id) {
throw new Meteor.Error('Sibling order clash',
'Sibling docs share the same order, sort failed');
}
}
// We haven't returned yet, all ancestors up to this point are shared and one
// doc has no more ancestors implying one is an ancestor of the other,
// return the difference in their ancestor list lengths, shorter comes first
return docA.ancestors.length - docB.ancestors.length
}
export function getHighestOrder({collection, parentId}){
const highestOrderedDoc = collection.findOne({
'parent.id': parentId,
}, {
fields: {order: 1},
sort: {order: -1},
});
return highestOrderedDoc ? highestOrderedDoc.order : -1;
}
export function setDocToLastOrder({collection, doc}){
doc.order = getHighestOrder({
collection,
parentId: doc.parent.id,
}) + 1;
}
// update the order of a doc, and shift the siblings around to suit the new
// order
export function updateDocOrder({docRef, order}){
let doc = fetchDocByRef(docRef, {fields: {
order: 1,
parent: 1,
}});
let collection = getCollectionByName(docRef.collection);
const currentOrder = doc.order;
if (currentOrder === order){
return;
} else {
// First move the documents that are in the way
let inBetweenSelector, increment;
if (order > currentOrder){
// Move in-between docs backward
inBetweenSelector = {
$gt: currentOrder,
$lte: order
};
increment = -1;
} else if (order < currentOrder){
// Move in-between docs forward
inBetweenSelector = {
$lt: currentOrder,
$gte: order
};
increment = 1;
}
collection.update({
'parent.id': doc.parent.id,
order: inBetweenSelector,
}, {
$inc: {order: increment},
}, {
multi: true,
selector: {type: 'any'},
});
// Then move the document itself
collection.update(doc._id, {$set: {order}}, {selector: {type: 'any'}});
}
}
export function removedDocAtOrder({collection, doc}){
// Decrement the order of all docs after the removed doc
collection.update({
'parent.id': doc.parent.id,
order: {$gt: doc.order},
}, {
$inc: {order: -1},
}, {
multi: true,
selector: {type: 'any'},
});
}
export function insertedDocAtOrder({collection, parentId, order}){
// Increment the order of all docs after the inserted doc
collection.update({
'parent.id': parentId,
order: {$gte: order},
}, {
$inc: {order: 1},
}, {
multi: true,
selector: {type: 'any'},
});
}
// Update the order a single doc and re-order the entire sibling list
// with the change
export function safeUpdateDocOrder({docRef, order}){
let collection = getCollectionByName(docRef.collection);
let movedDoc = fetchDocByRef(docRef, {fields: {
parent: 1, name: 1
}});
let parentId = movedDoc.parent.id;
let bulkWrite = [];
let docs = collection.find({
'parent.id': parentId,
'_id': {$ne: movedDoc._id},
}, {
fields: {order: 1, name: 1},
sort: {order: 1}
}).fetch();
docs.splice(order, 0, movedDoc);
docs.forEach((doc, index) => {
if (doc.order !== index){
bulkWrite.push({
updateOne: {
filter: {_id: doc._id},
update: {$set: {order: index}},
},
});
}
});
if (Meteor.isServer){
collection.rawCollection().bulkWrite(bulkWrite);
} else {
bulkWrite.forEach(op => {
collection.update(
op.updateOne.filter,
op.updateOne.update,
{selector: {type: 'any'}}
);
});
}
};
export function reorderDocs({collection, parentId}){
let bulkWrite = [];
collection.find({
'parent.id': parentId,
}, {
fields: {order: 1},
sort: {order: 1}
}).forEach((doc, index) => {
if (doc.order !== index){
bulkWrite.push({
updateOne : {
filter: {_id: doc._id},
update: {$set: {order: index}},
},
});
}
});
if (Meteor.isServer){
collection.rawCollection().bulkWrite(bulkWrite);
} else {
bulkWrite.forEach(op => {
collection.update(op.updateOne.filter, op.updateOne.update);
});
}
}