[ARVADOS-WORKBENCH2] created: 2.3.0-48-g7ef76c77

Git user git at public.arvados.org
Thu Dec 2 23:02:26 UTC 2021


        at  7ef76c77956b9938322e23e8981bbf2cee2acff6 (commit)


commit 7ef76c77956b9938322e23e8981bbf2cee2acff6
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Dec 2 19:58:33 2021 -0300

    18128: Fixes triple negation.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index cf14303d..d3d26708 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -123,7 +123,7 @@ export const DataExplorer = withStyles(styles)(
                             columns={columns}
                             onColumnToggle={onColumnToggle} />}
                     </Grid>
-                    { doMaximizePanel && !!!panelMaximized &&
+                    { doMaximizePanel && !panelMaximized &&
                         <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
                             <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
                         </Tooltip> }

commit a9ff17bf2167122f7655cebb4d9587ab41527230
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Nov 22 11:17:12 2021 -0300

    18128: Fixes unit test due to behavior change.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.test.tsx b/src/components/multi-panel-view/multi-panel-view.test.tsx
index 53a3bb60..6cf13d78 100644
--- a/src/components/multi-panel-view/multi-panel-view.test.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.test.tsx
@@ -22,7 +22,7 @@ describe('<MPVContainer />', () => {
         };
     });
 
