1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as React from 'react';
6 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
7 import { ReactElement } from "react";
8 import { FixedSizeList, ListChildComponentProps } from "react-window";
9 import AutoSizer from "react-virtualized-auto-sizer";
10 // import {FixedSizeTree as Tree} from 'react-vtree';
12 import { ArvadosTheme } from '~/common/custom-theme';
13 import { TreeItem } from './tree';
14 // import { FileTreeData } from '../file-tree/file-tree-data';
16 type CssRules = 'list'
20 | 'toggableIconContainer'
28 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
40 transform: 'translate(0px)',
43 toggableIconContainer: {
44 color: theme.palette.grey["700"],
55 color: theme.palette.primary.main,
58 transition: 'all 0.1s ease',
61 transition: 'all 0.1s ease',
62 transform: 'rotate(90deg)',
65 width: theme.spacing.unit * 3,
66 height: theme.spacing.unit * 3,
67 margin: `0 ${theme.spacing.unit}px`,
69 color: theme.palette.grey["500"],
73 export interface TreeProps<T> {
74 disableRipple?: boolean;
75 currentItemUuid?: string;
76 items?: Array<TreeItem<T>>;
78 onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
79 render: (item: TreeItem<T>, level?: number) => ReactElement<{}>;
80 showSelection?: boolean | ((item: TreeItem<T>) => boolean);
81 levelIndentation?: number;
82 itemRightPadding?: number;
83 toggleItemActive: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
84 toggleItemOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
85 toggleItemSelection?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
88 * When set to true use radio buttons instead of checkboxes for item selection.
89 * This does not guarantee radio group behavior (i.e item mutual exclusivity).
90 * Any item selection logic must be done in the toggleItemActive callback prop.
92 useRadioButtons?: boolean;
95 // export const RowA = <T, _>(items: TreeItem<T>[], render:any) => (index: number) => {
97 // {render(items[index])}
101 // For some reason, on TSX files it isn't accepted just one generic param, so
102 // I'm using <T, _> as a workaround.
103 export const Row = <T, _>(items: TreeItem<T>[], render: any) => (props: React.PropsWithChildren<ListChildComponentProps>) => {
104 const { index, style } = props;
105 const level = items[index].level || 0;
106 const levelIndentation = 20;
107 return <div style={style}>
108 <div style={{ paddingLeft: (level + 1) * levelIndentation,}}>
109 {typeof render === 'function'
110 ? items[index] && render(items[index]) || ''
114 // <div style={style} key={`item/${level}/${idx}`}>
115 // <ListItem button className={listItem}
117 // paddingLeft: (level + 1) * levelIndentation,
118 // paddingRight: itemRightPadding,
120 // disableRipple={disableRipple}
121 // onClick={event => toggleItemActive(event, it)}
122 // selected={showSelection(it) && it.id === currentItemUuid}
123 // onContextMenu={this.handleRowContextMenu(it)}>
124 // {it.status === TreeItemStatus.PENDING ?
125 // <CircularProgress size={10} className={loader} /> : null}
126 // <i onClick={this.handleToggleItemOpen(it)}
127 // className={toggableIconContainer}>
128 // <ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
129 // {this.getProperArrowAnimation(it.status, it.items!)}
132 // {showSelection(it) && !useRadioButtons &&
134 // checked={it.selected}
135 // className={classes.checkbox}
137 // onClick={this.handleCheckboxChange(it)} />}
138 // {showSelection(it) && useRadioButtons &&
140 // checked={it.selected}
141 // className={classes.checkbox}
142 // color="primary" />}
143 // <div className={renderContainer}>
144 // {render(it, level)}
147 // {it.items && it.items.length > 0 &&
148 // <Collapse in={it.open} timeout="auto" unmountOnExit>
150 // showSelection={this.props.showSelection}
153 // disableRipple={disableRipple}
154 // toggleItemOpen={toggleItemOpen}
155 // toggleItemActive={toggleItemActive}
157 // onContextMenu={onContextMenu}
158 // toggleItemSelection={this.props.toggleItemSelection} />
163 export const VirtualList = <T, _>(height: number, width: number, items: TreeItem<T>[], render: any) =>
166 itemCount={items.length}
173 export const VirtualTree = withStyles(styles)(
174 class Component<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
175 render(): ReactElement<any> {
176 const { items, render } = this.props;
178 return <div className={this.props.classes.virtualizedList}><AutoSizer>
179 {({ height, width }) => {
180 return VirtualList(height, width, items || [], render);
187 // const treeWalkerWithTree = (tree: Array<TreeItem<FileTreeData>>) => function* treeWalker(refresh: any) {
190 // // Remember all the necessary data of the first node in the stack.
196 // // Walk through the tree until we have no nodes available.
197 // while (stack.length !== 0) {
199 // node: {items = [], id, name},
203 // // Here we are sending the information about the node to the Tree component
204 // // and receive an information about the openness state from it. The
205 // // `refresh` parameter tells us if the full update of the tree is requested;
206 // // basing on it we decide to return the full node data or only the node
207 // // id to update the nodes order.
208 // const isOpened = yield refresh
211 // isLeaf: items.length === 0,
212 // isOpenByDefault: true,
218 // // Basing on the node openness state we are deciding if we need to render
219 // // the child nodes (if they exist).
220 // if (children.length !== 0 && isOpened) {
221 // // Since it is a stack structure, we need to put nodes we want to render
222 // // first to the end of the stack.
223 // for (let i = children.length - 1; i >= 0; i--) {
225 // nestingLevel: nestingLevel + 1,
226 // node: children[i],
233 // // Node component receives all the data we created in the `treeWalker` +
234 // // internal openness state (`isOpen`), function to change internal openness
235 // // state (`toggle`) and `style` parameter that should be added to the root div.
236 // const Node = ({data: {isLeaf, name}, isOpen, style, toggle}) => (
237 // <div style={style}>
239 // <button type="button" onClick={toggle}>
240 // {isOpen ? '-' : '+'}
247 // export const Example = () => (
248 // <Tree treeWalker={treeWalker} itemSize={30} height={150} width={300}>