X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/e92207c912aed73a07340b5fb2a9e2cb23e1da5f..c859c9d9325e2ed86d5d7b067e1209e73ee81251:/src/components/tree/virtual-tree.tsx?ds=sidebyside diff --git a/src/components/tree/virtual-tree.tsx b/src/components/tree/virtual-tree.tsx index 4615db4f..54938969 100644 --- a/src/components/tree/virtual-tree.tsx +++ b/src/components/tree/virtual-tree.tsx @@ -3,15 +3,16 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; +import classnames from "classnames"; import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles'; import { ReactElement } from "react"; import { FixedSizeList, ListChildComponentProps } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; -// import {FixedSizeTree as Tree} from 'react-vtree'; import { ArvadosTheme } from '~/common/custom-theme'; -import { TreeItem } from './tree'; -// import { FileTreeData } from '../file-tree/file-tree-data'; +import { TreeItem, TreeProps, TreeItemStatus } from './tree'; +import { ListItem, Radio, Checkbox, CircularProgress, ListItemIcon } from '@material-ui/core'; +import { SidePanelRightArrowIcon } from '../icon/icon'; type CssRules = 'list' | 'listItem' @@ -70,104 +71,111 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ } }); -export interface TreeProps { - disableRipple?: boolean; - currentItemUuid?: string; - items?: Array>; +export interface VirtualTreeItem extends TreeItem { + itemCount?: number; level?: number; - onContextMenu: (event: React.MouseEvent, item: TreeItem) => void; - render: (item: TreeItem, level?: number) => ReactElement<{}>; - showSelection?: boolean | ((item: TreeItem) => boolean); - levelIndentation?: number; - itemRightPadding?: number; - toggleItemActive: (event: React.MouseEvent, item: TreeItem) => void; - toggleItemOpen: (event: React.MouseEvent, item: TreeItem) => void; - toggleItemSelection?: (event: React.MouseEvent, item: TreeItem) => void; - - /** - * When set to true use radio buttons instead of checkboxes for item selection. - * This does not guarantee radio group behavior (i.e item mutual exclusivity). - * Any item selection logic must be done in the toggleItemActive callback prop. - */ - useRadioButtons?: boolean; } -// export const RowA = (items: TreeItem[], render:any) => (index: number) => { -// return
-// {render(items[index])} -//
; -// }; - // For some reason, on TSX files it isn't accepted just one generic param, so // I'm using as a workaround. -export const Row = (items: TreeItem[], render: any) => (props: React.PropsWithChildren) => { - const { index, style } = props; - const level = items[index].level || 0; - const levelIndentation = 20; - return
-
- {typeof render === 'function' - ? items[index] && render(items[index]) || '' - : 'whoops'} -
-
; - //
- // toggleItemActive(event, it)} - // selected={showSelection(it) && it.id === currentItemUuid} - // onContextMenu={this.handleRowContextMenu(it)}> - // {it.status === TreeItemStatus.PENDING ? - // : null} - // - // - // {this.getProperArrowAnimation(it.status, it.items!)} - // - // - // {showSelection(it) && !useRadioButtons && - // } - // {showSelection(it) && useRadioButtons && - // } - //
- // {render(it, level)} - //
- //
- // {it.items && it.items.length > 0 && - // - // - // } - //
-}; - -export const VirtualList = (height: number, width: number, items: TreeItem[], render: any) => +export const Row = (itemList: VirtualTreeItem[], render: any, treeProps: TreeProps) => withStyles(styles)( + (props: React.PropsWithChildren & WithStyles) => { + const { index, style, classes } = props; + const it = itemList[index]; + const level = it.level || 0; + const { toggleItemActive, disableRipple, currentItemUuid, useRadioButtons } = treeProps; + const { listItem, loader, toggableIconContainer, renderContainer } = classes; + const { levelIndentation = 20, itemRightPadding = 20 } = treeProps; + + const showSelection = typeof treeProps.showSelection === 'function' + ? treeProps.showSelection + : () => treeProps.showSelection ? true : false; + + const handleRowContextMenu = (item: VirtualTreeItem) => + (event: React.MouseEvent) => { + treeProps.onContextMenu(event, item); + }; + + const handleToggleItemOpen = (item: VirtualTreeItem) => + (event: React.MouseEvent) => { + event.stopPropagation(); + treeProps.toggleItemOpen(event, item); + }; + + const getToggableIconClassNames = (isOpen?: boolean, isActive?: boolean) => { + const { iconOpen, iconClose, active, toggableIcon } = props.classes; + return classnames(toggableIcon, { + [iconOpen]: isOpen, + [iconClose]: !isOpen, + [active]: isActive + }); + }; + + const isSidePanelIconNotNeeded = (status: string, itemCount: number) => { + return status === TreeItemStatus.PENDING || + (status === TreeItemStatus.LOADED && itemCount === 0); + }; + + const getProperArrowAnimation = (status: string, itemCount: number) => { + return isSidePanelIconNotNeeded(status, itemCount) ? : ; + }; + + const handleCheckboxChange = (item: VirtualTreeItem) => { + const { toggleItemSelection } = treeProps; + return toggleItemSelection + ? (event: React.MouseEvent) => { + event.stopPropagation(); + toggleItemSelection(event, item); + } + : undefined; + }; + + return
+ toggleItemActive(event, it)} + selected={showSelection(it) && it.id === currentItemUuid} + onContextMenu={handleRowContextMenu(it)}> + {it.status === TreeItemStatus.PENDING ? + : null} + + + {getProperArrowAnimation(it.status, it.itemCount!)} + + + {showSelection(it) && !useRadioButtons && + } + {showSelection(it) && useRadioButtons && + } +
+ {render(it, level)} +
+
+
; + }); + +const itemSize = 30; + +export const VirtualList = (height: number, width: number, items: VirtualTreeItem[], render: any, treeProps: TreeProps) => - {Row(items, render)} + {Row(items, render, treeProps)} ; export const VirtualTree = withStyles(styles)( @@ -175,77 +183,11 @@ export const VirtualTree = withStyles(styles)( render(): ReactElement { const { items, render } = this.props; - return
+ return {({ height, width }) => { - return VirtualList(height, width, items || [], render); + return VirtualList(height, width, items || [], render, this.props); }} -
; + ; } } ); - -// const treeWalkerWithTree = (tree: Array>) => function* treeWalker(refresh: any) { -// const stack = []; - -// // Remember all the necessary data of the first node in the stack. -// stack.push({ -// nestingLevel: 0, -// node: tree, -// }); - -// // Walk through the tree until we have no nodes available. -// while (stack.length !== 0) { -// const { -// node: {items = [], id, name}, -// nestingLevel, -// } = stack.pop()!; - -// // Here we are sending the information about the node to the Tree component -// // and receive an information about the openness state from it. The -// // `refresh` parameter tells us if the full update of the tree is requested; -// // basing on it we decide to return the full node data or only the node -// // id to update the nodes order. -// const isOpened = yield refresh -// ? { -// id, -// isLeaf: items.length === 0, -// isOpenByDefault: true, -// name, -// nestingLevel, -// } -// : id; - -// // Basing on the node openness state we are deciding if we need to render -// // the child nodes (if they exist). -// if (children.length !== 0 && isOpened) { -// // Since it is a stack structure, we need to put nodes we want to render -// // first to the end of the stack. -// for (let i = children.length - 1; i >= 0; i--) { -// stack.push({ -// nestingLevel: nestingLevel + 1, -// node: children[i], -// }); -// } -// } -// } -// }; - -// // Node component receives all the data we created in the `treeWalker` + -// // internal openness state (`isOpen`), function to change internal openness -// // state (`toggle`) and `style` parameter that should be added to the root div. -// const Node = ({data: {isLeaf, name}, isOpen, style, toggle}) => ( -//
-// {!isLeaf && ( -// -// )} -//
{name}
-//
-// ); - -// export const Example = () => ( -// -// {Node} -// -// ); \ No newline at end of file