20031: Change Tree component to functional, use react hooks to track selected tree...
[arvados-workbench2.git] / src / components / tree / virtual-tree.tsx
index 467969fa1417b357973c4dcb8b1fbaf0449c5814..ca7cd40caf43d4633a9b99bb111d29df204df97d 100644 (file)
@@ -2,14 +2,14 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from 'react';
-import * as classnames from "classnames";
+import 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 { ArvadosTheme } from '~/common/custom-theme';
+import { ArvadosTheme } from 'common/custom-theme';
 import { TreeItem, TreeProps, TreeItemStatus } from './tree';
 import { ListItem, Radio, Checkbox, CircularProgress, ListItemIcon } from '@material-ui/core';
 import { SidePanelRightArrowIcon } from '../icon/icon';
@@ -24,12 +24,18 @@ type CssRules = 'list'
     | 'iconOpen'
     | 'toggableIcon'
     | 'checkbox'
+    | 'virtualFileTree'
     | 'virtualizedList';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     list: {
         padding: '3px 0px',
     },
+    virtualFileTree: {
+        "&:last-child": {
+            paddingBottom: 20
+          }
+    },
     virtualizedList: {
         height: '200px',
     },
@@ -71,35 +77,36 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     }
 });
 
-// export const RowA = <T, _>(items: TreeItem<T>[], render:any) => (index: number) => {
-//     return <div>
-//         {render(items[index])}
-//     </div>;
-// };
+export interface VirtualTreeItem<T> extends TreeItem<T> {
+    itemCount?: number;
+    level?: number;
+}
 
 // For some reason, on TSX files it isn't accepted just one generic param, so
 // I'm using <T, _> as a workaround.
-export const Row =  <T, _>(itemList: TreeItem<T>[], render: any) => withStyles(styles)(
-    (props: React.PropsWithChildren<ListChildComponentProps> & TreeProps<T> & WithStyles<CssRules>) => {
-        const { index, style } = props;
+// eslint-disable-next-line
+export const Row =  <T, _>(itemList: VirtualTreeItem<T>[], render: any, treeProps: TreeProps<T>) => withStyles(styles)(
+    (props: React.PropsWithChildren<ListChildComponentProps> & WithStyles<CssRules>) => {
+        const { index, style, classes } = props;
         const it = itemList[index];
         const level = it.level || 0;
-        const { classes, toggleItemActive, disableRipple, currentItemUuid, useRadioButtons } = props;
-        const { listItem, loader, toggableIconContainer, renderContainer } = classes;
-        const { levelIndentation = 20, itemRightPadding = 20 } = props;
+        const { toggleItemActive, disableRipple, currentItemUuid, useRadioButtons } = treeProps;
+        const { listItem, loader, toggableIconContainer, renderContainer, virtualFileTree } = classes;
+        const { levelIndentation = 20, itemRightPadding = 20 } = treeProps;
 
-        const showSelection = typeof props.showSelection === 'function'
-            ? props.showSelection
-            : () => props.showSelection ? true : false;
+        const showSelection = typeof treeProps.showSelection === 'function'
+            ? treeProps.showSelection
+            : () => treeProps.showSelection ? true : false;
 
-        const handleRowContextMenu = (item: TreeItem<T>) =>
-            (event: React.MouseEvent<HTMLElement>) =>
-                props.onContextMenu(event, item);
+        const handleRowContextMenu = (item: VirtualTreeItem<T>) =>
+            (event: React.MouseEvent<HTMLElement>) => {
+                treeProps.onContextMenu(event, item);
+            };
 
-        const handleToggleItemOpen = (item: TreeItem<T>) =>
+        const handleToggleItemOpen = (item: VirtualTreeItem<T>) =>
             (event: React.MouseEvent<HTMLElement>) => {
                 event.stopPropagation();
-                props.toggleItemOpen(event, item);
+                treeProps.toggleItemOpen(event, item);
             };
 
         const getToggableIconClassNames = (isOpen?: boolean, isActive?: boolean) => {
@@ -120,8 +127,8 @@ export const Row =  <T, _>(itemList: TreeItem<T>[], render: any) => withStyles(s
             return isSidePanelIconNotNeeded(status, itemCount) ? <span /> : <SidePanelRightArrowIcon style={{ fontSize: '14px' }} />;
         };
 
-        const handleCheckboxChange = (item: TreeItem<T>) => {
-            const { toggleItemSelection } = props;
+        const handleCheckboxChange = (item: VirtualTreeItem<T>) => {
+            const { toggleItemSelection } = treeProps;
             return toggleItemSelection
                 ? (event: React.MouseEvent<HTMLElement>) => {
                     event.stopPropagation();
@@ -130,7 +137,7 @@ export const Row =  <T, _>(itemList: TreeItem<T>[], render: any) => withStyles(s
                 : undefined;
         };
 
-        return <div style={style}>
+        return <div className={virtualFileTree} data-cy='virtual-file-tree' style={style}>
             <ListItem button className={listItem}
                 style={{
                     paddingLeft: (level + 1) * levelIndentation,
@@ -166,26 +173,28 @@ export const Row =  <T, _>(itemList: TreeItem<T>[], render: any) => withStyles(s
         </div>;
     });
 
-export const VirtualList = <T, _>(height: number, width: number, items: TreeItem<T>[], render: any) =>
+const itemSize = 30;
+
+// eslint-disable-next-line
+export const VirtualList = <T, _>(height: number, width: number, items: VirtualTreeItem<T>[], render: any, treeProps: TreeProps<T>) =>
     <FixedSizeList
         height={height}
         itemCount={items.length}
-        itemSize={30}
+        itemSize={itemSize}
         width={width}
     >
-        {Row(items, render)}
+        {Row(items, render, treeProps)}
     </FixedSizeList>;
 
 export const VirtualTree = withStyles(styles)(
     class Component<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
         render(): ReactElement<any> {
             const { items, render } = this.props;
-
-            return <div className={this.props.classes.virtualizedList}><AutoSizer>
+            return <AutoSizer>
                 {({ height, width }) => {
-                    return VirtualList(height, width, items || [], render);
+                    return VirtualList(height, width, items || [], render, this.props);
                 }}
-            </AutoSizer></div>;
+            </AutoSizer>;
         }
     }
 );