X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/90df34ba0e84b5735c48382362284b5f0382dd0e..ceca6c57cc7d0357ab74abd47b80633a885ec575:/src/models/tree.ts diff --git a/src/models/tree.ts b/src/models/tree.ts index a5fb49cf..f0b53b46 100644 --- a/src/models/tree.ts +++ b/src/models/tree.ts @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 +import { pipe } from 'lodash/fp'; export type Tree = Record>; export const TREE_ROOT_ID = ''; @@ -11,6 +12,16 @@ export interface TreeNode { value: T; id: string; parent: string; + active: boolean; + selected: boolean; + expanded: boolean; + status: TreeNodeStatus; +} + +export enum TreeNodeStatus { + INITIAL = 'INITIAL', + PENDING = 'PENDING', + LOADED = 'LOADED', } export const createTree = (): Tree => ({}); @@ -18,12 +29,12 @@ export const createTree = (): Tree => ({}); export const getNode = (id: string) => (tree: Tree): TreeNode | undefined => tree[id]; export const setNode = (node: TreeNode) => (tree: Tree): Tree => { - const [newTree] = [tree] - .map(tree => getNode(node.id)(tree) === node + return pipe( + (tree: Tree) => getNode(node.id)(tree) === node ? tree - : { ...tree, [node.id]: node }) - .map(addChild(node.parent, node.id)); - return newTree; + : { ...tree, [node.id]: node }, + addChild(node.parent, node.id) + )(tree); }; export const getNodeValue = (id: string) => (tree: Tree) => { @@ -95,6 +106,104 @@ export const getNodeChildrenIds = (id: string) => (tree: Tree): string[] = export const mapIdsToNodes = (ids: string[]) => (tree: Tree) => ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode => node !== undefined); +export const activateNode = (id: string) => (tree: Tree) => + mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree); + +export const deactivateNode = (tree: Tree) => + mapTree(node => node.active ? { ...node, active: false } : node)(tree); + +export const expandNode = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree); + +export const collapseNode = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree); + +export const toggleNodeCollapse = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree); + +export const setNodeStatus = (id: string) => (status: TreeNodeStatus) => (tree: Tree) => { + const node = getNode(id)(tree); + return node + ? setNode({ ...node, status })(tree) + : tree; +}; + +export const toggleNodeSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + return node + ? pipe( + setNode({ ...node, selected: !node.selected }), + toggleAncestorsSelection(id), + toggleDescendantsSelection(id))(tree) + : tree; + +}; + +export const selectNode = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + return node && node.selected + ? tree + : toggleNodeSelection(id)(tree); +}; + +export const selectNodes = (id: string | string[]) => (tree: Tree) => { + const ids = typeof id === 'string' ? [id] : id; + return ids.reduce((tree, id) => selectNode(id)(tree), tree); +}; +export const deselectNode = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + return node && node.selected + ? toggleNodeSelection(id)(tree) + : tree; +}; + +export const deselectNodes = (id: string | string[]) => (tree: Tree) => { + const ids = typeof id === 'string' ? [id] : id; + return ids.reduce((tree, id) => deselectNode(id)(tree), tree); +}; + +export const initTreeNode = (data: Pick, 'id' | 'value'> & { parent?: string }): TreeNode => ({ + children: [], + active: false, + selected: false, + expanded: false, + status: TreeNodeStatus.INITIAL, + parent: '', + ...data, +}); + +const toggleDescendantsSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + if (node) { + return getNodeDescendants(id)(tree) + .reduce((newTree, subNode) => + setNode({ ...subNode, selected: node.selected })(newTree), + tree); + } + return tree; +}; + +const toggleAncestorsSelection = (id: string) => (tree: Tree) => { + const ancestors = getNodeAncestorsIds(id)(tree).reverse(); + return ancestors.reduce((newTree, parent) => parent ? toggleParentNodeSelection(parent)(newTree) : newTree, tree); +}; + +const toggleParentNodeSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + if (node) { + const parentNode = getNode(node.id)(tree); + if (parentNode) { + const selected = parentNode.children + .map(id => getNode(id)(tree)) + .every(node => node !== undefined && node.selected); + return setNode({ ...parentNode, selected })(tree); + } + return setNode(node)(tree); + } + return tree; +}; + + const mapNodeValue = (mapFn: (value: T) => R) => (node: TreeNode): TreeNode => ({ ...node, value: mapFn(node.value) });