1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as React from 'react';
6 import List from "@material-ui/core/List/List";
7 import ListItem from "@material-ui/core/ListItem/ListItem";
8 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
9 import { ReactElement } from "react";
10 import Collapse from "@material-ui/core/Collapse/Collapse";
11 import CircularProgress from '@material-ui/core/CircularProgress';
12 import * as classnames from "classnames";
13 import { ListItemIcon } from '@material-ui/core/';
15 import { ArvadosTheme } from '../../common/custom-theme';
16 import { SidePanelRightArrowIcon } from '../icon/icon';
18 type CssRules = 'list' | 'active' | 'loader' | 'toggableIconContainer' | 'iconClose' | 'iconOpen' | 'toggableIcon';
20 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
27 transform: 'translate(0px)',
30 toggableIconContainer: {
31 color: theme.palette.grey["700"],
39 color: theme.palette.primary.main,
42 transition: 'all 0.1s ease',
45 transition: 'all 0.1s ease',
46 transform: 'rotate(90deg)',
50 export enum TreeItemStatus {
56 export interface TreeItem<T> {
61 status: TreeItemStatus;
63 items?: Array<TreeItem<T>>;
66 interface TreeProps<T> {
67 items?: Array<TreeItem<T>>;
68 render: (item: TreeItem<T>, level?: number) => ReactElement<{}>;
69 toggleItemOpen: (id: string, status: TreeItemStatus) => void;
70 toggleItemActive: (id: string, status: TreeItemStatus) => void;
72 onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
75 export const Tree = withStyles(styles)(
76 class Component<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
77 render(): ReactElement<any> {
78 const level = this.props.level ? this.props.level : 0;
79 const { classes, render, toggleItemOpen, items, toggleItemActive, onContextMenu } = this.props;
80 const { list, loader, toggableIconContainer } = classes;
81 return <List component="div" className={list}>
82 {items && items.map((it: TreeItem<T>, idx: number) =>
83 <div key={`item/${level}/${idx}`}>
84 <ListItem button className={list} style={{ paddingLeft: (level + 1) * 20 }}
85 onClick={() => toggleItemActive(it.id, it.status)}
86 onContextMenu={this.handleRowContextMenu(it)}>
87 {it.status === TreeItemStatus.Pending ?
88 <CircularProgress size={10} className={loader} /> : null}
89 <i onClick={() => this.props.toggleItemOpen(it.id, it.status)}
90 className={toggableIconContainer}>
91 <ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
92 {it.toggled && it.items && it.items.length === 0 ? <span /> : <SidePanelRightArrowIcon />}
97 {it.items && it.items.length > 0 &&
98 <Collapse in={it.open} timeout="auto" unmountOnExit>
102 toggleItemOpen={toggleItemOpen}
103 toggleItemActive={toggleItemActive}
105 onContextMenu={onContextMenu} />
111 getToggableIconClassNames = (isOpen?: boolean, isActive?: boolean) => {
112 const { iconOpen, iconClose, active, toggableIcon } = this.props.classes;
113 return classnames(toggableIcon, {
115 [iconClose]: !isOpen,
120 handleRowContextMenu = (item: TreeItem<T>) =>
121 (event: React.MouseEvent<HTMLElement>) =>
122 this.props.onContextMenu(event, item)