Merge branch '17256-file-selection-dialog-issue'
[arvados-workbench2.git] / src / views-components / tree-picker / tree-picker.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { connect } from "react-redux";
6 import { Tree, TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree";
7 import { RootState } from "~/store/store";
8 import { getNodeChildrenIds, Tree as Ttree, createTree, getNode, TreeNodeStatus } from '~/models/tree';
9 import { Dispatch } from "redux";
10 import { initTreeNode } from '../../models/tree';
11
12 type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
13 export interface TreePickerProps<T> {
14     pickerId: string;
15     onContextMenu: Callback<T>;
16     toggleItemOpen: Callback<T>;
17     toggleItemActive: Callback<T>;
18     toggleItemSelection: Callback<T>;
19 }
20
21 const flatTree = (itemsIdMap: Map<string, any>, depth: number, items?: any): [] => {
22     return items ? items
23         .map((item: any) => addToItemsIdMap(item, itemsIdMap))
24         .reduce((prev: Array<any>, next: any) => {
25             const { items } = next;
26             prev.push({ ...next, depth });
27             prev.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : []));
28             return prev;
29         }, []) : [];
30 };
31
32 const addToItemsIdMap = <T>(item: TreeItem<T>, itemsIdMap: Map<string, TreeItem<T>>) => {
33     itemsIdMap[item.id] = item;
34     return item;
35 };
36
37 const memoizedMapStateToProps = () => {
38     let prevTree: Ttree<any>;
39     let mappedProps: Pick<TreeProps<any>, 'items' | 'disableRipple' | 'itemsMap'>;
40     return <T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items' | 'disableRipple' | 'itemsMap'> => {
41         const itemsIdMap: Map<string, TreeItem<T>> = new Map();
42         const tree = state.treePicker[props.pickerId] || createTree();
43         if (tree !== prevTree) {
44             prevTree = tree;
45             mappedProps = {
46                 disableRipple: true,
47                 items: getNodeChildrenIds('')(tree)
48                     .map(treePickerToTreeItems(tree))
49                     .map(item => addToItemsIdMap(item, itemsIdMap))
50                     .map(parentItem => ({
51                         ...parentItem,
52                         flatTree: true,
53                         items: flatTree(itemsIdMap, 2, parentItem.items || []),
54                     })),
55                 itemsMap: itemsIdMap,
56             };
57         }
58         return mappedProps;
59     };
60 };
61
62 const mapDispatchToProps = (_: Dispatch, props: TreePickerProps<any>): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
63     onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
64     toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
65     toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
66     toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
67 });
68
69 export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
70
71 const treePickerToTreeItems = (tree: Ttree<any>) =>
72     (id: string): TreeItem<any> => {
73         const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
74         const items = getNodeChildrenIds(node.id)(tree)
75             .map(treePickerToTreeItems(tree));
76         return {
77             active: node.active,
78             data: node.value,
79             id: node.id,
80             items: items.length > 0 ? items : undefined,
81             open: node.expanded,
82             selected: node.selected,
83             status: treeNodeStatusToTreeItem(node.status),
84         };
85     };
86
87 export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => {
88     switch (status) {
89         case TreeNodeStatus.INITIAL:
90             return TreeItemStatus.INITIAL;
91         case TreeNodeStatus.PENDING:
92             return TreeItemStatus.PENDING;
93         case TreeNodeStatus.LOADED:
94             return TreeItemStatus.LOADED;
95     }
96 };
97