// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import { connect } from "react-redux"; import { Tree as TreeComponent, TreeProps, TreeItem, TreeItemStatus } from "components/tree/tree"; import { RootState } from "store/store"; import { getNodeChildrenIds, Tree, createTree, getNode, TreeNodeStatus } from 'models/tree'; import { Dispatch } from "redux"; import { initTreeNode } from '../../models/tree'; import { ResourcesState } from "store/resources/resources"; import { Resource } from "models/resource"; type Callback = (event: React.MouseEvent, item: TreeItem, pickerId: string) => void; export interface TreePickerProps { pickerId: string; onContextMenu: Callback; toggleItemOpen: Callback; toggleItemActive: Callback; toggleItemSelection: Callback; } const flatTree = (itemsIdMap: Map>, depth: number, items?: TreeItem[]): TreeItem[] => { return items ? items .map((item: TreeItem) => addToItemsIdMap(item, itemsIdMap)) .reduce((acc: Array>, next: TreeItem) => { const { items } = next; acc.push({ ...next, depth }); acc.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : [])); return acc; }, [] as TreeItem[]) : []; }; const addToItemsIdMap = (item: TreeItem, itemsIdMap: Map>): TreeItem => { itemsIdMap[item.id] = item; return item; }; const mapStateToProps = (state: RootState, props: TreePickerProps): Pick, 'items' | 'disableRipple' | 'itemsMap'> => { const itemsIdMap: Map> = new Map(); const tree: Tree = state.treePicker[props.pickerId] || createTree(); return { disableRipple: true, items: getNodeChildrenIds('')(tree) .map(treePickerToTreeItems(tree, state.resources)) .map(item => addToItemsIdMap(item, itemsIdMap)) .map(parentItem => ({ ...parentItem, flatTree: true, items: flatTree(itemsIdMap, 2, parentItem.items || []), })), itemsMap: itemsIdMap, }; }; const mapDispatchToProps = (_: Dispatch, props: TreePickerProps): Pick, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({ onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId), toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId), toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId), toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId), }); export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(TreeComponent); const treePickerToTreeItems = (tree: Tree, resources: ResourcesState) => (id: string): TreeItem => { const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' }); const items = getNodeChildrenIds(node.id)(tree) .map(treePickerToTreeItems(tree, resources)); const resource = resources[node.id] as (Resource | undefined); return { active: node.active, data: resource ? { ...resource, name: typeof node.value === "string" ? node.value : typeof (node.value as any).name === "string" ? (node.value as any).name : "", weight: (node.value as any).weight } : node.value, id: node.id, items: items.length > 0 ? items : undefined, open: node.expanded, selected: node.selected, status: treeNodeStatusToTreeItem(node.status), }; }; export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => { switch (status) { case TreeNodeStatus.INITIAL: return TreeItemStatus.INITIAL; case TreeNodeStatus.PENDING: return TreeItemStatus.PENDING; case TreeNodeStatus.LOADED: return TreeItemStatus.LOADED; } };