Merge branch '17098-old-version-as-head'
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 2 Dec 2020 13:37:28 +0000 (10:37 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 2 Dec 2020 13:37:28 +0000 (10:37 -0300)
Closes #17098

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

src/components/tree/tree.tsx
src/views-components/side-panel-tree/side-panel-tree.tsx
src/views-components/tree-picker/tree-picker.ts

index 41498fc0525f1aa7f38410f4ca9ace2efa1d874c..9680e3c0780dc9000fe756c4e7a59375c117a50b 100644 (file)
@@ -3,8 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { List, ListItem, ListItemIcon, Collapse, Checkbox, Radio } from "@material-ui/core";
+import { List, ListItem, ListItemIcon, Checkbox, Radio, Collapse } from "@material-ui/core";
 import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
+import { ProjectIcon } from '~/components/icon/icon';
 import { ReactElement } from "react";
 import CircularProgress from '@material-ui/core/CircularProgress';
 import classnames from "classnames";
@@ -21,7 +22,9 @@ type CssRules = 'list'
     | 'renderContainer'
     | 'iconOpen'
     | 'toggableIcon'
-    | 'checkbox';
+    | 'checkbox'
+    | 'childItem'
+    | 'childItemIcon';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     list: {
@@ -46,9 +49,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     renderContainer: {
         flex: 1
     },
-    active: {
-        color: theme.palette.primary.main,
-    },
     iconClose: {
         transition: 'all 0.1s ease',
     },
@@ -62,7 +62,25 @@ const styles: StyleRulesCallback<CssRules> = (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',
+        alignItems: 'center',
+        '&:hover': {
+            backgroundColor: 'rgba(0, 0, 0, 0.08)',
+        }
+    },
+    childItemIcon: {
+        marginLeft: '8px',
+        marginRight: '16px',
+        color: 'rgba(0, 0, 0, 0.54)',
+    },
+    active: {
+        color: theme.palette.primary.main,
+    },
 });
 
 export enum TreeItemStatus {
@@ -77,6 +95,7 @@ export interface TreeItem<T> {
     open: boolean;
     active: boolean;
     selected?: boolean;
+    flatTree?: boolean;
     status: TreeItemStatus;
     items?: Array<TreeItem<T>>;
 }
@@ -103,11 +122,94 @@ export interface TreeProps<T> {
     useRadioButtons?: boolean;
 }
 
+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];
+};
+
+interface FlatTreeProps {
+    it: TreeItem<any>;
+    levelIndentation: number;
+    onContextMenu: Function;
+    handleToggleItemOpen: Function;
+    toggleItemActive: Function;
+    getToggableIconClassNames: Function;
+    getProperArrowAnimation: Function;
+    classes: any;
+}
+
+
+const FLAT_TREE_ACTIONS = {
+    toggleOpen: 'TOGGLE_OPEN',
+    contextMenu: 'CONTEXT_MENU',
+    toggleActive: 'TOGGLE_ACTIVE',
+};
+
+const FlatTree = (props: FlatTreeProps) =>
+    <div
+        onContextMenu={(event) => {
+            const [action, id] = getActionAndId(event, FLAT_TREE_ACTIONS.contextMenu);
+            props.onContextMenu(event, { id } as any);
+        }}
+        onClick={(event) => {
+            const [action, id] = getActionAndId(event);
+
+            if (action && id) {
+                switch (action) {
+                    case FLAT_TREE_ACTIONS.toggleOpen:
+                        props.handleToggleItemOpen({ id } as any, event);
+                        break;
+                    case FLAT_TREE_ACTIONS.toggleActive:
+                        props.toggleItemActive(event, { id } as any);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }}
+    >
+        {
+            (props.it.items || [])
+                .map((item: any) => <div key={item.id} data-id={item.id}
+                    className={classnames(props.classes.childItem, { [props.classes.active]: item.active })}
+                    style={{ paddingLeft: `${item.depth * props.levelIndentation}px` }}>
+                    <i data-action={FLAT_TREE_ACTIONS.toggleOpen} className={props.classes.toggableIconContainer}>
+                        <ListItemIcon className={props.getToggableIconClassNames(item.open, item.active)}>
+                            {props.getProperArrowAnimation(item.status, item.items!)}
+                        </ListItemIcon>
+                    </i>
+                    <div data-action={FLAT_TREE_ACTIONS.toggleActive} className={props.classes.renderContainer}>
+                        <span style={{ display: 'flex', alignItems: 'center' }}>
+                            <ProjectIcon className={classnames({ [props.classes.active]: item.active }, props.classes.childItemIcon)} />
+                            <span style={{ fontSize: '0.875rem' }}>
+                                {item.data.name}
+                            </span>
+                        </span>
+                    </div>
+                </div>)
+        }
+    </div>;
+
 export const Tree = withStyles(styles)(
     class Component<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
         render(): ReactElement<any> {
             const level = this.props.level ? this.props.level : 0;
-            const { classes, render, toggleItemOpen, items, toggleItemActive, onContextMenu, disableRipple, currentItemUuid, useRadioButtons } = this.props;
+            const { classes, render, items, toggleItemActive, toggleItemOpen, disableRipple, currentItemUuid, useRadioButtons } = this.props;
             const { list, listItem, loader, toggableIconContainer, renderContainer } = classes;
             const showSelection = typeof this.props.showSelection === 'function'
                 ? this.props.showSelection
@@ -117,7 +219,7 @@ export const Tree = withStyles(styles)(
 
             return <List className={list}>
                 {items && items.map((it: TreeItem<T>, idx: number) =>
-                    <div key={`item/${level}/${idx}`}>
+                    <div key={`item/${level}/${it.id}`}>
                         <ListItem button className={listItem}
                             style={{
                                 paddingLeft: (level + 1) * levelIndentation,
@@ -126,10 +228,10 @@ export const Tree = withStyles(styles)(
                             disableRipple={disableRipple}
                             onClick={event => toggleItemActive(event, it)}
                             selected={showSelection(it) && it.id === currentItemUuid}
-                            onContextMenu={this.handleRowContextMenu(it)}>
+                            onContextMenu={(event) => this.props.onContextMenu(event, it)}>
                             {it.status === TreeItemStatus.PENDING ?
                                 <CircularProgress size={10} className={loader} /> : null}
-                            <i onClick={this.handleToggleItemOpen(it)}
+                            <i onClick={(e) => this.handleToggleItemOpen(it, e)}
                                 className={toggableIconContainer}>
                                 <ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
                                     {this.getProperArrowAnimation(it.status, it.items!)}
@@ -150,19 +252,32 @@ export const Tree = withStyles(styles)(
                                 {render(it, level)}
                             </div>
                         </ListItem>
-                        {it.items && it.items.length > 0 &&
-                            <Collapse in={it.open} timeout="auto" unmountOnExit>
-                                <Tree
-                                    showSelection={this.props.showSelection}
-                                    items={it.items}
-                                    render={render}
-                                    disableRipple={disableRipple}
-                                    toggleItemOpen={toggleItemOpen}
-                                    toggleItemActive={toggleItemActive}
-                                    level={level + 1}
-                                    onContextMenu={onContextMenu}
-                                    toggleItemSelection={this.props.toggleItemSelection} />
-                            </Collapse>}
+                        {
+                            it.open && it.items && it.items.length > 0 &&
+                                it.flatTree ?
+                                    <FlatTree
+                                        it={it}
+                                        classes={this.props.classes}
+                                        levelIndentation={levelIndentation}
+                                        onContextMenu={this.props.onContextMenu}
+                                        handleToggleItemOpen={this.handleToggleItemOpen}
+                                        toggleItemActive={this.props.toggleItemActive}
+                                        getToggableIconClassNames={this.getToggableIconClassNames}
+                                        getProperArrowAnimation={this.getProperArrowAnimation}
+                                    /> :
+                                    <Collapse in={it.open} timeout="auto" unmountOnExit>
+                                        <Tree
+                                            showSelection={this.props.showSelection}
+                                            items={it.items}
+                                            render={render}
+                                            disableRipple={disableRipple}
+                                            toggleItemOpen={toggleItemOpen}
+                                            toggleItemActive={toggleItemActive}
+                                            level={level + 1}
+                                            onContextMenu={this.props.onContextMenu}
+                                            toggleItemSelection={this.props.toggleItemSelection} />
+                                    </Collapse>
+                        }
                     </div>)}
             </List>;
         }
@@ -186,10 +301,6 @@ export const Tree = withStyles(styles)(
             });
         }
 
-        handleRowContextMenu = (item: TreeItem<T>) =>
-            (event: React.MouseEvent<HTMLElement>) =>
-                this.props.onContextMenu(event, item)
-
         handleCheckboxChange = (item: TreeItem<T>) => {
             const { toggleItemSelection } = this.props;
             return toggleItemSelection
@@ -200,7 +311,7 @@ export const Tree = withStyles(styles)(
                 : undefined;
         }
 
-        handleToggleItemOpen = (item: TreeItem<T>) => (event: React.MouseEvent<HTMLElement>) => {
+        handleToggleItemOpen = (item: TreeItem<T>event: React.MouseEvent<HTMLElement>) => {
             event.stopPropagation();
             this.props.toggleItemOpen(event, item);
         }
index d96a3c837cb1db51948448c7205a0810b7d4b379..e0d9777da006aa317f9684eba4492ffd5934ff48 100644 (file)
@@ -52,7 +52,6 @@ const renderSidePanelItem = (item: TreeItem<ProjectResource>) => {
         nameDecorator={warn}
         isActive={item.active}
         hasMargin={true}
-        iconSize={1.25}
     />;
 };
 
index cb829059a5889f5e2872b9a95b602fa03c429cf9..c34b6c37eeb630fc63ca460a53b85c6cf058ba2d 100644 (file)
@@ -18,6 +18,18 @@ export interface TreePickerProps<T> {
     toggleItemSelection: Callback<T>;
 }
 
+const flatTree = (depth: number, items?: any): [] => {
+    return items ? items.reduce((prev: any, next: any) => {
+        const { items } = next;
+
+        return [
+            ...prev,
+            { ...next, depth },
+            ...(next.open ? flatTree(depth + 1, items) : []),
+        ];
+    }, []) : [];
+};
+
 const memoizedMapStateToProps = () => {
     let prevTree: Ttree<any>;
     let mappedProps: Pick<TreeProps<any>, 'items' | 'disableRipple'>;
@@ -29,6 +41,11 @@ const memoizedMapStateToProps = () => {
                 disableRipple: true,
                 items: getNodeChildrenIds('')(tree)
                     .map(treePickerToTreeItems(tree))
+                    .map(parentItem => ({
+                        ...parentItem,
+                        flatTree: true,
+                        items: flatTree(2, parentItem.items || []),
+                    }))
             };
         }
         return mappedProps;