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, 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';
12 type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
13 export interface TreePickerProps<T> {
15 onContextMenu: Callback<T>;
16 toggleItemOpen: Callback<T>;
17 toggleItemActive: Callback<T>;
18 toggleItemSelection: Callback<T>;
21 const flatTree = (depth: number, items?: any): [] => {
24 .reduce((prev: any, next: any) => {
25 const { items } = next;
30 ...(next.open ? flatTree(depth + 1, items) : []),
35 const itemsIdMap = new Map();
36 const addToItemsIdMap = <T>(item: TreeItem<T>) => {
37 itemsIdMap[item.id] = item;
41 const memoizedMapStateToProps = () => {
42 let prevTree: Ttree<any>;
43 let mappedProps: Pick<TreeProps<any>, 'items' | 'disableRipple' | 'itemsMap'>;
44 return <T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items' | 'disableRipple' | 'itemsMap'> => {
45 const tree = state.treePicker[props.pickerId] || createTree();
46 if (tree !== prevTree) {
50 items: getNodeChildrenIds('')(tree)
51 .map(treePickerToTreeItems(tree))
56 items: flatTree(2, parentItem.items || []),
65 const mapDispatchToProps = (_: Dispatch, props: TreePickerProps<any>): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
66 onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
67 toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
68 toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
69 toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
72 export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
74 const treePickerToTreeItems = (tree: Ttree<any>) =>
75 (id: string): TreeItem<any> => {
76 const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
77 const items = getNodeChildrenIds(node.id)(tree)
78 .map(treePickerToTreeItems(tree));
83 items: items.length > 0 ? items : undefined,
85 selected: node.selected,
86 status: treeNodeStatusToTreeItem(node.status),
90 export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => {
92 case TreeNodeStatus.INITIAL:
93 return TreeItemStatus.INITIAL;
94 case TreeNodeStatus.PENDING:
95 return TreeItemStatus.PENDING;
96 case TreeNodeStatus.LOADED:
97 return TreeItemStatus.LOADED;