-    it('should show default toggle buttons for every child', () => {
+    it('should show default panel buttons for every child', () => {
         const childs = [
             <PanelMock key={1}>This is one panel</PanelMock>,
             <PanelMock key={2}>This is another panel</PanelMock>,
@@ -34,25 +34,27 @@ describe('<MPVContainer />', () => {
         expect(wrapper.html()).toContain('This is another panel');
     });
 
-    it('should toggle panel when clicking on its button', () => {
+    it('should show panel when clicking on its button', () => {
         const childs = [
             <PanelMock key={1}>This is one panel</PanelMock>,
         ];
-        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+        props.panelStates = [
+            {name: 'Initially invisible Panel', visible: false},
+        ]
 
-        // Initial state: panel visible
-        expect(wrapper.html()).toContain('This is one panel');
+        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
 
-        // Panel toggling
-        wrapper.find(Button).simulate('click');
+        // Initial state: panel not visible
         expect(wrapper.html()).not.toContain('This is one panel');
         expect(wrapper.html()).toContain('All panels are hidden');
+
+        // Panel visible when clicking on its button
         wrapper.find(Button).simulate('click');
         expect(wrapper.html()).toContain('This is one panel');
         expect(wrapper.html()).not.toContain('All panels are hidden');
     });
 
-    it('should show custom toggle buttons when config provided', () => {
+    it('should show custom panel buttons when config provided', () => {
         const childs = [
             <PanelMock key={1}>This is one panel</PanelMock>,
             <PanelMock key={2}>This is another panel</PanelMock>,

commit 10d22d0f1085a9a9b32ae1a5ea3427492b3706f8
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Nov 19 19:36:32 2021 -0300

    18128: Adds panel indication and auto-scroll on mouse hovering.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 35daa13c..dbb37921 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -2,8 +2,16 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React, { ReactElement, ReactNode, useState } from 'react';
-import { Button, Grid, StyleRulesCallback, Tooltip, withStyles, WithStyles } from "@material-ui/core";
+import React, { MutableRefObject, ReactElement, ReactNode, useEffect, useRef, useState } from 'react';
+import {
+    Button,
+    Grid,
+    Paper,
+    StyleRulesCallback,
+    Tooltip,
+    withStyles,
+    WithStyles
+} from "@material-ui/core";
 import { GridProps } from '@material-ui/core/Grid';
 import { isArray } from 'lodash';
 import { DefaultView } from 'components/default-view/default-view';
@@ -32,7 +40,9 @@ interface MPVHideablePanelDataProps {
     name: string;
     visible: boolean;
     maximized: boolean;
+    illuminated: boolean;
     children: ReactNode;
+    panelRef?: MutableRefObject<any>;
 }
 
 interface MPVHideablePanelActionProps {
@@ -42,16 +52,18 @@ interface MPVHideablePanelActionProps {
 
 type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
 
-const MPVHideablePanel = ({doHidePanel, doMaximizePanel, name, visible, maximized, ...props}: MPVHideablePanelProps) =>
+const MPVHideablePanel = ({doHidePanel, doMaximizePanel, name, visible, maximized, illuminated, ...props}: MPVHideablePanelProps) =>
     visible
     ? <>
-        {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel,panelName: name, panelMaximized: maximized })}
+        {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel, panelName: name, panelMaximized: maximized, panelIlluminated: illuminated, panelRef: props.panelRef })}
     </>
     : null;
 
 interface MPVPanelDataProps {
     panelName?: string;
     panelMaximized?: boolean;
+    panelIlluminated?: boolean;
+    panelRef?: MutableRefObject<any>;
 }
 
 interface MPVPanelActionProps {
@@ -65,10 +77,20 @@ export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps;
 type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps;
 
 // Grid item compatible component for layout and MPV props passing
-export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelMaximized, ...props}: MPVPanelContentProps) =>
-    <Grid item {...props}>
-        {React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })}
+export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelMaximized, panelIlluminated, panelRef, ...props}: MPVPanelContentProps) => {
+    useEffect(() => {
+        if (panelRef && panelRef.current) {
+            panelRef.current.scrollIntoView({behavior: 'smooth'});
+        }
+    }, [panelRef]);
+
+    return <Grid item {...props}>
+        <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
+        <Paper style={{height: '100%'}} elevation={panelIlluminated ? 8 : 0}>
+            {React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })}
+        </Paper>
     </Grid>;
+}
 
 export interface MPVPanelState {
     name: string;
@@ -91,16 +113,25 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             (panelStates[idx] &&
                 (panelStates[idx].visible || panelStates[idx].visible === undefined)));
     const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
+    const [brightenedPanel, setBrightenedPanel] = useState<number>(-1);
+    const panelRef = useRef<any>(null);
 
     let panels: JSX.Element[] = [];
     let toggles: JSX.Element[] = [];
 
     if (isArray(children)) {
         for (let idx = 0; idx < children.length; idx++) {
-            const toggleFn = (idx: number) => () => {
+            const showFn = (idx: number) => () => {
+                setPanelVisibility([
+                    ...panelVisibility.slice(0, idx),
+                    true,
+                    ...panelVisibility.slice(idx+1)
+                ]);
+            };
+            const hideFn = (idx: number) => () => {
                 setPanelVisibility([
                     ...panelVisibility.slice(0, idx),
-                    !panelVisibility[idx],
+                    false,
                     ...panelVisibility.slice(idx+1)
                 ])
             };
@@ -118,12 +149,10 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             const panelName = panelStates === undefined
                 ? `Panel ${idx+1}`
                 : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx+1}`;
-            const toggleVariant = panelVisibility[idx]
-                ? "contained"
-                : "text";
+            const toggleVariant = "outlined";
             const toggleTooltip = panelVisibility[idx]
-                ? `Hide ${panelName} panel`
-                : `Show ${panelName} panel`;
+                ? ''
+                :`Show ${panelName} panel`;
             const panelIsMaximized = panelVisibility[idx] &&
                 panelVisibility.filter(e => e).length === 1;
 
@@ -132,7 +161,9 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                 <Tooltip title={toggleTooltip} disableFocusListener>
                     <Button variant={toggleVariant} size="small" color="primary"
                         className={classNames(classes.button)}
-                        onClick={toggleFn(idx)}>
+                        onMouseEnter={() => setBrightenedPanel(idx)}
+                        onMouseLeave={() => setBrightenedPanel(-1)}
+                        onClick={showFn(idx)}>
                             {panelName}
                             {toggleIcon}
                     </Button>
@@ -141,8 +172,9 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
 
             const aPanel =
                 <MPVHideablePanel key={idx} visible={panelVisibility[idx]} name={panelName}
-                    maximized={panelIsMaximized}
-                    doHidePanel={toggleFn(idx)} doMaximizePanel={maximizeFn(idx)}>
+                    panelRef={(idx === brightenedPanel) ? panelRef : undefined}
+                    maximized={panelIsMaximized} illuminated={idx === brightenedPanel}
+                    doHidePanel={hideFn(idx)} doMaximizePanel={maximizeFn(idx)}>
                     {children[idx]}
                 </MPVHideablePanel>;
             panels = [...panels, aPanel];

commit 6d34334ffc69a75ffa638d1799ec942c588bc4d4
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Oct 20 17:12:38 2021 -0300

    18128: Make the properties panel of hidden by default.
    
    This is to make cypress tests pass, while maintaining previous behavior.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 17fe0992..794e093f 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -148,7 +148,7 @@ export const CollectionPanel = withStyles(styles)(
                 const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props;
                 const panelsData: MPVPanelState[] = [
                     {name: "Details"},
-                    {name: "Properties", visible: false},
+                    {name: "Properties"},
                     {name: "Files"},
                 ];
                 return item

commit fa64be63023b0b06ba616db56c6cef8d62e8a5c1
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Oct 20 16:24:39 2021 -0300

    18128: Improves collection's view different panels layout.
    
    There's additional work to be done here, specially with the file browser
    that doesn't seem to behave correctly when trying to make it occupy all
    the available vertical space.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index a7001a61..e33b7df0 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -48,7 +48,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     wrapper: {
         display: 'flex',
         minHeight: '600px',
-        marginBottom: '1rem',
         color: 'rgba(0, 0, 0, 0.87)',
         fontSize: '0.875rem',
         fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
@@ -490,7 +489,7 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
                                                         data-parent-path={name}
                                                         className={classNames(classes.row, getActiveClass(name))}
                                                         key={id}>
-                                                            {getItemIcon(type, getActiveClass(name))} 
+                                                            {getItemIcon(type, getActiveClass(name))}
                                                             <div className={classes.rowName}>
                                                                 {name}
                                                             </div>
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index bcf72edc..17fe0992 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -4,15 +4,20 @@
 
 import React from 'react';
 import {
-    StyleRulesCallback, WithStyles, withStyles,
-    IconButton, Grid, Tooltip, Typography, ExpansionPanel,
-    ExpansionPanelSummary, ExpansionPanelDetails
+    StyleRulesCallback,
+    WithStyles,
+    withStyles,
+    IconButton,
+    Grid,
+    Tooltip,
+    Typography,
+    Card, CardHeader, CardContent,
 } from '@material-ui/core';
 import { connect, DispatchProp } from "react-redux";
 import { RouteComponentProps } from 'react-router';
 import { ArvadosTheme } from 'common/custom-theme';
 import { RootState } from 'store/store';
-import { MoreOptionsIcon, CollectionIcon, ReadOnlyIcon, ExpandIcon, CollectionOldVersionIcon } from 'components/icon/icon';
+import { MoreOptionsIcon, CollectionIcon, ReadOnlyIcon, CollectionOldVersionIcon } from 'components/icon/icon';
 import { DetailsAttribute } from 'components/details-attribute/details-attribute';
 import { CollectionResource, getCollectionUrl } from 'models/collection';
 import { CollectionPanelFiles } from 'views-components/collection-panel-files/collection-panel-files';
@@ -37,6 +42,8 @@ import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-p
 
 type CssRules = 'root'
     | 'button'
+    | 'infoCard'
+    | 'propertiesCard'
     | 'filesCard'
     | 'iconHeader'
     | 'tag'
@@ -55,9 +62,16 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         cursor: 'pointer'
     },
+    infoCard: {
+        paddingLeft: theme.spacing.unit * 2,
+        paddingRight: theme.spacing.unit * 2,
+        paddingBottom: theme.spacing.unit * 2,
+    },
+    propertiesCard: {
+        padding: 0,
+    },
     filesCard: {
-        marginBottom: theme.spacing.unit * 2,
-        flex: 1,
+        padding: 0,
     },
     iconHeader: {
         fontSize: '1.875rem',
@@ -139,8 +153,8 @@ export const CollectionPanel = withStyles(styles)(
                 ];
                 return item
                     ? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
-                        <MPVPanelContent xs="auto"><ExpansionPanel data-cy='collection-info-panel' defaultExpanded>
-                            <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
+                        <MPVPanelContent xs="auto" data-cy='collection-info-panel'>
+                            <Card className={classes.infoCard}>
                                 <Grid container justify="space-between">
                                     <Grid item xs={11}><span>
                                         <IconButton onClick={this.openCollectionDetails}>
@@ -169,8 +183,6 @@ export const CollectionPanel = withStyles(styles)(
                                         </Tooltip>
                                     </Grid>
                                 </Grid>
-                            </ExpansionPanelSummary>
-                            <ExpansionPanelDetails>
                                 <Grid container justify="space-between">
                                     <Grid item xs={12}>
                                         <Typography variant="caption">
@@ -189,15 +201,12 @@ export const CollectionPanel = withStyles(styles)(
                                         }
                                     </Grid>
                                 </Grid>
-                            </ExpansionPanelDetails>
-                        </ExpansionPanel></MPVPanelContent>
-
-                        <MPVPanelContent xs="auto"><ExpansionPanel data-cy='collection-properties-panel' defaultExpanded>
-                            <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
-                                {"Properties"}
-                            </ExpansionPanelSummary>
-                            <ExpansionPanelDetails>
-                                <Grid container>
+                            </Card>
+                        </MPVPanelContent>
+                        <MPVPanelContent xs="auto" data-cy='collection-properties-panel'>
+                            <Card className={classes.propertiesCard}>
+                                <CardHeader title="Properties" />
+                                <CardContent><Grid container>
                                     {isWritable && <Grid item xs={12}>
                                         <CollectionTagForm />
                                     </Grid>}
@@ -222,19 +231,21 @@ export const CollectionPanel = withStyles(styles)(
                                             : <div className={classes.centeredLabel}>No properties set on this collection.</div>
                                         }
                                     </Grid>
-                                </Grid>
-                            </ExpansionPanelDetails>
-                        </ExpansionPanel></MPVPanelContent>
-                        <MPVPanelContent xs className={classes.filesCard}>
-                            <CollectionPanelFiles
-                                isWritable={isWritable}
-                                isLoading={isLoadingFiles}
-                                tooManyFiles={tooManyFiles}
-                                loadFilesFunc={() => {
-                                    dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true));
-                                    dispatch<any>(loadCollectionFiles(this.props.item.uuid));
-                                }
-                                } />
+                                </Grid></CardContent>
+                            </Card>
+                        </MPVPanelContent>
+                        <MPVPanelContent xs>
+                            <Card className={classes.filesCard}>
+                                <CollectionPanelFiles
+                                    isWritable={isWritable}
+                                    isLoading={isLoadingFiles}
+                                    tooManyFiles={tooManyFiles}
+                                    loadFilesFunc={() => {
+                                        dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true));
+                                        dispatch<any>(loadCollectionFiles(this.props.item.uuid));
+                                    }
+                                    } />
+                            </Card>
                         </MPVPanelContent>
                     </MPVContainer>
                     : null;

commit 892d0e98eeb20624cf49ca408af6156b4d3b0f55
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Oct 20 14:16:35 2021 -0300

    18128: Adds Multi-View Panel to collection's view.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index e78b1f3d..bcf72edc 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -33,6 +33,7 @@ import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD
 import { Link } from 'react-router-dom';
 import { Link as ButtonLink } from '@material-ui/core';
 import { ResourceOwnerWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
+import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 
 type CssRules = 'root'
     | 'button'
@@ -49,9 +50,7 @@ type CssRules = 'root'
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
-        display: 'flex',
-        flexFlow: 'column',
-        height: 'calc(100vh - 130px)', // (100% viewport height) - (top bar + breadcrumbs)
+        width: '100%',
     },
     button: {
         cursor: 'pointer'
@@ -133,9 +132,14 @@ export const CollectionPanel = withStyles(styles)(
         class extends React.Component<CollectionPanelProps> {
             render() {
                 const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props;
+                const panelsData: MPVPanelState[] = [
+                    {name: "Details"},
+                    {name: "Properties", visible: false},
+                    {name: "Files"},
+                ];
                 return item
-                    ? <div className={classes.root}>
-                        <ExpansionPanel data-cy='collection-info-panel' defaultExpanded>
+                    ? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
+                        <MPVPanelContent xs="auto"><ExpansionPanel data-cy='collection-info-panel' defaultExpanded>
                             <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
                                 <Grid container justify="space-between">
                                     <Grid item xs={11}><span>
@@ -186,9 +190,9 @@ export const CollectionPanel = withStyles(styles)(
                                     </Grid>
                                 </Grid>
                             </ExpansionPanelDetails>
-                        </ExpansionPanel>
+                        </ExpansionPanel></MPVPanelContent>
 
-                        <ExpansionPanel data-cy='collection-properties-panel' defaultExpanded>
+                        <MPVPanelContent xs="auto"><ExpansionPanel data-cy='collection-properties-panel' defaultExpanded>
                             <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
                                 {"Properties"}
                             </ExpansionPanelSummary>
@@ -220,8 +224,8 @@ export const CollectionPanel = withStyles(styles)(
                                     </Grid>
                                 </Grid>
                             </ExpansionPanelDetails>
-                        </ExpansionPanel>
-                        <div className={classes.filesCard}>
+                        </ExpansionPanel></MPVPanelContent>
+                        <MPVPanelContent xs className={classes.filesCard}>
                             <CollectionPanelFiles
                                 isWritable={isWritable}
                                 isLoading={isLoadingFiles}
@@ -231,8 +235,8 @@ export const CollectionPanel = withStyles(styles)(
                                     dispatch<any>(loadCollectionFiles(this.props.item.uuid));
                                 }
                                 } />
-                        </div>
-                    </div>
+                        </MPVPanelContent>
+                    </MPVContainer>
                     : null;
             }
 
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 6879d997..deb5f1b0 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -12,7 +12,6 @@ import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { ArvadosTheme } from 'common/custom-theme';
-import { ProcessLogPanel } from 'views/process-log-panel/process-log-panel';
 import { ProcessDetailsCard } from './process-details-card';
 
 type CssRules = 'root';

commit 9f8e9bf57453b23786c9f0deffe0c8a14e28c13c
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Wed Oct 20 14:05:02 2021 -0300

    18128: Removes WIP logs panel & hides details panel on process view.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index f30ebf70..6879d997 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -42,8 +42,7 @@ export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRoot
 
 const panelsData: MPVPanelState[] = [
     {name: "Info"},
-    {name: "Details"},
-    {name: "Logs", visible: false},
+    {name: "Details", visible: false},
     {name: "Subprocesses"},
 ];
 
@@ -63,9 +62,6 @@ export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: Proce
             <MPVPanelContent xs="auto">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
-            <MPVPanelContent xs="auto">
-                <ProcessLogPanel />
-            </MPVPanelContent>
             <MPVPanelContent xs>
                 <SubprocessPanel />
             </MPVPanelContent>

commit 8699b7818676e6b22707757989885b58060e06b3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 17:50:38 2021 -0300

    18128: Adds unit tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.test.tsx b/src/components/multi-panel-view/multi-panel-view.test.tsx
new file mode 100644
index 00000000..53a3bb60
--- /dev/null
+++ b/src/components/multi-panel-view/multi-panel-view.test.tsx
@@ -0,0 +1,85 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import { configure, mount } from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+import { MPVContainer } from './multi-panel-view';
+import { Button } from "@material-ui/core";
+
+configure({ adapter: new Adapter() });
+
+const PanelMock = ({panelName, panelMaximized, doHidePanel, doMaximizePanel, children, ...rest}) =>
+    <div {...rest}>{children}</div>;
+
+describe('<MPVContainer />', () => {
+    let props;
+
+    beforeEach(() => {
+        props = {
+            classes: {},
+        };
+    });
+
+    it('should show default toggle buttons for every child', () => {
+        const childs = [
+            <PanelMock key={1}>This is one panel</PanelMock>,
+            <PanelMock key={2}>This is another panel</PanelMock>,
+        ];
+        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+        expect(wrapper.find(Button).first().html()).toContain('Panel 1');
+        expect(wrapper.html()).toContain('This is one panel');
+        expect(wrapper.find(Button).last().html()).toContain('Panel 2');
+        expect(wrapper.html()).toContain('This is another panel');
+    });
+
+    it('should toggle panel when clicking on its button', () => {
+        const childs = [
+            <PanelMock key={1}>This is one panel</PanelMock>,
+        ];
+        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+
+        // Initial state: panel visible
+        expect(wrapper.html()).toContain('This is one panel');
+
+        // Panel toggling
+        wrapper.find(Button).simulate('click');
+        expect(wrapper.html()).not.toContain('This is one panel');
+        expect(wrapper.html()).toContain('All panels are hidden');
+        wrapper.find(Button).simulate('click');
+        expect(wrapper.html()).toContain('This is one panel');
+        expect(wrapper.html()).not.toContain('All panels are hidden');
+    });
+
+    it('should show custom toggle buttons when config provided', () => {
+        const childs = [
+            <PanelMock key={1}>This is one panel</PanelMock>,
+            <PanelMock key={2}>This is another panel</PanelMock>,
+        ];
+        props.panelStates = [
+            {name: 'First Panel'},
+        ]
+        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+        expect(wrapper.find(Button).first().html()).toContain('First Panel');
+        expect(wrapper.html()).toContain('This is one panel');
+        // Second panel received the default button naming and hidden status by default
+        expect(wrapper.find(Button).last().html()).toContain('Panel 2');
+        expect(wrapper.html()).not.toContain('This is another panel');
+        wrapper.find(Button).last().simulate('click');
+        expect(wrapper.html()).toContain('This is another panel');
+    });
+
+    it('should set panel hidden when requested', () => {
+        const childs = [
+            <PanelMock key={1}>This is one panel</PanelMock>,
+        ];
+        props.panelStates = [
+            {name: 'First Panel', visible: false},
+        ]
+        const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+        expect(wrapper.find(Button).html()).toContain('First Panel');
+        expect(wrapper.html()).not.toContain('This is one panel');
+        expect(wrapper.html()).toContain('All panels are hidden');
+    });
+});
\ No newline at end of file
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 6778b526..35daa13c 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -119,8 +119,8 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                 ? `Panel ${idx+1}`
                 : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx+1}`;
             const toggleVariant = panelVisibility[idx]
-                ? "raised"
-                : "flat";
+                ? "contained"
+                : "text";
             const toggleTooltip = panelVisibility[idx]
                 ? `Hide ${panelName} panel`
                 : `Show ${panelName} panel`;
@@ -140,7 +140,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             ];
 
             const aPanel =
-                <MPVHideablePanel visible={panelVisibility[idx]} name={panelName}
+                <MPVHideablePanel key={idx} visible={panelVisibility[idx]} name={panelName}
                     maximized={panelIsMaximized}
                     doHidePanel={toggleFn(idx)} doMaximizePanel={maximizeFn(idx)}>
                     {children[idx]}
@@ -151,7 +151,7 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
 
     return <Grid container {...props}>
         <Grid container item direction="row">
-            { toggles.map(tgl => <Grid item>{tgl}</Grid>) }
+            { toggles.map((tgl, idx) => <Grid item key={idx}>{tgl}</Grid>) }
         </Grid>
         <Grid container item {...props} xs className={classes.content}>
             { panelVisibility.includes(true)

commit 5eeb8bd77267293db601ba914fc09ef162057b33
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 16:23:21 2021 -0300

    18128: Adds logs panel to process view. WIP
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/store/process-logs-panel/process-logs-panel.ts b/src/store/process-logs-panel/process-logs-panel.ts
index deaaab6a..87b50bd2 100644
--- a/src/store/process-logs-panel/process-logs-panel.ts
+++ b/src/store/process-logs-panel/process-logs-panel.ts
@@ -1,9 +1,10 @@
-import { RootState } from '../store';
-import { matchProcessLogRoute } from 'routes/routes';
 // Copyright (C) The Arvados Authors. All rights reserved.
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { RootState } from '../store';
+import { matchProcessLogRoute, matchProcessRoute } from 'routes/routes';
+
 export interface ProcessLogsPanel {
     filters: string[];
     selectedFilter: string;
@@ -20,6 +21,6 @@ export const getProcessPanelLogs = ({ selectedFilter, logs }: ProcessLogsPanel)
 
 export const getProcessLogsPanelCurrentUuid = ({ router }: RootState) => {
     const pathname = router.location ? router.location.pathname : '';
-    const match = matchProcessLogRoute(pathname);
+    const match = matchProcessLogRoute(pathname) || matchProcessRoute(pathname);
     return match ? match.params.id : undefined;
 };
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index e604fe2b..f30ebf70 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -12,6 +12,8 @@ import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { ArvadosTheme } from 'common/custom-theme';
+import { ProcessLogPanel } from 'views/process-log-panel/process-log-panel';
+import { ProcessDetailsCard } from './process-details-card';
 
 type CssRules = 'root';
 
@@ -40,6 +42,8 @@ export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRoot
 
 const panelsData: MPVPanelState[] = [
     {name: "Info"},
+    {name: "Details"},
+    {name: "Logs", visible: false},
     {name: "Subprocesses"},
 ];
 
@@ -56,6 +60,12 @@ export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: Proce
                     cancelProcess={props.cancelProcess}
                 />
             </MPVPanelContent>
+            <MPVPanelContent xs="auto">
+                <ProcessDetailsCard process={process} />
+            </MPVPanelContent>
+            <MPVPanelContent xs="auto">
+                <ProcessLogPanel />
+            </MPVPanelContent>
             <MPVPanelContent xs>
                 <SubprocessPanel />
             </MPVPanelContent>

commit 0f433d72c1af64a6359478da2edb8c9f589d579b
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 16:20:32 2021 -0300

    18128: Separate process details into their own component.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views-components/details-panel/process-details.tsx b/src/views-components/details-panel/process-details.tsx
index c4b374b9..d9c991f5 100644
--- a/src/views-components/details-panel/process-details.tsx
+++ b/src/views-components/details-panel/process-details.tsx
@@ -5,12 +5,8 @@
 import React from 'react';
 import { ProcessIcon } from 'components/icon/icon';
 import { ProcessResource } from 'models/process';
-import { formatDate } from 'common/formatters';
-import { ResourceKind } from 'models/resource';
-import { resourceLabel } from 'common/labels';
 import { DetailsData } from "./details-data";
-import { DetailsAttribute } from "components/details-attribute/details-attribute";
-import { ResourceOwnerWithName } from '../data-explorer/renderers';
+import { ProcessDetailsAttributes } from 'views/process-panel/process-details-attributes';
 
 export class ProcessDetails extends DetailsData<ProcessResource> {
 
@@ -19,25 +15,6 @@ export class ProcessDetails extends DetailsData<ProcessResource> {
     }
 
     getDetails() {
-        return <div>
-            <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
-            <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid}
-                uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
-
-            <DetailsAttribute label='Status' value={this.item.state} />
-            <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
-
-            <DetailsAttribute label='Started at' value={formatDate(this.item.createdAt)} />
-            <DetailsAttribute label='Finished at' value={formatDate(this.item.expiresAt)} />
-
-            <DetailsAttribute label='Outputs' value={this.item.outputPath} />
-            <DetailsAttribute label='UUID' linkToUuid={this.item.uuid} value={this.item.uuid} />
-            <DetailsAttribute label='Container UUID' value={this.item.containerUuid} />
-
-            <DetailsAttribute label='Priority' value={this.item.priority} />
-            <DetailsAttribute label='Runtime Constraints' value={JSON.stringify(this.item.runtimeConstraints)} />
-
-            <DetailsAttribute label='Docker Image locator' linkToUuid={this.item.containerImage} value={this.item.containerImage} />
-        </div>;
+        return <ProcessDetailsAttributes item={this.item} />;
     }
 }
diff --git a/src/views/process-panel/process-details-attributes.tsx b/src/views/process-panel/process-details-attributes.tsx
new file mode 100644
index 00000000..4f26a71f
--- /dev/null
+++ b/src/views/process-panel/process-details-attributes.tsx
@@ -0,0 +1,65 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import { Grid } from "@material-ui/core";
+import { formatDate } from "common/formatters";
+import { resourceLabel } from "common/labels";
+import { DetailsAttribute } from "components/details-attribute/details-attribute";
+import { ProcessResource } from "models/process";
+import { ResourceKind } from "models/resource";
+import { ResourceOwnerWithName } from "views-components/data-explorer/renderers";
+
+type CssRules = 'label' | 'value';
+
+export const ProcessDetailsAttributes = (props: { item: ProcessResource, twoCol?: boolean, classes?: Record<CssRules, string> }) => {
+    const item = props.item;
+    const classes = props.classes || { label: '', value: '', button: '' };
+    const mdSize = props.twoCol ? 6 : 12;
+    return <Grid container>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                label='Owner' linkToUuid={item.ownerUuid}
+                uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+        </Grid>
+        <Grid item xs={12} md={12}>
+            <DetailsAttribute label='Status' value={item.state} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Last modified' value={formatDate(item.modifiedAt)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Started at' value={formatDate(item.createdAt)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Created at' value={formatDate(item.createdAt)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Finished at' value={formatDate(item.expiresAt)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Outputs' value={item.outputPath} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='UUID' linkToUuid={item.uuid} value={item.uuid} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Container UUID' value={item.containerUuid} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Priority' value={item.priority} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Runtime Constraints'
+            value={JSON.stringify(item.runtimeConstraints)} />
+        </Grid>
+        <Grid item xs={12} md={mdSize}>
+            <DetailsAttribute label='Docker Image locator'
+            linkToUuid={item.containerImage} value={item.containerImage} />
+        </Grid>
+    </Grid>;
+};
diff --git a/src/views/process-panel/process-details-card.tsx b/src/views/process-panel/process-details-card.tsx
new file mode 100644
index 00000000..18610781
--- /dev/null
+++ b/src/views/process-panel/process-details-card.tsx
@@ -0,0 +1,63 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import {
+    StyleRulesCallback,
+    WithStyles,
+    withStyles,
+    Card,
+    CardHeader,
+    IconButton,
+    CardContent,
+    Tooltip,
+} from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import { CloseIcon } from 'components/icon/icon';
+import { Process } from 'store/processes/process';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
+import { ProcessDetailsAttributes } from './process-details-attributes';
+
+type CssRules = 'card' | 'content' | 'title';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    card: {
+        height: '100%'
+    },
+    content: {
+        '&:last-child': {
+            paddingBottom: theme.spacing.unit * 2,
+        }
+    },
+    title: {
+        overflow: 'hidden',
+        paddingTop: theme.spacing.unit * 0.5
+    },
+});
+
+export interface ProcessDetailsCardDataProps {
+    process: Process;
+}
+
+type ProcessDetailsCardProps = ProcessDetailsCardDataProps & WithStyles<CssRules> & MPVPanelProps;
+
+export const ProcessDetailsCard = withStyles(styles)(
+    ({ classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => {
+        return <Card className={classes.card}>
+            <CardHeader
+                classes={{
+                    content: classes.title,
+                }}
+                title='Details'
+                action={ doHidePanel &&
+                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+                        </Tooltip> } />
+            <CardContent className={classes.content}>
+                <ProcessDetailsAttributes item={process.containerRequest} twoCol />
+            </CardContent>
+        </Card>;
+    }
+);
+

commit b4b78a306a93fc566ce9a442d8008beaff81cb64
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 13:49:14 2021 -0300

    18128: Makes the toggle button bar always visible.
    
    In case there's vertical scrolling involved, the button bar will always
    be accessible.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index e0698750..6778b526 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -11,7 +11,7 @@ import { InfoIcon, InvisibleIcon, VisibleIcon } from 'components/icon/icon';
 import { ReactNodeArray } from 'prop-types';
 import classNames from 'classnames';
 
-type CssRules = 'button' | 'buttonIcon';
+type CssRules = 'button' | 'buttonIcon' | 'content';
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -23,6 +23,9 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
         padding: '2px 0px 2px 5px',
         fontSize: '1rem'
     },
+    content: {
+        overflow: 'auto',
+    },
 });
 
 interface MPVHideablePanelDataProps {
@@ -150,11 +153,13 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
         <Grid container item direction="row">
             { toggles.map(tgl => <Grid item>{tgl}</Grid>) }
         </Grid>
-        { panelVisibility.includes(true)
-            ? panels
-            : <Grid container item alignItems='center' justify='center'>
-                <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
-            </Grid> }
+        <Grid container item {...props} xs className={classes.content}>
+            { panelVisibility.includes(true)
+                ? panels
+                : <Grid container item alignItems='center' justify='center'>
+                    <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
+                </Grid> }
+        </Grid>
     </Grid>;
 };
 

commit 38c87aec8a898f4d1c180be6a7554523aeadcb83
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 11:58:09 2021 -0300

    18128: Improves process panel.
    
    Makes the subprocess panel take the available screen space.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 05c1de05..e0698750 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -147,12 +147,12 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
     };
 
     return <Grid container {...props}>
-        <Grid item>
-            { toggles }
+        <Grid container item direction="row">
+            { toggles.map(tgl => <Grid item>{tgl}</Grid>) }
         </Grid>
         { panelVisibility.includes(true)
             ? panels
-            : <Grid container alignItems='center' justify='center'>
+            : <Grid container item alignItems='center' justify='center'>
                 <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
             </Grid> }
     </Grid>;
diff --git a/src/views/all-processes-panel/all-processes-panel.tsx b/src/views/all-processes-panel/all-processes-panel.tsx
index f9fab44d..928b4fff 100644
--- a/src/views/all-processes-panel/all-processes-panel.tsx
+++ b/src/views/all-processes-panel/all-processes-panel.tsx
@@ -33,7 +33,7 @@ import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from 'st
 import { getProcess } from 'store/processes/process';
 import { ResourcesState } from 'store/resources/resources';
 
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
@@ -43,6 +43,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginLeft: theme.spacing.unit
     },
+    root: {
+        width: '100%',
+    }
 });
 
 export enum AllProcessesPanelColumnNames {
@@ -142,18 +145,17 @@ export const AllProcessesPanel = withStyles(styles)(
             }
 
             render() {
-                return <DataExplorer
+                return <div className={this.props.classes.root}><DataExplorer
                     id={ALL_PROCESSES_PANEL_ID}
                     onRowClick={this.handleRowClick}
                     onRowDoubleClick={this.handleRowDoubleClick}
                     onContextMenu={this.handleContextMenu}
                     contextMenuColumn={true}
-                    dataTableDefaultView={
-                        <DataTableDefaultView
-                            icon={ProcessIcon}
-                            messages={['Processes list empty.']}
-                            />
-                    } />;
+                    dataTableDefaultView={ <DataTableDefaultView
+                        icon={ProcessIcon}
+                        messages={['Processes list empty.']}
+                        /> } />
+                </div>
             }
         }
     )
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index e2854bdd..e604fe2b 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { Grid } from '@material-ui/core';
+import { Grid, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 import { ProcessInformationCard } from './process-information-card';
 import { DefaultView } from 'components/default-view/default-view';
 import { ProcessIcon } from 'components/icon/icon';
@@ -11,6 +11,15 @@ import { Process } from 'store/processes/process';
 import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
+import { ArvadosTheme } from 'common/custom-theme';
+
+type CssRules = 'root';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        width: '100%',
+    },
+});
 
 export interface ProcessPanelRootDataProps {
     process?: Process;
@@ -27,17 +36,17 @@ export interface ProcessPanelRootActionProps {
     cancelProcess: (uuid: string) => void;
 }
 
-export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
+export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
 
 const panelsData: MPVPanelState[] = [
     {name: "Info"},
     {name: "Subprocesses"},
 ];
 
-export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =>
+export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: ProcessPanelRootProps) =>
     process
