From: Stephen Smith Date: Wed, 31 May 2023 13:42:54 +0000 (-0400) Subject: 20031: Change Tree component to functional, use react hooks to track selected tree... X-Git-Tag: 2.7.0~20^2~2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/fe0f3f07a4dea0506844d47529752484cf0347a7 20031: Change Tree component to functional, use react hooks to track selected tree item ref and scroll to item when it changes Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index e3708621..9c001e7a 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import React from 'react'; +import React, { useCallback, useState } from 'react'; import { List, ListItem, ListItemIcon, Checkbox, Radio, Collapse } from "@material-ui/core"; import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles'; import { CollectionIcon, DefaultIcon, DirectoryIcon, FileIcon, ProjectIcon, FilterGroupIcon, FreezeIcon } from 'components/icon/icon'; @@ -125,6 +125,7 @@ export interface TreeProps { toggleItemActive: (event: React.MouseEvent, item: TreeItem) => void; toggleItemOpen: (event: React.MouseEvent, item: TreeItem) => void; toggleItemSelection?: (event: React.MouseEvent, item: TreeItem) => void; + selectedRef?: (node: HTMLDivElement | null) => void; /** * When set to true use radio buttons instead of checkboxes for item selection. @@ -167,6 +168,7 @@ interface FlatTreeProps { showSelection: any; useRadioButtons?: boolean; handleCheckboxChange: Function; + selectedRef?: (node: HTMLDivElement | null) => void; } const FLAT_TREE_ACTIONS = { @@ -254,7 +256,7 @@ const FlatTree = (props: FlatTreeProps) => checked={item.selected} className={props.classes.checkbox} color="primary" />} -
+
@@ -270,98 +272,26 @@ const FlatTree = (props: FlatTreeProps) =>
; 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, items, toggleItemActive, toggleItemOpen, disableRipple, currentItemUuid, useRadioButtons, itemsMap } = this.props; - const { list, listItem, loader, toggableIconContainer, renderContainer } = classes; - const showSelection = typeof this.props.showSelection === 'function' - ? this.props.showSelection - : () => this.props.showSelection ? true : false; + function(props: TreeProps & WithStyles) { + const level = props.level ? props.level : 0; + const { classes, render, items, toggleItemActive, toggleItemOpen, disableRipple, currentItemUuid, useRadioButtons, itemsMap } = props; + const { list, listItem, loader, toggableIconContainer, renderContainer } = classes; + const showSelection = typeof props.showSelection === 'function' + ? props.showSelection + : () => props.showSelection ? true : false; - const { levelIndentation = 20, itemRightPadding = 20 } = this.props; - return - {items && items.map((it: TreeItem, idx: number) => -
- toggleItemActive(event, it)} - selected={showSelection(it) && it.id === currentItemUuid} - onContextMenu={(event) => this.props.onContextMenu(event, it)}> - {it.status === TreeItemStatus.PENDING ? - : null} - this.handleToggleItemOpen(it, e)} - className={toggableIconContainer}> - - {this.getProperArrowAnimation(it.status, it.items!)} - - - {showSelection(it) && !useRadioButtons && - } - {showSelection(it) && useRadioButtons && - } -
- {render(it, level)} -
-
- { - it.open && it.items && it.items.length > 0 && - it.flatTree ? - : - - - - } -
)} -
; + const getProperArrowAnimation = (status: string, items: Array>) => { + return isSidePanelIconNotNeeded(status, items) ? : ; } - getProperArrowAnimation = (status: string, items: Array>) => { - return this.isSidePanelIconNotNeeded(status, items) ? : ; - } - - isSidePanelIconNotNeeded = (status: string, items: Array>) => { + const 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; + const getToggableIconClassNames = (isOpen?: boolean, isActive?: boolean) => { + const { iconOpen, iconClose, active, toggableIcon } = props.classes; return classnames(toggableIcon, { [iconOpen]: isOpen, [iconClose]: !isOpen, @@ -369,8 +299,8 @@ export const Tree = withStyles(styles)( }); } - handleCheckboxChange = (item: TreeItem) => { - const { toggleItemSelection } = this.props; + const handleCheckboxChange = (item: TreeItem) => { + const { toggleItemSelection } = props; return toggleItemSelection ? (event: React.MouseEvent) => { event.stopPropagation(); @@ -379,9 +309,92 @@ export const Tree = withStyles(styles)( : undefined; } - handleToggleItemOpen = (item: TreeItem, event: React.MouseEvent) => { + const handleToggleItemOpen = (item: TreeItem, event: React.MouseEvent) => { event.stopPropagation(); - this.props.toggleItemOpen(event, item); + props.toggleItemOpen(event, item); } + + // Scroll to selected item whenever it changes, accepts selectedRef from props for recursive trees + const [cachedSelectedRef, setCachedRef] = useState(null) + const selectedRef = props.selectedRef || useCallback((node: HTMLDivElement | null) => { + if (node && node !== cachedSelectedRef) { + node.scrollIntoView({ behavior: "smooth", block: "center" }); + } + setCachedRef(node); + }, [cachedSelectedRef]); + + const { levelIndentation = 20, itemRightPadding = 20 } = props; + return + {items && items.map((it: TreeItem, idx: number) => { + return
+ toggleItemActive(event, it)} + selected={showSelection(it) && it.id === currentItemUuid} + onContextMenu={(event) => props.onContextMenu(event, it)}> + {it.status === TreeItemStatus.PENDING ? + : null} + handleToggleItemOpen(it, e)} + className={toggableIconContainer}> + + {getProperArrowAnimation(it.status, it.items!)} + + + {showSelection(it) && !useRadioButtons && + } + {showSelection(it) && useRadioButtons && + } +
+ {render(it, level)} +
+
+ { + it.open && it.items && it.items.length > 0 && + it.flatTree ? + : + + + + } +
; + })} +
; } );