X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/f1db282d059c1d0a6e264943344e09bda5d40282..bf8c821490525bc063ffcc9ceeef06c525696e0c:/src/components/tree/tree.tsx diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index 5b070b70..7371654b 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -3,11 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { List, ListItem, ListItemIcon, Collapse, Checkbox } from "@material-ui/core"; +import { List, ListItem, ListItemIcon, Checkbox, Radio } 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"; +import classnames from "classnames"; import { ArvadosTheme } from '~/common/custom-theme'; import { SidePanelRightArrowIcon } from '../icon/icon'; @@ -21,7 +21,8 @@ type CssRules = 'list' | 'renderContainer' | 'iconOpen' | 'toggableIcon' - | 'checkbox'; + | 'checkbox' + | 'childItem'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ list: { @@ -62,7 +63,16 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ margin: `0 ${theme.spacing.unit}px`, padding: 0, color: theme.palette.grey["500"], - } + }, + childItem: { + cursor: 'pointer', + display: 'flex', + padding: '3px 20px', + fontSize: '0.875rem', + '&:hover': { + backgroundColor: 'rgba(0, 0, 0, 0.08)', + } + }, }); export enum TreeItemStatus { @@ -83,6 +93,7 @@ export interface TreeItem { export interface TreeProps { disableRipple?: boolean; + currentItemUuid?: string; items?: Array>; level?: number; onContextMenu: (event: React.MouseEvent, item: TreeItem) => void; @@ -93,52 +104,139 @@ export interface TreeProps { toggleItemActive: (event: React.MouseEvent, item: TreeItem) => void; toggleItemOpen: (event: React.MouseEvent, item: TreeItem) => void; toggleItemSelection?: (event: React.MouseEvent, item: TreeItem) => void; + + /** + * When set to true use radio buttons instead of checkboxes for item selection. + * This does not guarantee radio group behavior (i.e item mutual exclusivity). + * Any item selection logic must be done in the toggleItemActive callback prop. + */ + useRadioButtons?: boolean; } +const flatTree = (depth: number, items?: any): [] => { + return items ? items.reduce((prev: any, next: any) => { + const { items } = next; + // delete next.items; + return [ + ...prev, + { ...next, depth }, + ...(next.open ? flatTree(depth + 1, items) : []), + ]; + }, []) : []; +}; + +const getActionAndId = (event: any, initAction: string | undefined = undefined) => { + const { nativeEvent: { target } } = event; + let currentTarget: HTMLElement = target as HTMLElement; + let action: string | undefined = initAction || currentTarget.dataset.action; + let id: string | undefined = currentTarget.dataset.id; + + while (action === undefined || id === undefined) { + currentTarget = currentTarget.parentElement as HTMLElement; + + if (!currentTarget) { + break; + } + + action = action || currentTarget.dataset.action; + id = id || currentTarget.dataset.id; + } + + return [action, id]; +}; + 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; - const isCheckboxVisible = typeof this.props.showSelection === 'function' + const { classes, render, items, toggleItemActive, disableRipple, currentItemUuid, useRadioButtons } = this.props; + const { list, listItem, loader, toggableIconContainer, renderContainer, childItem, active } = classes; + const showSelection = typeof this.props.showSelection === 'function' ? this.props.showSelection : () => this.props.showSelection ? true : false; const { levelIndentation = 20, itemRightPadding = 20 } = this.props; + const flatItems = (items || []) + .map(parentItem => ({ + ...parentItem, + items: flatTree(2, parentItem.items || []), + })); + return - {items && items.map((it: TreeItem, idx: number) => -
- , idx: number) => +
+ toggleItemActive(event, it)} - onContextMenu={this.handleRowContextMenu(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!)} - { isCheckboxVisible(it) && + {showSelection(it) && !useRadioButtons && } + {showSelection(it) && useRadioButtons && + }
{render(it, level)}
{it.items && it.items.length > 0 && - - { + const [action, id] = getActionAndId(event, 'CONTEXT_MENU'); + this.props.onContextMenu(event, { id } as any); + }} + onClick={(event) => { + const [action, id] = getActionAndId(event); + + if (action && id) { + switch(action) { + case 'TOGGLE_OPEN': + this.handleToggleItemOpen({ id } as any, event); + break; + case 'TOGGLE_ACTIVE': + toggleItemActive(event, { id } as any); + break; + default: + break; + } + } + }} + > + { + it.items + .map((item: any) =>
+ + + {this.getProperArrowAnimation(item.status, item.items!)} + + +
+ {item.data.name} +
+
) + } + {/* -
} + toggleItemSelection={this.props.toggleItemSelection} /> */} +
}
)}
; } @@ -172,10 +270,6 @@ export const Tree = withStyles(styles)( }); } - handleRowContextMenu = (item: TreeItem) => - (event: React.MouseEvent) => - this.props.onContextMenu(event, item) - handleCheckboxChange = (item: TreeItem) => { const { toggleItemSelection } = this.props; return toggleItemSelection @@ -186,7 +280,7 @@ export const Tree = withStyles(styles)( : undefined; } - handleToggleItemOpen = (item: TreeItem) => (event: React.MouseEvent) => { + handleToggleItemOpen = (item: TreeItem, event: React.MouseEvent) => { event.stopPropagation(); this.props.toggleItemOpen(event, item); }