Merge branch '19434-collapse-left-panel' closes #19434
authorLisa Knox <lisaknox83@gmail.com>
Mon, 19 Dec 2022 20:53:30 +0000 (15:53 -0500)
committerLisa Knox <lisaknox83@gmail.com>
Mon, 19 Dec 2022 20:53:30 +0000 (15:53 -0500)
Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox@curii.com>

src/store/side-panel/side-panel-action.ts
src/store/side-panel/side-panel-reducer.tsx [new file with mode: 0644]
src/store/store.ts
src/views-components/main-app-bar/main-app-bar.tsx
src/views-components/side-panel/side-panel.tsx
src/views/main-panel/main-panel-root.tsx
src/views/main-panel/main-panel.tsx
src/views/workbench/workbench.tsx

index 9e8da28307fc410467c594ba75d702e96f9d5db8..e4f53ceaa6be6df93c35c0831fb4d8c93d986d59 100644 (file)
@@ -5,7 +5,17 @@
 import { Dispatch } from 'redux';
 import { navigateTo } from 'store/navigation/navigation-action';
 
+export const sidePanelActions = {
+    TOGGLE_COLLAPSE: 'TOGGLE_COLLAPSE'
+}
+
 export const navigateFromSidePanel = (id: string) =>
     (dispatch: Dispatch) => {
         dispatch<any>(navigateTo(id));
     };
+
+export const toggleSidePanel = (collapsedState: boolean) => {
+    return (dispatch) => {
+        dispatch({type: sidePanelActions.TOGGLE_COLLAPSE, payload: !collapsedState})
+    }
+}
\ No newline at end of file
diff --git a/src/store/side-panel/side-panel-reducer.tsx b/src/store/side-panel/side-panel-reducer.tsx
new file mode 100644 (file)
index 0000000..a6ed03b
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { sidePanelActions } from "./side-panel-action"
+
+interface SidePanelState {
+  collapsedState: boolean
+}
+
+const sidePanelInitialState = {
+  collapsedState: false
+}
+
+export const sidePanelReducer = (state: SidePanelState = sidePanelInitialState, action)=>{
+  if(action.type === sidePanelActions.TOGGLE_COLLAPSE) return {...state, collapsedState: action.payload}
+  return state
+}
\ No newline at end of file
index 1b9e05a904714cf6a84c8b2ae8eeecb5c4fba4fc..2057939e6812e79c1dd27b021591e645f89ff91e 100644 (file)
@@ -74,6 +74,7 @@ import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-pane
 import { Config } from 'common/config';
 import { pluginConfig } from 'plugins';
 import { MiddlewareListReducer } from 'common/plugintypes';
+import { sidePanelReducer } from './side-panel/side-panel-reducer'
 
 declare global {
     interface Window {
@@ -215,5 +216,6 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     virtualMachines: virtualMachinesReducer,
     repositories: repositoriesReducer,
     keepServices: keepServicesReducer,
-    linkAccountPanel: linkAccountPanelReducer
+    linkAccountPanel: linkAccountPanelReducer,
+    sidePanel: sidePanelReducer
 });
index 442b90345dadad7687266a46749757a9ac8c6527..86aed88f1e1e893f9c39b2ed72cf55c03bdb42cc 100644 (file)
@@ -15,6 +15,7 @@ import { HelpMenu } from 'views-components/main-app-bar/help-menu';
 import { ReactNode } from "react";
 import { AdminMenu } from "views-components/main-app-bar/admin-menu";
 import { pluginConfig } from 'plugins';
+import { CollapseLeftPanelTrigger } from 'views-components/side-panel/side-panel'
 
 type CssRules = 'toolbar' | 'link';
 
@@ -24,7 +25,8 @@ const styles: StyleRulesCallback<CssRules> = () => ({
         color: 'inherit'
     },
     toolbar: {
-        height: '56px'
+        height: '56px',
+        paddingLeft: '0'
     }
 });
 
@@ -34,6 +36,8 @@ interface MainAppBarDataProps {
     children?: ReactNode;
     uuidPrefix: string;
     siteBanner: string;
+    sidePanelIsCollapsed: boolean;
+    toggleSidePanel: (collapsedState:boolean) => void
 }
 
 export type MainAppBarProps = MainAppBarDataProps & WithStyles<CssRules>;
