Merge branch '21128-toolbar-context-menu'
[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 import { ResourcesState } from "store/resources/resources";
12
13 type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
14 export interface TreePickerProps<T> {
15     pickerId: string;
16     onContextMenu: Callback<T>;
17     toggleItemOpen: Callback<T>;
18     toggleItemActive: Callback<T>;
19     toggleItemSelection: Callback<T>;
20 }
21
22 const flatTree = (itemsIdMap: Map<string, any>, depth: number, items?: any): [] => {
23     return items ? items
24         .map((item: any) => addToItemsIdMap(item, itemsIdMap))
25         .reduce((prev: Array<any>, next: any) => {
26             const { items } = next;
27             prev.push({ ...next, depth });
28             prev.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : []));
29             return prev;
30         }, []) : [];
31 };
32
33 const addToItemsIdMap = <T>(item: TreeItem<T>, itemsIdMap: Map<string, TreeItem<T>>) => {
34     itemsIdMap[item.id] = item;
35     return item;
36 };
37
38 const mapStateToProps =
39     <T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items' | 'disableRipple' | 'itemsMap'> => {
40         const itemsIdMap: Map<string, TreeItem<T>> = new Map();
41         const tree = state.treePicker[props.pickerId] || createTree();
42         return {
43             disableRipple: true,
44             items: getNodeChildrenIds('')(tree)
45                 .map(treePickerToTreeItems(tree, state.resources))
46                 .map(item => addToItemsIdMap(item, itemsIdMap))
47                 .map(parentItem => ({
48                     ...parentItem,
49                     flatTree: true,
50                     items: flatTree(itemsIdMap, 2, parentItem.items || []),
51                 })),
52             itemsMap: itemsIdMap,
53         };
54     };
55
56 const mapDispatchToProps = (_: Dispatch, props: TreePickerProps<any>): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
57     onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
58     toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
59     toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
60     toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
61 });
62
63 export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(Tree);
64
65 const treePickerToTreeItems = (tree: Ttree<any>, resources: ResourcesState) =>
66     (id: string): TreeItem<any> => {
67         const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
68         const items = getNodeChildrenIds(node.id)(tree)
69             .map(treePickerToTreeItems(tree, resources));
70         const resource = resources[node.id];
71         return {
72             active: node.active,
73             data: resource ? { ...resource, name: node.value.name || node.value } : undefined || node.value,
74             id: node.id,
75             items: items.length > 0 ? items : undefined,
76             open: node.expanded,
77             selected: node.selected,
78             status: treeNodeStatusToTreeItem(node.status),
79         };
80     };
81
82 export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => {
83     switch (status) {
84         case TreeNodeStatus.INITIAL:
85             return TreeItemStatus.INITIAL;
86         case TreeNodeStatus.PENDING:
87             return TreeItemStatus.PENDING;
88         case TreeNodeStatus.LOADED:
89             return TreeItemStatus.LOADED;
90     }
91 };
92