-        ? <MPVContainer spacing={8} panelStates={panelsData} alignItems="stretch">
-            <MPVPanelContent sm={12} md={12}>
+        ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
+            <MPVPanelContent xs="auto">
                 <ProcessInformationCard
                     process={process}
                     onContextMenu={event => props.onContextMenu(event, process)}
@@ -47,7 +56,7 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =
                     cancelProcess={props.cancelProcess}
                 />
             </MPVPanelContent>
-            <MPVPanelContent sm={12} md={12}>
+            <MPVPanelContent xs>
                 <SubprocessPanel />
             </MPVPanelContent>
         </MPVContainer>
@@ -58,5 +67,5 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =
             <DefaultView
                 icon={ProcessIcon}
                 messages={['Process not found']} />
-        </Grid>;
+        </Grid>);
 

commit 770ffecbecc120d200bebaaf4606dfc055c64008
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Tue Oct 19 11:56:07 2021 -0300

    18128: Fixes app's main content layouts.
    
    Moves the height calculation from the DataExplorer/DataTable component combo
    to the outer layers so that DataExplorer can be used as one of many data
    panels inside a view.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 7fce77de..cf14303d 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -15,7 +15,7 @@ import { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
 import { PaperProps } from '@material-ui/core/Paper';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
-type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
+type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title' | 'dataTable' | 'container';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     searchBox: {
@@ -32,7 +32,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         overflow: 'auto'
     },
     root: {
-        height: '100%'
+        height: '100%',
     },
     moreOptionsButton: {
         padding: 0
@@ -41,7 +41,14 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         paddingLeft: theme.spacing.unit * 3,
         paddingTop: theme.spacing.unit * 3,
         fontSize: '18px'
-    }
+    },
+    dataTable: {
+        height: '100%',
+        overflow: 'auto',
+    },
+    container: {
+        height: '100%',
+    },
 });
 
 interface DataExplorerDataProps<T> {
@@ -101,8 +108,9 @@ export const DataExplorer = withStyles(styles)(
                 doHidePanel, doMaximizePanel, panelName, panelMaximized
             } = this.props;
             return <Paper className={classes.root} {...paperProps} key={paperKey}>
-                {title && <div className={classes.title}>{title}</div>}
-                {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
+                <Grid container direction="column" wrap="nowrap" className={classes.container}>
+                {title && <Grid item xs className={classes.title}>{title}</Grid>}
+                {(!hideColumnSelector || !hideSearchInput) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
                         <div className={classes.searchBox}>
                             {!hideSearchInput && <SearchInput
@@ -123,8 +131,8 @@ export const DataExplorer = withStyles(styles)(
                         <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
                             <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
                         </Tooltip> }
-                </Toolbar>}
-                <DataTable
+                </Toolbar></Grid>}
+                <Grid item xs="auto" className={classes.dataTable}><DataTable
                     columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
                     items={items}
                     onRowClick={(_, item: T) => onRowClick(item)}
@@ -136,8 +144,8 @@ export const DataExplorer = withStyles(styles)(
                     working={working}
                     defaultView={dataTableDefaultView}
                     currentItemUuid={currentItemUuid}
-                    currentRoute={paperKey} />
-                <Toolbar className={classes.footer}>
+                    currentRoute={paperKey} /></Grid>
+                <Grid item xs><Toolbar className={classes.footer}>
                     <Grid container justify="flex-end">
                         {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
                             count={itemsAvailable}
@@ -154,7 +162,8 @@ export const DataExplorer = withStyles(styles)(
                                 onClick={this.loadMore}
                             >Load more</Button>}
                     </Grid>
-                </Toolbar>
+                </Toolbar></Grid>
+                </Grid>
             </Paper>;
         }
 
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index 0c84f642..de52d365 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -39,13 +39,11 @@ type CssRules = "tableBody" | "root" | "content" | "noItemsInfo" | 'tableCell' |
 
 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     root: {
-        overflowX: 'auto',
-        overflowY: 'auto',
-        height: 'calc(100vh - 280px)',
+        width: '100%',
     },
     content: {
         display: 'inline-block',
-        width: '100%'
+        width: '100%',
     },
     tableBody: {
         background: theme.palette.background.paper
diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
index 703bbec5..8f87cb26 100644
--- a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
+++ b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx
@@ -4,10 +4,10 @@
 
 import React from 'react';
 import {
-    StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Grid, Tooltip, IconButton
+    StyleRulesCallback, WithStyles, withStyles
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
-import { HelpIcon, ShareMeIcon } from 'components/icon/icon';
+import { ShareMeIcon } from 'components/icon/icon';
 import { createTree } from 'models/tree';
 import { DataColumns } from 'components/data-table/data-table';
 import { SortDirection } from 'components/data-table/data-column';
@@ -20,21 +20,11 @@ import {
     TokenLastUsedAt, TokenLastUsedByIpAddress, TokenScopes, TokenUserId
 } from 'views-components/data-explorer/renderers';
 
-type CssRules = 'card' | 'cardContent' | 'helpIconGrid';
+type CssRules = 'root';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    card: {
+    root: {
         width: '100%',
-        overflow: 'auto'
-    },
-    cardContent: {
-        padding: 0,
-        '&:last-child': {
-            paddingBottom: 0
-        }
-    },
-    helpIconGrid: {
-        textAlign: 'right'
     }
 });
 
@@ -132,7 +122,6 @@ export interface ApiClientAuthorizationPanelRootActionProps {
     onItemClick: (item: string) => void;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
     onItemDoubleClick: (item: string) => void;
-    openHelpDialog: () => void;
 }
 
 export interface ApiClientAuthorizationPanelRootDataProps {
@@ -143,33 +132,18 @@ type ApiClientAuthorizationPanelRootProps = ApiClientAuthorizationPanelRootActio
     & ApiClientAuthorizationPanelRootDataProps & WithStyles<CssRules>;
 
 export const ApiClientAuthorizationPanelRoot = withStyles(styles)(
-    ({ classes, onItemDoubleClick, onItemClick, onContextMenu, openHelpDialog }: ApiClientAuthorizationPanelRootProps) =>
-        <Card className={classes.card}>
-            <CardContent className={classes.cardContent}>
-                <Grid container direction="row" justify="flex-end">
-                    <Grid item xs={12} className={classes.helpIconGrid}>
-                        <Tooltip title="Api token - help">
-                            <IconButton onClick={openHelpDialog}>
-                                <HelpIcon />
-                            </IconButton>
-                        </Tooltip>
-                    </Grid>
-                    <Grid item xs={12}>
-                        <DataExplorer
-                            id={API_CLIENT_AUTHORIZATION_PANEL_ID}
-                            onRowClick={onItemClick}
-                            onRowDoubleClick={onItemDoubleClick}
-                            onContextMenu={onContextMenu}
-                            contextMenuColumn={true}
-                            hideColumnSelector
-                            hideSearchInput
-                            dataTableDefaultView={
-                                <DataTableDefaultView
-                                    icon={ShareMeIcon}
-                                    messages={[DEFAULT_MESSAGE]} />
-                            } />
-                    </Grid>
-                </Grid>
-            </CardContent>
-        </Card>
+    ({ classes, onItemDoubleClick, onItemClick, onContextMenu }: ApiClientAuthorizationPanelRootProps) =>
+        <div className={classes.root}><DataExplorer
+            id={API_CLIENT_AUTHORIZATION_PANEL_ID}
+            onRowClick={onItemClick}
+            onRowDoubleClick={onItemDoubleClick}
+            onContextMenu={onContextMenu}
+            contextMenuColumn={true}
+            hideColumnSelector
+            hideSearchInput
+            dataTableDefaultView={
+                <DataTableDefaultView
+                    icon={ShareMeIcon}
+                    messages={[DEFAULT_MESSAGE]} />
+            } /></div>
 );
\ No newline at end of file
diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
index 89254dcc..9604bf50 100644
--- a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
+++ b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx
@@ -11,7 +11,6 @@ import {
     ApiClientAuthorizationPanelRootActionProps
 } from 'views/api-client-authorization-panel/api-client-authorization-panel-root';
 import { openApiClientAuthorizationContextMenu } from 'store/context-menu/context-menu-actions';
-import { openApiClientAuthorizationsHelpDialog } from 'store/api-client-authorizations/api-client-authorizations-actions';
 
 const mapStateToProps = (state: RootState): ApiClientAuthorizationPanelRootDataProps => {
     return {
@@ -25,9 +24,6 @@ const mapDispatchToProps = (dispatch: Dispatch): ApiClientAuthorizationPanelRoot
     },
     onItemClick: (resourceUuid: string) => { return; },
     onItemDoubleClick: uuid => { return; },
-    openHelpDialog: () => {
-        dispatch<any>(openApiClientAuthorizationsHelpDialog());
-    }
 });
 
 export const ApiClientAuthorizationPanel = connect(mapStateToProps, mapDispatchToProps)(ApiClientAuthorizationPanelRoot);
\ No newline at end of file
diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
index 88638085..f1278049 100644
--- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx
+++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
@@ -7,7 +7,6 @@ import {
     StyleRulesCallback,
     WithStyles,
     withStyles,
-    Grid,
     Button
 } from '@material-ui/core';
 import { CollectionIcon } from 'components/icon/icon';
@@ -38,7 +37,7 @@ import { getResource, ResourcesState } from 'store/resources/resources';
 import { RootState } from 'store/store';
 import { CollectionResource } from 'models/collection';
 
-type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
+type CssRules = 'backLink' | 'backIcon' | 'root' | 'content';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     backLink: {
@@ -53,24 +52,13 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     backIcon: {
         marginRight: theme.spacing.unit
     },
-    card: {
-        width: '100%'
+    root: {
+        width: '100%',
     },
-    title: {
-        color: theme.palette.grey["700"]
+    content: {
+        // reserve space for the content address bar
+        height: `calc(100% - ${theme.spacing.unit * 7}px)`,
     },
-    iconHeader: {
-        fontSize: '1.875rem',
-        color: theme.customs.colors.green700
-    },
-    link: {
-        fontSize: '0.875rem',
-        color: theme.palette.primary.main,
-        textAlign: 'right',
-        '&:hover': {
-            cursor: 'pointer'
-        }
-    }
 });
 
 enum CollectionContentAddressPanelColumnNames {
@@ -162,14 +150,14 @@ export const CollectionsContentAddressPanel = withStyles(styles)(
     connect(mapStateToProps, mapDispatchToProps)(
         class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressPanelDataProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
             render() {
-                return <Grid item xs={12}>
+                return <div className={this.props.classes.root}>
                     <Button
                         onClick={() => window.history.back()}
                         className={this.props.classes.backLink}>
                         <BackIcon className={this.props.classes.backIcon} />
                         Back
                     </Button>
-                    <DataExplorer
+                    <div className={this.props.classes.content}><DataExplorer
                         id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
                         hideSearchInput
                         onRowClick={this.props.onItemClick}
@@ -181,8 +169,8 @@ export const CollectionsContentAddressPanel = withStyles(styles)(
                             <DataTableDefaultView
                                 icon={CollectionIcon}
                                 messages={['Collections with this content address not found.']} />
-                        } />;
-                    </Grid >;
+                        } /></div>
+                    </div>;
             }
         }
     )
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 404baeb9..0b6532c1 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -41,7 +41,7 @@ import { getProperty } from 'store/properties/properties';
 import { PROJECT_PANEL_CURRENT_UUID } from 'store/project-panel/project-panel-action';
 import { CollectionResource } from 'models/collection';
 
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
@@ -51,6 +51,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginLeft: theme.spacing.unit
     },
+    root: {
+        width: '100%',
+    },
 });
 
 export enum FavoritePanelColumnNames {
@@ -176,7 +179,7 @@ export const FavoritePanel = withStyles(styles)(
             }
 
             render() {
-                return <DataExplorer
+                return <div className={this.props.classes.root}><DataExplorer
                     id={FAVORITE_PANEL_ID}
                     onRowClick={this.handleRowClick}
                     onRowDoubleClick={this.handleRowDoubleClick}
@@ -187,7 +190,7 @@ export const FavoritePanel = withStyles(styles)(
                             icon={FavoriteIcon}
                             messages={['Your favorites list is empty.']}
                             />
-                    } />;
+                    } /></div>;
             }
         }
     )
diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx
index 4d15118c..faefab10 100644
--- a/src/views/groups-panel/groups-panel.tsx
+++ b/src/views/groups-panel/groups-panel.tsx
@@ -4,7 +4,7 @@
 
 import React from 'react';
 import { connect } from 'react-redux';
-import { Grid, Button, Typography } from "@material-ui/core";
+import { Grid, Button, Typography, StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
 import { DataExplorer } from "views-components/data-explorer/data-explorer";
 import { DataColumns } from 'components/data-table/data-table';
 import { SortDirection } from 'components/data-table/data-column';
@@ -22,6 +22,15 @@ import { openContextMenu } from 'store/context-menu/context-menu-actions';
 import { ResourceKind } from 'models/resource';
 import { LinkClass, LinkResource } from 'models/link';
 import { navigateToGroupDetails } from 'store/navigation/navigation-action';
+import { ArvadosTheme } from 'common/custom-theme';
+
+type CssRules = "root";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        width: '100%',
+    }
+});
 
 export enum GroupsPanelColumnNames {
     GROUP = "Name",
@@ -74,14 +83,14 @@ export interface GroupsPanelProps {
     resources: ResourcesState;
 }
 
-export const GroupsPanel = connect(
+export const GroupsPanel = withStyles(styles)(connect(
     mapStateToProps, mapDispatchToProps
 )(
-    class GroupsPanel extends React.Component<GroupsPanelProps> {
+    class GroupsPanel extends React.Component<GroupsPanelProps & WithStyles<CssRules>> {
 
         render() {
             return (
-                <DataExplorer
+                <div className={this.props.classes.root}><DataExplorer
                     id={GROUPS_PANEL_ID}
                     onRowClick={noop}
                     onRowDoubleClick={this.props.onRowDoubleClick}
@@ -97,7 +106,7 @@ export const GroupsPanel = connect(
                                 <AddIcon /> New group
                         </Button>
                         </Grid>
-                    } />
+                    } /></div>
             );
         }
 
@@ -113,7 +122,7 @@ export const GroupsPanel = connect(
                 });
             }
         }
-    });
+    }));
 
 
 const GroupMembersCount = connect(
diff --git a/src/views/link-panel/link-panel-root.tsx b/src/views/link-panel/link-panel-root.tsx
index 7a5f0503..b32208cd 100644
--- a/src/views/link-panel/link-panel-root.tsx
+++ b/src/views/link-panel/link-panel-root.tsx
@@ -11,10 +11,20 @@ import { DataTableDefaultView } from 'components/data-table-default-view/data-ta
 import { ResourcesState } from 'store/resources/resources';
 import { ShareMeIcon } from 'components/icon/icon';
 import { createTree } from 'models/tree';
-import { 
-    ResourceLinkUuid, ResourceLinkHead, ResourceLinkTail, 
-    ResourceLinkClass, ResourceLinkName } 
+import {
+    ResourceLinkUuid, ResourceLinkHead, ResourceLinkTail,
+    ResourceLinkClass, ResourceLinkName }
 from 'views-components/data-explorer/renderers';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+
+type CssRules = "root";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        width: '100%',
+    }
+});
 
 export enum LinkPanelColumnNames {
     NAME = "Name",
@@ -73,20 +83,20 @@ export interface LinkPanelRootActionProps {
     onItemDoubleClick: (item: string) => void;
 }
 
-export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps;
+export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps & WithStyles<CssRules>;
 
-export const LinkPanelRoot = (props: LinkPanelRootProps) => {
-    return <DataExplorer
+export const LinkPanelRoot = withStyles(styles)((props: LinkPanelRootProps) => {
+    return <div className={props.classes.root}><DataExplorer
         id={LINK_PANEL_ID}
         onRowClick={props.onItemClick}
         onRowDoubleClick={props.onItemDoubleClick}
         onContextMenu={props.onContextMenu}
-        contextMenuColumn={true} 
+        contextMenuColumn={true}
         hideColumnSelector
         hideSearchInput
         dataTableDefaultView={
             <DataTableDefaultView
                 icon={ShareMeIcon}
                 messages={['Your link list is empty.']} />
-        }/>;
-};
\ No newline at end of file
+        }/></div>;
+});
\ No newline at end of file
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 67264511..97f79517 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -51,9 +51,7 @@ type CssRules = 'root' | "button";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
-        position: 'relative',
         width: '100%',
