//
// SPDX-License-Identifier: AGPL-3.0
-import { pipe } from 'lodash/fp';
+import { pipe, map, reduce } from 'lodash/fp';
export type Tree<T> = Record<string, TreeNode<T>>;
export const TREE_ROOT_ID = '';
LOADED = 'LOADED',
}
+export enum TreePickerId {
+ PROJECTS = 'Projects',
+ SHARED_WITH_ME = 'Shared with me',
+ FAVORITES = 'Favorites',
+ PUBLIC_FAVORITES = 'Public Favorites'
+}
+
export const createTree = <T>(): Tree<T> => ({});
export const getNode = (id: string) => <T>(tree: Tree<T>): TreeNode<T> | undefined => tree[id];
+export const appendSubtree = <T>(id: string, subtree: Tree<T>) => (tree: Tree<T>) =>
+ pipe(
+ getNodeDescendants(''),
+ map(node => node.parent === '' ? { ...node, parent: id } : node),
+ reduce((newTree, node) => setNode(node)(newTree), tree)
+ )(subtree) as Tree<T>;
+
export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
- return pipe(
- (tree: Tree<T>) => getNode(node.id)(tree) === node
- ? tree
- : { ...tree, [node.id]: node },
- addChild(node.parent, node.id)
- )(tree);
+ if (tree[node.id] && tree[node.id] === node) { return tree; }
+
+ tree[node.id] = node;
+ if (tree[node.parent]) {
+ tree[node.parent].children = Array.from(new Set([...tree[node.parent].children, node.id]));
+ }
+ return tree;
};
export const getNodeValue = (id: string) => <T>(tree: Tree<T>) => {
export const mapTreeValues = <T, R>(mapFn: (value: T) => R) => (tree: Tree<T>): Tree<R> =>
getNodeDescendantsIds('')(tree)
.map(id => getNode(id)(tree))
+ .filter(node => !!node)
.map(mapNodeValue(mapFn))
.reduce((newTree, node) => setNode(node)(newTree), createTree<R>());
export const getNodeDescendants = (id: string, limit = Infinity) => <T>(tree: Tree<T>) =>
mapIdsToNodes(getNodeDescendantsIds(id, limit)(tree))(tree);
+export const countNodes = <T>(tree: Tree<T>) =>
+ getNodeDescendantsIds('')(tree).length;
+
+export const countChildren = (id: string) => <T>(tree: Tree<T>) =>
+ getNodeChildren('')(tree).length;
+
export const getNodeDescendantsIds = (id: string, limit = Infinity) => <T>(tree: Tree<T>): string[] => {
const node = getNode(id)(tree);
const children = node ? node.children :
id === TREE_ROOT_ID
- ? getRootNodeChildren(tree)
+ ? getRootNodeChildrenIds(tree)
: [];
return children
ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode<T> => node !== undefined);
export const activateNode = (id: string) => <T>(tree: Tree<T>) =>
- mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree);
+ mapTree((node: TreeNode<T>) => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree);
export const deactivateNode = <T>(tree: Tree<T>) =>
- mapTree(node => node.active ? { ...node, active: false } : node)(tree);
+ mapTree((node: TreeNode<T>) => node.active ? { ...node, active: false } : node)(tree);
export const expandNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
- mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree);
+ mapTree((node: TreeNode<T>) => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree);
export const collapseNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
- mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree);
+ mapTree((node: TreeNode<T>) => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree);
export const toggleNodeCollapse = (...ids: string[]) => <T>(tree: Tree<T>) =>
- mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree);
+ mapTree((node: TreeNode<T>) => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree);
export const setNodeStatus = (id: string) => (status: TreeNodeStatus) => <T>(tree: Tree<T>) => {
const node = getNode(id)(tree);
toggleAncestorsSelection(id),
toggleDescendantsSelection(id))(tree)
: tree;
+};
+export const selectNode = (id: string) => <T>(tree: Tree<T>) => {
+ const node = getNode(id)(tree);
+ return node && node.selected
+ ? tree
+ : toggleNodeSelection(id)(tree);
};
-export const initTreeNode = <T>(data: Pick<TreeNode<T>, 'id' | 'value'> & {parent?: string}): TreeNode<T> => ({
+export const selectNodes = (id: string | string[]) => <T>(tree: Tree<T>) => {
+ const ids = typeof id === 'string' ? [id] : id;
+ return ids.reduce((tree, id) => selectNode(id)(tree), tree);
+};
+export const deselectNode = (id: string) => <T>(tree: Tree<T>) => {
+ const node = getNode(id)(tree);
+ return node && node.selected
+ ? toggleNodeSelection(id)(tree)
+ : tree;
+};
+
+export const deselectNodes = (id: string | string[]) => <T>(tree: Tree<T>) => {
+ const ids = typeof id === 'string' ? [id] : id;
+ return ids.reduce((tree, id) => deselectNode(id)(tree), tree);
+};
+
+export const getSelectedNodes = <T>(tree: Tree<T>) =>
+ getNodeDescendants('')(tree)
+ .filter(node => node.selected);
+
+export const initTreeNode = <T>(data: Pick<TreeNode<T>, 'id' | 'value'> & { parent?: string }): TreeNode<T> => ({
children: [],
active: false,
selected: false,
const mapNodeValue = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
({ ...node, value: mapFn(node.value) });
-const getRootNodeChildren = <T>(tree: Tree<T>) =>
+const getRootNodeChildrenIds = <T>(tree: Tree<T>) =>
Object
.keys(tree)
.filter(id => getNode(id)(tree)!.parent === TREE_ROOT_ID);
-
-const addChild = (parentId: string, childId: string) => <T>(tree: Tree<T>): Tree<T> => {
- const node = getNode(parentId)(tree);
- if (node) {
- const children = node.children.some(id => id === childId)
- ? node.children
- : [...node.children, childId];
-
- const newNode = children === node.children
- ? node
- : { ...node, children };
-
- return setNode(newNode)(tree);
- }
- return tree;
-};