//
// 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
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);
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,
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> => {
+ if (childId === "") {
+ return tree;
+ }
const node = getNode(parentId)(tree);
if (node) {
const children = node.children.some(id => id === childId)