1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { connect } from "react-redux";
6 import { Tree as TreeComponent, TreeProps, TreeItem, TreeItemStatus } from "components/tree/tree";
7 import { RootState } from "store/store";
8 import { getNodeChildrenIds, Tree, 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 import { Resource } from "models/resource";
14 type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
15 export interface TreePickerProps<T> {
17 onContextMenu: Callback<T>;
18 toggleItemOpen: Callback<T>;
19 toggleItemActive: Callback<T>;
20 toggleItemSelection: Callback<T>;
23 const flatTree = <T>(itemsIdMap: Map<string, TreeItem<T>>, depth: number, items?: TreeItem<T>[]): TreeItem<T>[] => {
25 .map((item: TreeItem<T>) => addToItemsIdMap(item, itemsIdMap))
26 .reduce((acc: Array<TreeItem<T>>, next: TreeItem<T>) => {
27 const { items } = next;
28 acc.push({ ...next, depth });
29 acc.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : []));
31 }, [] as TreeItem<T>[]) : [];
34 const addToItemsIdMap = <T>(item: TreeItem<T>, itemsIdMap: Map<string, TreeItem<T>>): TreeItem<T> => {
35 itemsIdMap[item.id] = item;
39 const mapStateToProps =
40 <T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items' | 'disableRipple' | 'itemsMap'> => {
41 const itemsIdMap: Map<string, TreeItem<T>> = new Map();
42 const tree: Tree<T> = state.treePicker[props.pickerId] || createTree<T>();
45 items: getNodeChildrenIds('')(tree)
46 .map(treePickerToTreeItems(tree, state.resources))
47 .map(item => addToItemsIdMap(item, itemsIdMap))
51 items: flatTree(itemsIdMap, 2, parentItem.items || []),
57 const mapDispatchToProps = <T>(_: Dispatch, props: TreePickerProps<T>): Pick<TreeProps<T>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
58 onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
59 toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
60 toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
61 toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
64 export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(TreeComponent);
66 const treePickerToTreeItems = <T>(tree: Tree<T>, resources: ResourcesState) =>
67 (id: string): TreeItem<any> => {
68 const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
69 const items = getNodeChildrenIds(node.id)(tree)
70 .map(treePickerToTreeItems(tree, resources));
71 const resource = resources[node.id] as (Resource | undefined);
78 name: typeof node.value === "string"
80 : typeof (node.value as any).name === "string"
81 ? (node.value as any).name
86 items: items.length > 0 ? items : undefined,
88 selected: node.selected,
89 status: treeNodeStatusToTreeItem(node.status),
93 export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => {
95 case TreeNodeStatus.INITIAL:
96 return TreeItemStatus.INITIAL;
97 case TreeNodeStatus.PENDING:
98 return TreeItemStatus.PENDING;
99 case TreeNodeStatus.LOADED:
100 return TreeItemStatus.LOADED;