Drastically improved tree tab search UX for locating parts of the sheet
This commit is contained in:
115
app/imports/api/parenting/nodesToTree.js
Normal file
115
app/imports/api/parenting/nodesToTree.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { union, difference, sortBy, findLast } from 'lodash';
|
||||
|
||||
export function nodeArrayToTree(nodes){
|
||||
// Store a dict and list of all the nodes
|
||||
let nodeIndex = {};
|
||||
let nodeList = [];
|
||||
nodes.forEach( node => {
|
||||
let treeNode = {
|
||||
node: node,
|
||||
children: [],
|
||||
};
|
||||
nodeIndex[node._id] = treeNode;
|
||||
nodeList.push(treeNode);
|
||||
});
|
||||
// Create a forest of trees
|
||||
let forest = [];
|
||||
// Either the node is a child of its nearest found ancestor, or in the forest as a root
|
||||
nodeList.forEach(treeNode => {
|
||||
let ancestorInForest = findLast(
|
||||
treeNode.node.ancestors,
|
||||
ancestor => !!nodeIndex[ancestor.id]
|
||||
);
|
||||
if (ancestorInForest){
|
||||
nodeIndex[ancestorInForest.id].children.push(treeNode);
|
||||
} else {
|
||||
forest.push(treeNode);
|
||||
}
|
||||
});
|
||||
return forest;
|
||||
}
|
||||
|
||||
// Fetch the documents from a collection, and return the tree of those documents
|
||||
export default function nodesToTree({
|
||||
collection, ancestorId, filter, options = {},
|
||||
includeFilteredDocAncestors = false, includeFilteredDocDescendants = false
|
||||
}){
|
||||
// Setup the filter
|
||||
let collectionFilter = {
|
||||
'ancestors.id': ancestorId,
|
||||
'removed': {$ne: true},
|
||||
};
|
||||
if (filter){
|
||||
collectionFilter = {
|
||||
...collectionFilter,
|
||||
...filter,
|
||||
}
|
||||
}
|
||||
// Set up the options
|
||||
let collectionSort = {
|
||||
order: 1
|
||||
};
|
||||
if (options && options.sort){
|
||||
collectionSort = {
|
||||
...collectionSort,
|
||||
...options.sort,
|
||||
}
|
||||
}
|
||||
let collectionOptions = {
|
||||
sort: collectionSort,
|
||||
}
|
||||
if (options){
|
||||
collectionOptions = {
|
||||
...collectionOptions,
|
||||
...options,
|
||||
}
|
||||
}
|
||||
// Find all the nodes that match the filter
|
||||
let docs = collection.find(collectionFilter, collectionOptions).map(doc => {
|
||||
if (!filter) return doc;
|
||||
// Mark the nodes that were found by the custom filter
|
||||
doc._matchedDocumentFilter = true;
|
||||
return doc;
|
||||
});
|
||||
let ancestors = [];
|
||||
let ancestorIds = [];
|
||||
let docIds = [];
|
||||
if (filter && (includeFilteredDocAncestors || includeFilteredDocDescendants)){
|
||||
docIds = docs.map(doc => doc._id)
|
||||
}
|
||||
if (filter && includeFilteredDocAncestors){
|
||||
// Add all ancestor ids to an array
|
||||
docs.forEach(doc => {
|
||||
ancestorIds = union(ancestorIds, doc.ancestors.map(ref => ref.id));
|
||||
});
|
||||
// Remove the IDs of docs we have already found
|
||||
ancestorIds = difference(ancestorIds, docIds);
|
||||
// Get the docs from the collection, don't worry about `removed` docs,
|
||||
// if their descendant was not removed, neither are they
|
||||
ancestors = collection.find({_id: {$in: ancestorIds}}).map(doc => {
|
||||
// Mark that the nodes are ancestors of the found nodes
|
||||
doc._ancestorOfMatchedDocument = true;
|
||||
return doc;
|
||||
});
|
||||
}
|
||||
let descendants = [];
|
||||
if (filter && includeFilteredDocDescendants){
|
||||
let exludeIds = union(ancestorIds, docIds);
|
||||
descendants = collection.find({
|
||||
'_id': {$nin: exludeIds},
|
||||
'ancestors.id': {$in: docIds},
|
||||
'removed': {$ne: true},
|
||||
}).map(doc => {
|
||||
// Mark that the nodes are descendants of the found nodes
|
||||
doc._descendantOfMatchedDocument = true;
|
||||
return doc;
|
||||
});
|
||||
}
|
||||
let nodes = sortBy([
|
||||
...ancestors,
|
||||
...docs,
|
||||
...descendants
|
||||
], 'order');
|
||||
// Find all the nodes
|
||||
return nodeArrayToTree(nodes);
|
||||
}
|
||||
Reference in New Issue
Block a user