-        height: '100%'
     },
     button: {
         marginLeft: theme.spacing.unit
diff --git a/src/views/public-favorites-panel/public-favorites-panel.tsx b/src/views/public-favorites-panel/public-favorites-panel.tsx
index ee09654a..b58aa2f0 100644
--- a/src/views/public-favorites-panel/public-favorites-panel.tsx
+++ b/src/views/public-favorites-panel/public-favorites-panel.tsx
@@ -39,7 +39,7 @@ import { getResource, ResourcesState } from 'store/resources/resources';
 import { GroupContentsResource } from 'services/groups-service/groups-service';
 import { CollectionResource } from 'models/collection';
 
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
@@ -49,6 +49,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginLeft: theme.spacing.unit
     },
+    root: {
+        width: '100%',
+    },
 });
 
 export enum PublicFavoritePanelColumnNames {
@@ -160,7 +163,7 @@ export const PublicFavoritePanel = withStyles(styles)(
     connect(mapStateToProps, mapDispatchToProps)(
         class extends React.Component<FavoritePanelProps> {
             render() {
-                return <DataExplorer
+                return <div className={this.props.classes.root}><DataExplorer
                     id={PUBLIC_FAVORITE_PANEL_ID}
                     onRowClick={this.props.onItemClick}
                     onRowDoubleClick={this.props.onItemDoubleClick}
@@ -170,7 +173,7 @@ export const PublicFavoritePanel = withStyles(styles)(
                         <DataTableDefaultView
                             icon={PublicFavoriteIcon}
                             messages={['Public favorites list is empty.']} />
-                    } />;
+                    } /></div>;
             }
         }
     )
diff --git a/src/views/shared-with-me-panel/shared-with-me-panel.tsx b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
index eb3127a7..219410c5 100644
--- a/src/views/shared-with-me-panel/shared-with-me-panel.tsx
+++ b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
@@ -20,7 +20,7 @@ import {
 } from 'store/context-menu/context-menu-actions';
 import { GroupContentsResource } from 'services/groups-service/groups-service';
 
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
@@ -30,6 +30,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginLeft: theme.spacing.unit
     },
