X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/a5984c9693267f41237746af6b37d8402effdc39..89efe3a344e0850cf7e401142d2a838cbc825ffb:/src/components/tree/tree.tsx diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index 12369d57..c892d7d2 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -3,25 +3,178 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import List from "@material-ui/core/List/List"; -import ListItem from "@material-ui/core/ListItem/ListItem"; +import { List, ListItem, ListItemIcon, Collapse, Checkbox } from "@material-ui/core"; +import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles'; import { ReactElement } from "react"; +import CircularProgress from '@material-ui/core/CircularProgress'; +import * as classnames from "classnames"; -interface TreeProps { - items: T[], - render: (item: T) => ReactElement<{}> -} +import { ArvadosTheme } from '~/common/custom-theme'; +import { SidePanelRightArrowIcon } from '../icon/icon'; + +type CssRules = 'list' + | 'listItem' + | 'active' + | 'loader' + | 'toggableIconContainer' + | 'iconClose' + | 'renderContainer' + | 'iconOpen' + | 'toggableIcon' + | 'checkbox'; -class Tree extends React.Component, {}> { - render() { - return - {this.props.items && this.props.items.map((it: T, idx: number) => - - {this.props.render(it)} - - )} - +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + list: { + padding: '3px 0px' + }, + listItem: { + padding: '3px 0px', + }, + loader: { + position: 'absolute', + transform: 'translate(0px)', + top: '3px' + }, + toggableIconContainer: { + color: theme.palette.grey["700"], + height: '14px', + width: '14px', + }, + toggableIcon: { + fontSize: '14px' + }, + renderContainer: { + flex: 1 + }, + active: { + color: theme.palette.primary.main, + }, + iconClose: { + transition: 'all 0.1s ease', + }, + iconOpen: { + transition: 'all 0.1s ease', + transform: 'rotate(90deg)', + }, + checkbox: { + width: theme.spacing.unit * 3, + height: theme.spacing.unit * 3, + margin: `0 ${theme.spacing.unit}px`, + color: theme.palette.grey["500"] } +}); + +export enum TreeItemStatus { + INITIAL = 'initial', + PENDING = 'pending', + LOADED = 'loaded' +} + +export interface TreeItem { + data: T; + id: string; + open: boolean; + active: boolean; + selected?: boolean; + status: TreeItemStatus; + items?: Array>; +} + +export interface TreeProps { + items?: Array>; + render: (item: TreeItem, level?: number) => ReactElement<{}>; + toggleItemOpen: (id: string, status: TreeItemStatus) => void; + toggleItemActive: (id: string, status: TreeItemStatus) => void; + level?: number; + onContextMenu: (event: React.MouseEvent, item: TreeItem) => void; + showSelection?: boolean; + onSelectionChange?: (event: React.MouseEvent, item: TreeItem) => void; + disableRipple?: boolean; } -export default Tree; +export const Tree = withStyles(styles)( + class Component extends React.Component & WithStyles, {}> { + render(): ReactElement { + const level = this.props.level ? this.props.level : 0; + const { classes, render, toggleItemOpen, items, toggleItemActive, onContextMenu, disableRipple } = this.props; + const { list, listItem, loader, toggableIconContainer, renderContainer } = classes; + return + {items && items.map((it: TreeItem, idx: number) => +
+ toggleItemActive(it.id, it.status)} + onContextMenu={this.handleRowContextMenu(it)}> + {it.status === TreeItemStatus.PENDING ? + : null} + + + {this.getProperArrowAnimation(it.status, it.items!)} + + + {this.props.showSelection && + } +
+ {render(it, level)} +
+
+ {it.items && it.items.length > 0 && + + + } +
)} +
; + } + + getProperArrowAnimation = (status: string, items: Array>) => { + return this.isSidePanelIconNotNeeded(status, items) ? : ; + } + + isSidePanelIconNotNeeded = (status: string, items: Array>) => { + return status === TreeItemStatus.PENDING || + (status === TreeItemStatus.LOADED && !items) || + (status === TreeItemStatus.LOADED && items && items.length === 0); + } + + getToggableIconClassNames = (isOpen?: boolean, isActive?: boolean) => { + const { iconOpen, iconClose, active, toggableIcon } = this.props.classes; + return classnames(toggableIcon, { + [iconOpen]: isOpen, + [iconClose]: !isOpen, + [active]: isActive + }); + } + + handleRowContextMenu = (item: TreeItem) => + (event: React.MouseEvent) => + this.props.onContextMenu(event, item) + + handleCheckboxChange = (item: TreeItem) => { + const { onSelectionChange } = this.props; + return onSelectionChange + ? (event: React.MouseEvent) => { + onSelectionChange(event, item); + } + : undefined; + } + + handleToggleItemOpen = (id: string, status: TreeItemStatus) => (event: React.MouseEvent) => { + event.stopPropagation(); + this.props.toggleItemOpen(id, status); + } + } +);