@@ -42,6 +46,9 @@ export const MainAppBar = withStyles(styles)(
     (props: MainAppBarProps) => {
         return <AppBar position="absolute">
             <Toolbar className={props.classes.toolbar}>
+                <CollapseLeftPanelTrigger sidePanelIsCollapsed={props.sidePanelIsCollapsed} 
+                toggleSidePanel={props.toggleSidePanel}
+                />
                 <Grid container justify="space-between">
                     {pluginConfig.appBarLeft || <Grid container item xs={3} direction="column" justify="center">
                         <Typography variant='h6' color="inherit" noWrap>
@@ -49,7 +56,9 @@ export const MainAppBar = withStyles(styles)(
                                 <span dangerouslySetInnerHTML={{ __html: props.siteBanner }} /> ({props.uuidPrefix})
                 </Link>
                         </Typography>
-                        <Typography variant="caption" color="inherit">{props.buildInfo}</Typography>
+                        <Typography variant="caption" color="inherit">
+                            
+                            {props.buildInfo}</Typography>
                     </Grid>}
                     <Grid
                         item
index 218b624cd5675585120cf1b8d34b1322a0ff62c0..b4caef23d0f177cf5730189baa9f024764549129 100644 (file)
@@ -9,11 +9,12 @@ import { SidePanelTree, SidePanelTreeProps } from 'views-components/side-panel-t
 import { Dispatch } from 'redux';
 import { connect } from 'react-redux';
 import { navigateFromSidePanel } from 'store/side-panel/side-panel-action';
-import { Grid } from '@material-ui/core';
+import { Grid, Tooltip, IconButton  } from '@material-ui/core';
 import { SidePanelButton } from 'views-components/side-panel-button/side-panel-button';
 import { RootState } from 'store/store';
+import MenuIcon from "@material-ui/icons/Menu";
 
-const DRAWER_WITDH = 240;
+const DRAWER_WIDTH = 240;
 
 type CssRules = 'root';
 
@@ -23,7 +24,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         borderRight: `1px solid ${theme.palette.divider}`,
         height: '100%',
         overflowX: 'auto',
-        width: DRAWER_WITDH,
+        width: DRAWER_WIDTH,
     }
 });
 
@@ -45,3 +46,24 @@ export const SidePanel = withStyles(styles)(
                 <SidePanelTree {...props} />
             </Grid>
     ));
+
+type collapseButtonIconProps = {
+    sidePanelIsCollapsed: boolean;
+    toggleSidePanel: (collapsedState: boolean) => void
+}
+
+const collapseButtonIconStyles = {
+    menuIcon: {
+        height: '4rem',
+        width: '4rem', 
+        paddingBottom: '0.25rem'
+    }
+}
+
+export const CollapseLeftPanelTrigger = (props: collapseButtonIconProps) =>{ 
+    return <Tooltip disableFocusListener title="Toggle Side Panel">
+                <IconButton onClick={()=>{props.toggleSidePanel(props.sidePanelIsCollapsed)}}>
+                    <MenuIcon style={collapseButtonIconStyles.menuIcon} aria-label="Toggle Side Panel" />
+                </IconButton>
+            </Tooltip>
+    };
\ No newline at end of file
index 5853acb065b97acf18ea9061b5dabad77fc1bb50..543e9c635190e89068e3b9ce8079ccf5f30b352b 100644 (file)
@@ -32,31 +32,41 @@ export interface MainPanelRootDataProps {
     isLinkingPath: boolean;
     siteBanner: string;
     sessionIdleTimeout: number;
+    sidePanelIsCollapsed: boolean;
 }
 
-type MainPanelRootProps = MainPanelRootDataProps & WithStyles<CssRules>;
+interface MainPanelRootDispatchProps {
+    toggleSidePanel: () => void
+}
+
+type MainPanelRootProps = MainPanelRootDataProps & MainPanelRootDispatchProps & WithStyles<CssRules>;
 
 export const MainPanelRoot = withStyles(styles)(
     ({ classes, loading, working, user, buildInfo, uuidPrefix,
-        isNotLinking, isLinkingPath, siteBanner, sessionIdleTimeout }: MainPanelRootProps) =>
-        loading
+        isNotLinking, isLinkingPath, siteBanner, sessionIdleTimeout, 
+        sidePanelIsCollapsed, toggleSidePanel }: MainPanelRootProps) =>{
+        return loading
             ? <WorkbenchLoadingScreen />
             : <>
-                {isNotLinking && <MainAppBar
-                    user={user}
-                    buildInfo={buildInfo}
-                    uuidPrefix={uuidPrefix}
-                    siteBanner={siteBanner}>
-                    {working
-                        ? <LinearProgress color="secondary" data-cy="linear-progress" />
-                        : null}
-                </MainAppBar>}
-                <Grid container direction="column" className={classes.root}>
-                    {user
-                        ? (user.isActive || (!user.isActive && isLinkingPath)
-                            ? <WorkbenchPanel isNotLinking={isNotLinking} isUserActive={user.isActive} sessionIdleTimeout={sessionIdleTimeout} />
-                            : <InactivePanel />)
-                        : <LoginPanel />}
-                </Grid>
-            </>
+            {isNotLinking && <MainAppBar
+                user={user}
+                buildInfo={buildInfo}
+                uuidPrefix={uuidPrefix}
+                siteBanner={siteBanner}
+                sidePanelIsCollapsed={sidePanelIsCollapsed}
+                toggleSidePanel={toggleSidePanel}
+                >
+                {working
+                    ? <LinearProgress color="secondary" data-cy="linear-progress" />
+                    : null}
+            </MainAppBar>}
+            <Grid container direction="column" className={classes.root}>
+                {user
+                    ? (user.isActive || (!user.isActive && isLinkingPath)
+                    ? <WorkbenchPanel isNotLinking={isNotLinking} isUserActive={user.isActive} sessionIdleTimeout={sessionIdleTimeout} sidePanelIsCollapsed={sidePanelIsCollapsed}/>
+                    : <InactivePanel />)
+                    : <LoginPanel />}
+            </Grid>
+        </>
+    }
 );