+    root: {
+        width: '100%',
+    },
 });
 
 interface SharedWithMePanelDataProps {
@@ -46,13 +49,13 @@ export const SharedWithMePanel = withStyles(styles)(
     }))(
         class extends React.Component<SharedWithMePanelProps> {
             render() {
-                return <DataExplorer
+                return <div className={this.props.classes.root}><DataExplorer
                     id={SHARED_WITH_ME_PANEL_ID}
                     onRowClick={this.handleRowClick}
                     onRowDoubleClick={this.handleRowDoubleClick}
                     onContextMenu={this.handleContextMenu}
                     contextMenuColumn={false}
-                    dataTableDefaultView={<DataTableDefaultView icon={ShareMeIcon} />} />;
+                    dataTableDefaultView={<DataTableDefaultView icon={ShareMeIcon} />} /></div>;
             }
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
diff --git a/src/views/trash-panel/trash-panel.tsx b/src/views/trash-panel/trash-panel.tsx
index b67b666c..d303c2f7 100644
--- a/src/views/trash-panel/trash-panel.tsx
+++ b/src/views/trash-panel/trash-panel.tsx
@@ -36,7 +36,7 @@ import {
     getTrashPanelTypeFilters
 } from 'store/resource-type-filters/resource-type-filters';
 
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
@@ -46,6 +46,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginLeft: theme.spacing.unit
     },
+    root: {
+        width: '100%',
+    },
 });
 
 export enum TrashPanelColumnNames {
@@ -146,7 +149,7 @@ export const TrashPanel = withStyles(styles)(
     }))(
         class extends React.Component<TrashPanelProps> {
             render() {
-                return <DataExplorer
+                return <div className={this.props.classes.root}><DataExplorer
                     id={TRASH_PANEL_ID}
                     onRowClick={this.handleRowClick}
                     onRowDoubleClick={this.handleRowDoubleClick}
@@ -156,7 +159,7 @@ export const TrashPanel = withStyles(styles)(
                         <DataTableDefaultView
                             icon={TrashIcon}
                             messages={['Your trash list is empty.']}/>
-                    } />;
+                    } /></div>;
             }
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
diff --git a/src/views/user-panel/user-panel.tsx b/src/views/user-panel/user-panel.tsx
index c86ca519..5fb979a2 100644
--- a/src/views/user-panel/user-panel.tsx
+++ b/src/views/user-panel/user-panel.tsx
@@ -30,7 +30,7 @@ import { ShareMeIcon, AddIcon } from 'components/icon/icon';
 import { USERS_PANEL_ID, openUserCreateDialog } from 'store/users/users-actions';
 import { noop } from 'lodash';
 
-type UserPanelRules = "button";
+type UserPanelRules = "button" | 'root' | 'content';
 
 const styles = withStyles<UserPanelRules>(theme => ({
     button: {
@@ -39,6 +39,13 @@ const styles = withStyles<UserPanelRules>(theme => ({
         textAlign: 'right',
         alignSelf: 'center'
     },
+    root: {
+        width: '100%',
+    },
+    content: {
+        // reserve space for the tab bar
+        height: `calc(100% - ${theme.spacing.unit * 7}px)`,
+    }
 }));
 
 export enum UserPanelColumnNames {
@@ -149,13 +156,13 @@ export const UserPanel = compose(
 
             render() {
                 const { value } = this.state;
-                return <Paper>
+                return <Paper className={this.props.classes.root}>
                     <Tabs value={value} onChange={this.handleChange} fullWidth>
                         <Tab label="USERS" />
                         <Tab label="ACTIVITY" disabled />
                     </Tabs>
                     {value === 0 &&
-                        <span>
+                        <div className={this.props.classes.content}>
                             <DataExplorer
                                 id={USERS_PANEL_ID}
                                 onRowClick={noop}
@@ -178,7 +185,7 @@ export const UserPanel = compose(
                                         icon={ShareMeIcon}
                                         messages={['Your user list is empty.']} />
                                 } />
-                        </span>}
+                        </div>}
                 </Paper>;
             }
 
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 9ce93bf2..1c6bf03f 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -129,6 +129,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         minWidth: 0,
         paddingLeft: theme.spacing.unit * 3,
         paddingRight: theme.spacing.unit * 3,
+        // Reserve vertical space for app bar + MainContentBar
+        minHeight: `calc(100vh - ${theme.spacing.unit * 16}px)`,
+        display: 'flex',
     }
 });
 

commit 9c2c8aa06d5693a6e2b00e1d8ec0a8ca79098ce0
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Oct 14 18:04:09 2021 -0300

    18128: Adds panel maximizing capabilities.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 940dbd0a..7fce77de 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -11,8 +11,9 @@ import { SearchInput } from 'components/search-input/search-input';
 import { ArvadosTheme } from "common/custom-theme";
 import { createTree } from 'models/tree';
 import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import { CloseIcon, MoreOptionsIcon } from 'components/icon/icon';
+import { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
 import { PaperProps } from '@material-ui/core/Paper';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
 type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
 
@@ -21,7 +22,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         paddingBottom: theme.spacing.unit * 2
     },
     toolbar: {
-        paddingTop: theme.spacing.unit * 2
+        paddingTop: theme.spacing.unit,
+        paddingRight: theme.spacing.unit * 2,
     },
     toolbarUnderTitle: {
         paddingTop: 0
@@ -62,7 +64,6 @@ interface DataExplorerDataProps<T> {
     title?: React.ReactNode;
     paperKey?: string;
     currentItemUuid: string;
-    panelName?: string
 }
 
 interface DataExplorerActionProps<T> {
@@ -78,10 +79,10 @@ interface DataExplorerActionProps<T> {
     onChangeRowsPerPage: (rowsPerPage: number) => void;
     onLoadMore: (page: number) => void;
     extractKey?: (item: T) => React.Key;
-    doHidePanel?: () => void;
 }
 
-type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
+type DataExplorerProps<T> = DataExplorerDataProps<T> &
+    DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
 
 export const DataExplorer = withStyles(styles)(
     class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
@@ -96,7 +97,8 @@ export const DataExplorer = withStyles(styles)(
                 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
-                paperKey, fetchMode, currentItemUuid, title, doHidePanel, panelName
+                paperKey, fetchMode, currentItemUuid, title,
+                doHidePanel, doMaximizePanel, panelName, panelMaximized
             } = this.props;
             return <Paper className={classes.root} {...paperProps} key={paperKey}>
                 {title && <div className={classes.title}>{title}</div>}
@@ -113,9 +115,13 @@ export const DataExplorer = withStyles(styles)(
                             columns={columns}
                             onColumnToggle={onColumnToggle} />}
                     </Grid>
+                    { doMaximizePanel && !!!panelMaximized &&
+                        <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
+                        </Tooltip> }
                     { doHidePanel &&
                         <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                            <Button onClick={doHidePanel}><CloseIcon /></Button>
+                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
                         </Tooltip> }
                 </Toolbar>}
                 <DataTable
diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 94374c62..523eefbd 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -67,6 +67,7 @@ import LinkOutlined from '@material-ui/icons/LinkOutlined';
 // Import FontAwesome icons
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faPencilAlt, faSlash } from '@fortawesome/free-solid-svg-icons';
+import { CropFreeSharp } from '@material-ui/icons';
 library.add(
     faPencilAlt,
     faSlash,
@@ -119,6 +120,7 @@ export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
 export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
 export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
 export const MailIcon: IconType = (props) => <Mail {...props} />;
+export const MaximizeIcon: IconType = (props) => <CropFreeSharp {...props} />;
 export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
 export const MoveToIcon: IconType = (props) => <Input {...props} />;
 export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index fd192d13..05c1de05 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -28,28 +28,32 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 interface MPVHideablePanelDataProps {
     name: string;
     visible: boolean;
+    maximized: boolean;
     children: ReactNode;
 }
 
 interface MPVHideablePanelActionProps {
     doHidePanel: () => void;
+    doMaximizePanel: () => void;
 }
 
 type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
 
-const MPVHideablePanel = ({doHidePanel, name, visible, ...props}: MPVHideablePanelProps) =>
+const MPVHideablePanel = ({doHidePanel, doMaximizePanel, name, visible, maximized, ...props}: MPVHideablePanelProps) =>
     visible
     ? <>
-        {React.cloneElement((props.children as ReactElement), { doHidePanel, panelName: name })}
+        {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel,panelName: name, panelMaximized: maximized })}
     </>
     : null;
 
 interface MPVPanelDataProps {
     panelName?: string;
+    panelMaximized?: boolean;
 }
 
 interface MPVPanelActionProps {
     doHidePanel?: () => void;
+    doMaximizePanel?: () => void;
 }
 
 // Props received by panel implementors
@@ -58,9 +62,9 @@ export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps;
 type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps;
 
 // Grid item compatible component for layout and MPV props passing
-export const MPVPanelContent = ({doHidePanel, panelName, ...props}: MPVPanelContentProps) =>
+export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelMaximized, ...props}: MPVPanelContentProps) =>
     <Grid item {...props}>
-        {React.cloneElement(props.children, { doHidePanel, panelName })}
+        {React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })}
     </Grid>;
 
 export interface MPVPanelState {
@@ -97,6 +101,14 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
                     ...panelVisibility.slice(idx+1)
                 ])
             };
+            const maximizeFn = (idx: number) => () => {
+                // Maximize X == hide all but X
+                setPanelVisibility([
+                    ...panelVisibility.slice(0, idx).map(() => false),
+                    true,
+                    ...panelVisibility.slice(idx+1).map(() => false),
+                ])
+            };
             const toggleIcon = panelVisibility[idx]
                 ? <VisibleIcon className={classNames(classes.buttonIcon)} />
                 : <InvisibleIcon className={classNames(classes.buttonIcon)}/>
@@ -109,6 +121,8 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             const toggleTooltip = panelVisibility[idx]
                 ? `Hide ${panelName} panel`
                 : `Show ${panelName} panel`;
+            const panelIsMaximized = panelVisibility[idx] &&
+                panelVisibility.filter(e => e).length === 1;
 
             toggles = [
                 ...toggles,
@@ -123,7 +137,9 @@ const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVCo
             ];
 
             const aPanel =
-                <MPVHideablePanel visible={panelVisibility[idx]} name={panelName} doHidePanel={toggleFn(idx)}>
+                <MPVHideablePanel visible={panelVisibility[idx]} name={panelName}
+                    maximized={panelIsMaximized}
+                    doHidePanel={toggleFn(idx)} doMaximizePanel={maximizeFn(idx)}>
                     {children[idx]}
                 </MPVHideablePanel>;
             panels = [...panels, aPanel];
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
index 8be7d1cc..4c938017 100644
--- a/src/views/process-panel/process-information-card.tsx
+++ b/src/views/process-panel/process-information-card.tsx
@@ -5,7 +5,7 @@
 import React from 'react';
 import {
     StyleRulesCallback, WithStyles, withStyles, Card,
-    CardHeader, IconButton, CardContent, Grid, Chip, Typography, Tooltip, Button
+    CardHeader, IconButton, CardContent, Grid, Chip, Typography, Tooltip
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import { CloseIcon, MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
@@ -114,7 +114,7 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
                         </Tooltip>
                         { doHidePanel &&
                         <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                            <Button onClick={doHidePanel}><CloseIcon /></Button>
+                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
                         </Tooltip> }
                     </div>
                 }
diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx
index 49354671..41a8f66b 100644
--- a/src/views/subprocess-panel/subprocess-panel-root.tsx
+++ b/src/views/subprocess-panel/subprocess-panel-root.tsx
@@ -94,5 +94,7 @@ export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps)
                 messages={DEFAULT_VIEW_MESSAGES} />
         }
         doHidePanel={props.doHidePanel}
+        doMaximizePanel={props.doMaximizePanel}
+        panelMaximized={props.panelMaximized}
         panelName={props.panelName} />;
 };
\ No newline at end of file