index 2968499df1cef23364329e8fbb1badf4a1ec780a..fac3da67150f37cacfaf7a6a711c94361dea326e 100644 (file)
@@ -10,6 +10,7 @@ import { isSystemWorking } from 'store/progress-indicator/progress-indicator-red
 import { isWorkbenchLoading } from 'store/workbench/workbench-actions';
 import { LinkAccountPanelStatus } from 'store/link-account-panel/link-account-panel-reducer';
 import { matchLinkAccountRoute } from 'routes/routes';
+import { toggleSidePanel } from "store/side-panel/side-panel-action";
 
 const mapStateToProps = (state: RootState): MainPanelRootDataProps => {
     return {
@@ -21,10 +22,17 @@ const mapStateToProps = (state: RootState): MainPanelRootDataProps => {
         isNotLinking: state.linkAccountPanel.status === LinkAccountPanelStatus.NONE || state.linkAccountPanel.status === LinkAccountPanelStatus.INITIAL,
         isLinkingPath: state.router.location ? matchLinkAccountRoute(state.router.location.pathname) !== null : false,
         siteBanner: state.auth.config.clusterConfig.Workbench.SiteName,
-        sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0
+        sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
+        sidePanelIsCollapsed: state.sidePanel.collapsedState,
     };
 };
 
-const mapDispatchToProps = null;
+const mapDispatchToProps = (dispatch) => {
+    return {
+        toggleSidePanel: (collapsedState)=>{
+            return dispatch(toggleSidePanel(collapsedState))
+        }
+    }
+};
 
 export const MainPanel = connect(mapStateToProps, mapDispatchToProps)(MainPanelRoot);
index a6c49e348495e6f48a21e1d8a99e6a78016a1e83..35105e56ccc02b265050cfaad9a51b7d15e0e03d 100644 (file)
@@ -137,6 +137,7 @@ interface WorkbenchDataProps {
     isUserActive: boolean;
     isNotLinking: boolean;
     sessionIdleTimeout: number;
+    sidePanelIsCollapsed: boolean;
 }
 
 type WorkbenchPanelProps = WithStyles<CssRules> & WorkbenchDataProps;
@@ -185,9 +186,17 @@ const reduceRoutesFn: (a: React.ReactElement[],
 
 routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children)));
 
+const applyCollapsedState = (isCollapsed) => {
+    const rightPanel: Element = document.getElementsByClassName('layout-pane')[1]
+    if(rightPanel) {
+        rightPanel.setAttribute('style', `width: ${isCollapsed ? 100 : getSplitterInitialSize()}%`)
+    }
+}
+
 export const WorkbenchPanel =
-    withStyles(styles)((props: WorkbenchPanelProps) =>
-        <Grid container item xs className={props.classes.root}>
+    withStyles(styles)((props: WorkbenchPanelProps) =>{
+        applyCollapsedState(props.sidePanelIsCollapsed)
+        return <Grid container item xs className={props.classes.root}>
             {props.sessionIdleTimeout > 0 && <AutoLogout />}
             <Grid container item xs className={props.classes.container}>
                 <SplitterLayout customClassName={props.classes.splitter} percentage={true}
@@ -271,5 +280,5 @@ export const WorkbenchPanel =
             <FedLogin />
             <WebDavS3InfoDialog />
             {React.createElement(React.Fragment, null, pluginConfig.dialogs)}
-        </Grid>
+        </Grid>}
     );