commit a65b85dae6c3ec24c686c7ee2cdbdbf0734138bf
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Oct 14 16:16:18 2021 -0300

    18128: Adds ability to set up initial per-panel visibility.
    
    If panelStates isn't defined, the default keeps being "show all panels".
    If panelStates is defined, but some panel state isn't included, the default
    is to set its initial visibility to false. If it is indeed included but
    the optional visibility setting isn't defined, by default the panel will
    be shown to avoid having to explicitly set visible:true to all panels.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 5c16096b..fd192d13 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -63,20 +63,26 @@ export const MPVPanelContent = ({doHidePanel, panelName, ...props}: MPVPanelCont
         {React.cloneElement(props.children, { doHidePanel, panelName })}
     </Grid>;
 
-export interface MPVContainerDataProps {
-    panelNames?: string[];
+export interface MPVPanelState {
+    name: string;
+    visible?: boolean;
+}
+interface MPVContainerDataProps {
+    panelStates?: MPVPanelState[];
 }
-
 type MPVContainerProps = MPVContainerDataProps & GridProps;
 
 // Grid container compatible component that also handles panel toggling.
-const MPVContainerComponent = ({children, panelNames, classes, ...props}: MPVContainerProps & WithStyles<CssRules>) => {
+const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVContainerProps & WithStyles<CssRules>) => {
     if (children === undefined || children === null || children === {}) {
         children = [];
     } else if (!isArray(children)) {
         children = [children];
     }
-    const visibility = (children as ReactNodeArray).map(() => true);
+    const visibility = (children as ReactNodeArray).map((_, idx) =>
+        !!!panelStates || // if panelStates wasn't passed, default to all visible panels
+            (panelStates[idx] &&
+                (panelStates[idx].visible || panelStates[idx].visible === undefined)));
     const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
 
     let panels: JSX.Element[] = [];
@@ -94,9 +100,9 @@ const MPVContainerComponent = ({children, panelNames, classes, ...props}: MPVCon
             const toggleIcon = panelVisibility[idx]
                 ? <VisibleIcon className={classNames(classes.buttonIcon)} />
                 : <InvisibleIcon className={classNames(classes.buttonIcon)}/>
-            const panelName = panelNames === undefined
+            const panelName = panelStates === undefined
                 ? `Panel ${idx+1}`
-                : panelNames[idx] || `Panel ${idx+1}`;
+                : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx+1}`;
             const toggleVariant = panelVisibility[idx]
                 ? "raised"
                 : "flat";
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 045e5cfa..e2854bdd 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -10,7 +10,7 @@ import { ProcessIcon } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
-import { MPVContainer, MPVPanelContent } from 'components/multi-panel-view/multi-panel-view';
+import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 
 export interface ProcessPanelRootDataProps {
     process?: Process;
@@ -29,9 +29,14 @@ export interface ProcessPanelRootActionProps {
 
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
 
+const panelsData: MPVPanelState[] = [
+    {name: "Info"},
+    {name: "Subprocesses"},
+];
+
 export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =>
     process
-        ? <MPVContainer spacing={8} panelNames={["Info", "Subprocesses"]} alignItems="stretch">
+        ? <MPVContainer spacing={8} panelStates={panelsData} alignItems="stretch">
             <MPVPanelContent sm={12} md={12}>
                 <ProcessInformationCard
                     process={process}

commit a482d45b5a46c897ff2da9808e01317831559a00
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Oct 14 15:28:40 2021 -0300

    18128: Exports a common type for panel implementors to receive MPV props.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 8b38e6c6..5c16096b 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -35,25 +35,27 @@ interface MPVHideablePanelActionProps {
     doHidePanel: () => void;
 }
 
-type MPVPanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
+type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
 
-const MPVHideablePanel = ({doHidePanel, name, visible, ...props}: MPVPanelProps) =>
+const MPVHideablePanel = ({doHidePanel, name, visible, ...props}: MPVHideablePanelProps) =>
     visible
     ? <>
         {React.cloneElement((props.children as ReactElement), { doHidePanel, panelName: name })}
     </>
     : null;
 
-interface MPVPanelContentDataProps {
+interface MPVPanelDataProps {
     panelName?: string;
-    children: ReactElement;
 }
 
-interface MPVPanelContentActionProps {
+interface MPVPanelActionProps {
     doHidePanel?: () => void;
 }
 
-type MPVPanelContentProps = MPVPanelContentDataProps & MPVPanelContentActionProps & GridProps;
+// Props received by panel implementors
+export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps;
+
+type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps;
 
 // Grid item compatible component for layout and MPV props passing
 export const MPVPanelContent = ({doHidePanel, panelName, ...props}: MPVPanelContentProps) =>
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
index ad7673af..8be7d1cc 100644
--- a/src/views/process-panel/process-information-card.tsx
+++ b/src/views/process-panel/process-information-card.tsx
@@ -15,6 +15,7 @@ import { getProcessStatus, getProcessStatusColor } from 'store/processes/process
 import { formatDate } from 'common/formatters';
 import classNames from 'classnames';
 import { ContainerState } from 'models/container';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
 type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton';
 
@@ -81,11 +82,9 @@ export interface ProcessInformationCardDataProps {
     navigateToOutput: (uuid: string) => void;
     openWorkflow: (uuid: string) => void;
     cancelProcess: (uuid: string) => void;
-    doHidePanel?: () => void;
-    panelName?: string;
 }
 
-type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true>;
+type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true> & MPVPanelProps;
 
 export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
     ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess, doHidePanel, panelName }: ProcessInformationCardProps) => {
diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx
index e35a5712..49354671 100644
--- a/src/views/subprocess-panel/subprocess-panel-root.tsx
+++ b/src/views/subprocess-panel/subprocess-panel-root.tsx
@@ -17,6 +17,7 @@ import { DataTableDefaultView } from 'components/data-table-default-view/data-ta
 import { createTree } from 'models/tree';
 import { getInitialProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
 import { ResourcesState } from 'store/resources/resources';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 
 export enum SubprocessPanelColumnNames {
     NAME = "Name",
@@ -65,14 +66,12 @@ export const subprocessPanelColumns: DataColumns<string> = [
 
 export interface SubprocessPanelDataProps {
     resources: ResourcesState;
-    panelName?: string;
 }
 
 export interface SubprocessPanelActionProps {
     onItemClick: (item: string) => void;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string, resources: ResourcesState) => void;
     onItemDoubleClick: (item: string) => void;
-    doHidePanel?: () => void;
 }
 
 type SubprocessPanelProps = SubprocessPanelActionProps & SubprocessPanelDataProps;
@@ -82,7 +81,7 @@ const DEFAULT_VIEW_MESSAGES = [
     'The current process may not have any or none matches current filtering.'
 ];
 
-export const SubprocessPanelRoot = (props: SubprocessPanelProps) => {
+export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps) => {
     return <DataExplorer
         id={SUBPROCESS_PANEL_ID}
         onRowClick={props.onItemClick}

commit 8efd02a6af777d05429eed7233ea2f43eb859b94
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Oct 14 14:54:28 2021 -0300

    18128: Improves toggle button bar's alignment & separation.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index ff1680b0..8b38e6c6 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -16,7 +16,7 @@ type CssRules = 'button' | 'buttonIcon';
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
         padding: '2px 5px',
-        marginRight: '2px',
+        marginRight: '5px',
     },
     buttonIcon: {
         boxShadow: 'none',
@@ -123,7 +123,9 @@ const MPVContainerComponent = ({children, panelNames, classes, ...props}: MPVCon
     };
 
     return <Grid container {...props}>
-        { toggles }
+        <Grid item>
+            { toggles }
+        </Grid>
         { panelVisibility.includes(true)
             ? panels
             : <Grid container alignItems='center' justify='center'>

commit e58425d4018d5d2b3050a9ef99275b64af616c26
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Oct 14 11:49:29 2021 -0300

    18128: Adds better styling to the toggle bar.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 05b94f7e..94374c62 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -59,6 +59,8 @@ import SettingsEthernet from '@material-ui/icons/SettingsEthernet';
 import Star from '@material-ui/icons/Star';
 import StarBorder from '@material-ui/icons/StarBorder';
 import Warning from '@material-ui/icons/Warning';
+import Visibility from '@material-ui/icons/Visibility';
+import VisibilityOff from '@material-ui/icons/VisibilityOff';
 import VpnKey from '@material-ui/icons/VpnKey';
 import LinkOutlined from '@material-ui/icons/LinkOutlined';
 
@@ -145,6 +147,8 @@ export const SidePanelRightArrowIcon: IconType = (props) => <PlayArrow {...props
 export const TrashIcon: IconType = (props) => <Delete {...props} />;
 export const UserPanelIcon: IconType = (props) => <Person {...props} />;
 export const UsedByIcon: IconType = (props) => <Folder {...props} />;
+export const VisibleIcon: IconType = (props) => <Visibility {...props} />;
+export const InvisibleIcon: IconType = (props) => <VisibilityOff {...props} />;
 export const WorkflowIcon: IconType = (props) => <Code {...props} />;
 export const WarningIcon: IconType = (props) => <Warning style={{ color: '#fbc02d', height: '30px', width: '30px' }} {...props} />;
 export const Link: IconType = (props) => <LinkOutlined {...props} />;
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
index 54bea41d..ff1680b0 100644
--- a/src/components/multi-panel-view/multi-panel-view.tsx
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -3,12 +3,27 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React, { ReactElement, ReactNode, useState } from 'react';
-import { Button, Grid } from "@material-ui/core";
+import { Button, Grid, StyleRulesCallback, Tooltip, withStyles, WithStyles } from "@material-ui/core";
 import { GridProps } from '@material-ui/core/Grid';
 import { isArray } from 'lodash';
 import { DefaultView } from 'components/default-view/default-view';
-import { InfoIcon } from 'components/icon/icon';
+import { InfoIcon, InvisibleIcon, VisibleIcon } from 'components/icon/icon';
 import { ReactNodeArray } from 'prop-types';
+import classNames from 'classnames';
+
+type CssRules = 'button' | 'buttonIcon';
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+    button: {
+        padding: '2px 5px',
+        marginRight: '2px',
+    },
+    buttonIcon: {
+        boxShadow: 'none',
+        padding: '2px 0px 2px 5px',
+        fontSize: '1rem'
+    },
+});
 
 interface MPVHideablePanelDataProps {
     name: string;
@@ -53,7 +68,7 @@ export interface MPVContainerDataProps {
 type MPVContainerProps = MPVContainerDataProps & GridProps;
 
 // Grid container compatible component that also handles panel toggling.
-export const MPVContainer = ({children, panelNames, ...props}: MPVContainerProps) => {
+const MPVContainerComponent = ({children, panelNames, classes, ...props}: MPVContainerProps & WithStyles<CssRules>) => {
     if (children === undefined || children === null || children === {}) {
         children = [];
     } else if (!isArray(children)) {
@@ -74,15 +89,29 @@ export const MPVContainer = ({children, panelNames, ...props}: MPVContainerProps
                     ...panelVisibility.slice(idx+1)
                 ])
             };
-            const toggleLabel = panelVisibility[idx] ? 'Hide' : 'Show'
+            const toggleIcon = panelVisibility[idx]
+                ? <VisibleIcon className={classNames(classes.buttonIcon)} />
+                : <InvisibleIcon className={classNames(classes.buttonIcon)}/>
             const panelName = panelNames === undefined
                 ? `Panel ${idx+1}`
                 : panelNames[idx] || `Panel ${idx+1}`;
-
+            const toggleVariant = panelVisibility[idx]
+                ? "raised"
+                : "flat";
+            const toggleTooltip = panelVisibility[idx]
+                ? `Hide ${panelName} panel`
+                : `Show ${panelName} panel`;
 
             toggles = [
                 ...toggles,
-                <Button onClick={toggleFn(idx)}>{toggleLabel} {panelName}</Button>
+                <Tooltip title={toggleTooltip} disableFocusListener>
+                    <Button variant={toggleVariant} size="small" color="primary"
+                        className={classNames(classes.button)}
+                        onClick={toggleFn(idx)}>
+                            {panelName}
+                            {toggleIcon}
+                    </Button>
+                </Tooltip>
             ];
 
             const aPanel =
@@ -102,3 +131,5 @@ export const MPVContainer = ({children, panelNames, ...props}: MPVContainerProps
             </Grid> }
     </Grid>;
 };
+
+export const MPVContainer = withStyles(styles)(MPVContainerComponent);
\ No newline at end of file

commit 8ff2ace73eab152113f8eec5027a302df48d31a5
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Oct 8 16:11:16 2021 -0300

    18128: Show [X] close button on individual panels.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index d272e870..940dbd0a 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -11,7 +11,7 @@ import { SearchInput } from 'components/search-input/search-input';
 import { ArvadosTheme } from "common/custom-theme";
 import { createTree } from 'models/tree';
 import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import { MoreOptionsIcon } from 'components/icon/icon';
+import { CloseIcon, MoreOptionsIcon } from 'components/icon/icon';
 import { PaperProps } from '@material-ui/core/Paper';
 
 type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
@@ -62,6 +62,7 @@ interface DataExplorerDataProps<T> {
     title?: React.ReactNode;
     paperKey?: string;
     currentItemUuid: string;
+    panelName?: string
 }
 
 interface DataExplorerActionProps<T> {
@@ -77,6 +78,7 @@ interface DataExplorerActionProps<T> {
     onChangeRowsPerPage: (rowsPerPage: number) => void;
     onLoadMore: (page: number) => void;
     extractKey?: (item: T) => React.Key;
+    doHidePanel?: () => void;
 }
 
 type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
@@ -94,7 +96,7 @@ export const DataExplorer = withStyles(styles)(
                 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
-                paperKey, fetchMode, currentItemUuid, title
+                paperKey, fetchMode, currentItemUuid, title, doHidePanel, panelName
             } = this.props;
             return <Paper className={classes.root} {...paperProps} key={paperKey}>
                 {title && <div className={classes.title}>{title}</div>}
@@ -111,6 +113,10 @@ export const DataExplorer = withStyles(styles)(
                             columns={columns}
                             onColumnToggle={onColumnToggle} />}
                     </Grid>
+                    { doHidePanel &&
+                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                            <Button onClick={doHidePanel}><CloseIcon /></Button>
+                        </Tooltip> }
                 </Toolbar>}
                 <DataTable
                     columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
index e70a0478..ad7673af 100644
--- a/src/views/process-panel/process-information-card.tsx
+++ b/src/views/process-panel/process-information-card.tsx
@@ -5,10 +5,10 @@
 import React from 'react';
 import {
     StyleRulesCallback, WithStyles, withStyles, Card,
-    CardHeader, IconButton, CardContent, Grid, Chip, Typography, Tooltip
+    CardHeader, IconButton, CardContent, Grid, Chip, Typography, Tooltip, Button
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
-import { MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
+import { CloseIcon, MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
 import { DetailsAttribute } from 'components/details-attribute/details-attribute';
 import { Process } from 'store/processes/process';
 import { getProcessStatus, getProcessStatusColor } from 'store/processes/process';
@@ -81,12 +81,14 @@ export interface ProcessInformationCardDataProps {
     navigateToOutput: (uuid: string) => void;
     openWorkflow: (uuid: string) => void;
     cancelProcess: (uuid: string) => void;
+    doHidePanel?: () => void;
+    panelName?: string;
 }
 
 type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true>;
 
 export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
-    ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess }: ProcessInformationCardProps) => {
+    ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess, doHidePanel, panelName }: ProcessInformationCardProps) => {
         const { container } = process;
         const startedAt = container ? formatDate(container.startedAt) : 'N/A';
         const finishedAt = container ? formatDate(container.finishedAt) : 'N/A';
@@ -111,6 +113,10 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
                                 <MoreOptionsIcon />
                             </IconButton>
                         </Tooltip>
+                        { doHidePanel &&
+                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                            <Button onClick={doHidePanel}><CloseIcon /></Button>
+                        </Tooltip> }
                     </div>
                 }
                 title={
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 242349b5..045e5cfa 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -10,7 +10,7 @@ import { ProcessIcon } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
-import { MPVContainer } from 'components/multi-panel-view/multi-panel-view';
+import { MPVContainer, MPVPanelContent } from 'components/multi-panel-view/multi-panel-view';
 
 export interface ProcessPanelRootDataProps {
     process?: Process;
@@ -32,7 +32,7 @@ export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRoot
 export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =>
     process
         ? <MPVContainer spacing={8} panelNames={["Info", "Subprocesses"]} alignItems="stretch">
-            <Grid item sm={12} md={12}>
+            <MPVPanelContent sm={12} md={12}>
                 <ProcessInformationCard
                     process={process}
                     onContextMenu={event => props.onContextMenu(event, process)}
@@ -41,10 +41,10 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =
                     openWorkflow={props.navigateToWorkflow}
                     cancelProcess={props.cancelProcess}
                 />
-            </Grid>
-            <Grid item sm={12} md={12}>
+            </MPVPanelContent>
+            <MPVPanelContent sm={12} md={12}>
                 <SubprocessPanel />
-            </Grid>
+            </MPVPanelContent>
         </MPVContainer>
         : <Grid container
             alignItems='center'
diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx
index b8e1b081..e35a5712 100644
--- a/src/views/subprocess-panel/subprocess-panel-root.tsx
+++ b/src/views/subprocess-panel/subprocess-panel-root.tsx
@@ -65,12 +65,14 @@ export const subprocessPanelColumns: DataColumns<string> = [
 
 export interface SubprocessPanelDataProps {
     resources: ResourcesState;
+    panelName?: string;
 }
 
 export interface SubprocessPanelActionProps {
     onItemClick: (item: string) => void;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string, resources: ResourcesState) => void;
     onItemDoubleClick: (item: string) => void;
+    doHidePanel?: () => void;
 }
 
 type SubprocessPanelProps = SubprocessPanelActionProps & SubprocessPanelDataProps;
@@ -91,5 +93,7 @@ export const SubprocessPanelRoot = (props: SubprocessPanelProps) => {
             <DataTableDefaultView
                 icon={ProcessIcon}
                 messages={DEFAULT_VIEW_MESSAGES} />
-        } />;
+        }
+        doHidePanel={props.doHidePanel}
+        panelName={props.panelName} />;
 };
\ No newline at end of file

commit a5e931aafba85d90e98a82372f3c06ad107dbe46
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Oct 8 16:05:05 2021 -0300

    18128: Applies MPVContainer component to the process panel.
    
    Both panel are now able to be toggled via the top level button tray.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index e7f66573..242349b5 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -10,6 +10,7 @@ import { ProcessIcon } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
+import { MPVContainer } from 'components/multi-panel-view/multi-panel-view';
 
 export interface ProcessPanelRootDataProps {
     process?: Process;
@@ -30,7 +31,7 @@ export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRoot
 
 export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =>
     process
-        ? <Grid container spacing={16} alignItems="stretch">
+        ? <MPVContainer spacing={8} panelNames={["Info", "Subprocesses"]} alignItems="stretch">
             <Grid item sm={12} md={12}>
                 <ProcessInformationCard
                     process={process}
@@ -44,7 +45,7 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) =
             <Grid item sm={12} md={12}>
                 <SubprocessPanel />
             </Grid>
-        </Grid>
+        </MPVContainer>
         : <Grid container
             alignItems='center'
             justify='center'

commit 247888ae38e7f5451d7e54cd5aac27391f361c78
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Oct 8 15:49:15 2021 -0300

    18128: Adds Multi Panel View (MPV for short) component family.
    
    MPVContainer aims to be a drop-in replacement for <Grid container...>
    that will handle its children's visibility and pass to them additional
    props so they can optionally offer UI elements to the user.
    
    MPVPanelContent aims to be a drop-in replacement for <Grid item...>
    in case there's a need for special layout inside a container. This
    component will forward the additional props to its children.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 26ce4fea..05b94f7e 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -112,6 +112,7 @@ export const FileIcon: IconType = (props) => <DescriptionIcon {...props} />;
 export const HelpIcon: IconType = (props) => <Help {...props} />;
 export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
 export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
+export const InfoIcon: IconType = (props) => <Info {...props} />;
 export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
 export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
 export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx
new file mode 100644
index 00000000..54bea41d
--- /dev/null
+++ b/src/components/multi-panel-view/multi-panel-view.tsx
@@ -0,0 +1,104 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React, { ReactElement, ReactNode, useState } from 'react';
+import { Button, Grid } from "@material-ui/core";
+import { GridProps } from '@material-ui/core/Grid';
+import { isArray } from 'lodash';
+import { DefaultView } from 'components/default-view/default-view';
+import { InfoIcon } from 'components/icon/icon';
+import { ReactNodeArray } from 'prop-types';
+
+interface MPVHideablePanelDataProps {
+    name: string;
+    visible: boolean;
+    children: ReactNode;
+}
+
+interface MPVHideablePanelActionProps {
+    doHidePanel: () => void;
+}
+
+type MPVPanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
+
+const MPVHideablePanel = ({doHidePanel, name, visible, ...props}: MPVPanelProps) =>
+    visible
+    ? <>
+        {React.cloneElement((props.children as ReactElement), { doHidePanel, panelName: name })}
+    </>
+    : null;
+
+interface MPVPanelContentDataProps {
+    panelName?: string;
+    children: ReactElement;
+}
+
+interface MPVPanelContentActionProps {
+    doHidePanel?: () => void;
+}
+
+type MPVPanelContentProps = MPVPanelContentDataProps & MPVPanelContentActionProps & GridProps;
+
+// Grid item compatible component for layout and MPV props passing
+export const MPVPanelContent = ({doHidePanel, panelName, ...props}: MPVPanelContentProps) =>
+    <Grid item {...props}>
+        {React.cloneElement(props.children, { doHidePanel, panelName })}
+    </Grid>;
+
+export interface MPVContainerDataProps {
+    panelNames?: string[];
+}
+
+type MPVContainerProps = MPVContainerDataProps & GridProps;
+
+// Grid container compatible component that also handles panel toggling.
+export const MPVContainer = ({children, panelNames, ...props}: MPVContainerProps) => {
+    if (children === undefined || children === null || children === {}) {
+        children = [];
+    } else if (!isArray(children)) {
+        children = [children];
+    }
+    const visibility = (children as ReactNodeArray).map(() => true);
+    const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
+
+    let panels: JSX.Element[] = [];
+    let toggles: JSX.Element[] = [];
+
+    if (isArray(children)) {
+        for (let idx = 0; idx < children.length; idx++) {
+            const toggleFn = (idx: number) => () => {
+                setPanelVisibility([
+                    ...panelVisibility.slice(0, idx),
+                    !panelVisibility[idx],
+                    ...panelVisibility.slice(idx+1)
+                ])
+            };
+            const toggleLabel = panelVisibility[idx] ? 'Hide' : 'Show'
+            const panelName = panelNames === undefined
+                ? `Panel ${idx+1}`
+                : panelNames[idx] || `Panel ${idx+1}`;
+
+
+            toggles = [
+                ...toggles,
+                <Button onClick={toggleFn(idx)}>{toggleLabel} {panelName}</Button>
+            ];
+
+            const aPanel =
+                <MPVHideablePanel visible={panelVisibility[idx]} name={panelName} doHidePanel={toggleFn(idx)}>
+                    {children[idx]}
+                </MPVHideablePanel>;
+            panels = [...panels, aPanel];
+        };
+    };
+
+    return <Grid container {...props}>
+        { toggles }
+        { panelVisibility.includes(true)
+            ? panels
+            : <Grid container alignItems='center' justify='center'>
+                <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
+            </Grid> }
+    </Grid>;
+};

commit 5878b0e81cebae0283b13e2c2006b8457da5db13
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Fri Oct 8 15:42:02 2021 -0300

    18128: Removes unused code.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/components/panel-default-view/panel-default-view.tsx b/src/components/panel-default-view/panel-default-view.tsx
deleted file mode 100644
index c364bb75..00000000
--- a/src/components/panel-default-view/panel-default-view.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from 'react';
-import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { DefaultViewDataProps, DefaultView } from 'components/default-view/default-view';
-
-type CssRules = 'classRoot' | 'classIcon' | 'classMessage';
-
-const styles: StyleRulesCallback<CssRules> = () => ({
-    classRoot: {
-        position: 'absolute',
-        width: '80%',
-        left: '50%',
-        top: '50%',
-        transform: 'translate(-50%, -50%)'
-    },
-    classMessage: {
-        fontSize: '1.75rem',
-    },
-    classIcon: {
-        fontSize: '6rem'
-    }
-});
-
-type PanelDefaultViewProps = Pick<DefaultViewDataProps, 'icon' | 'messages'> & WithStyles<CssRules>;
-
-export const PanelDefaultView = withStyles(styles)(
-    ({ classes, ...props }: PanelDefaultViewProps) =>
-        <DefaultView {...classes} {...props} />);

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list