merge conflicts
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 23 Jul 2018 12:14:16 +0000 (14:14 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 23 Jul 2018 12:14:16 +0000 (14:14 +0200)
Feature #13781

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

1  2 
package.json
src/common/api/common-resource-service.ts
src/store/project/project-action.ts
src/store/project/project-reducer.ts
src/store/store.ts
src/views-components/create-project-dialog/create-project-dialog.tsx
src/views-components/dialog-create/dialog-project-create.tsx
src/views/workbench/workbench.tsx
yarn.lock

diff --cc package.json
index a8c56177bfeaf5e4dadcbb3c063709ab1a4d98e7,0c06a6f17a357115da8c6897f0f159de7da51084..06fa893f97abe1dbcd18523df1deebf0d2660a3e
@@@ -3,12 -3,11 +3,12 @@@
    "version": "0.1.0",
    "private": true,
    "dependencies": {
-     "@material-ui/core": "1.2.1",
+     "@material-ui/core": "1.4.0",
      "@material-ui/icons": "1.1.0",
-     "@types/lodash": "4.14.109",
+     "@types/lodash": "4.14.112",
 +    "@types/redux-form": "^7.4.1",
      "axios": "0.18.0",
-     "classnames": "^2.2.6",
+     "classnames": "2.2.6",
      "lodash": "4.17.10",
      "react": "16.4.1",
      "react-dom": "16.4.1",
      "@types/react-router-dom": "4.2.7",
      "@types/react-router-redux": "5.0.15",
      "@types/redux-devtools": "3.0.44",
-     "axios-mock-adapter": "^1.15.0",
-     "enzyme": "^3.3.0",
-     "enzyme-adapter-react-16": "^1.1.1",
 +    "@types/redux-form": "^7.4.1",
+     "axios-mock-adapter": "1.15.0",
+     "enzyme": "3.3.0",
+     "enzyme-adapter-react-16": "1.1.1",
      "jest-localstorage-mock": "2.2.0",
      "redux-devtools": "3.4.1",
 +    "redux-form": "^7.4.2",
      "typescript": "2.9.2"
    },
    "moduleNameMapper": {
index 39825c0e3eb8ae661212161325f7673df7cb095b,2541feab026989228c2cf14521c762c78be97d5c..3956fb7390983824a402456abc2144850b85cda2
@@@ -3,9 -3,9 +3,9 @@@
  // SPDX-License-Identifier: AGPL-3.0
  
  import * as _ from "lodash";
- import FilterBuilder from "./filter-builder";
- import OrderBuilder from "./order-builder";
+ import { FilterBuilder } from "./filter-builder";
+ import { OrderBuilder } from "./order-builder";
 -import { AxiosInstance } from "axios";
 +import { AxiosInstance, AxiosPromise } from "axios";
  import { Resource } from "../../models/resource";
  
  export interface ListArguments {
@@@ -26,12 -26,7 +26,12 @@@ export interface ListResults<T> 
      itemsAvailable: number;
  }
  
- export default class CommonResourceService<T extends Resource> {
 +export interface Errors {
 +    errors: string[];
 +    errorToken: string;
 +}
 +
+ export class CommonResourceService<T extends Resource> {
  
      static mapResponseKeys = (response: any): Promise<any> =>
          CommonResourceService.mapKeys(_.camelCase)(response.data)
index 3da60f65c9ae9c3d7a14ccbfe0806ba2d6990394,2a7a5c126860253af5929695d05ef9020978e53a..075e77d15483746a751d59553299546f01bd1460
@@@ -41,11 -42,11 +41,10 @@@ export const createProject = (project: 
      (dispatch: Dispatch, getState: () => RootState) => {
          const { ownerUuid } = getState().projects.creator;
          const projectData = { ownerUuid, ...project };
-         dispatch(actions.CREATE_PROJECT(projectData));
+         dispatch(projectActions.CREATE_PROJECT(projectData));
          return projectService
              .create(projectData)
-             .then(project => dispatch(actions.CREATE_PROJECT_SUCCESS(project)));
 -            .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project)))
 -            .catch(() => dispatch(projectActions.CREATE_PROJECT_ERROR("Could not create a project")));
++            .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project)));
      };
  
- export type ProjectAction = UnionOf<typeof actions>;
- export default actions;
+ export type ProjectAction = UnionOf<typeof projectActions>;
index a329e81242f4b8d7e4fd0ab37555281297a16c56,40356c0c90123d7775484849d8fcf937fdbce87d..94a451a86574e70de24f6d143ede7217ce25cf9c
@@@ -112,12 -111,13 +112,12 @@@ const initialState: ProjectState = 
  };
  
  
- const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
-     return actions.match(action, {
-         OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }),
export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
+     return projectActions.match(action, {
+         OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }),
          CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
 -        CREATE_PROJECT: () => updateCreator(state, { opened: false, pending: true }),
 -        CREATE_PROJECT_SUCCESS: () => updateCreator(state, { ownerUuid: "", pending: false }),
 -        CREATE_PROJECT_ERROR: () => updateCreator(state, { ownerUuid: "", pending: false }),
 +        CREATE_PROJECT: () => updateCreator(state, { error: undefined }),
 +        CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }),
          REMOVE_PROJECT: () => state,
          PROJECTS_REQUEST: itemId => {
              const items = _.cloneDeep(state.items);
index 956fb46011bf3d2fd95cd98fafc4b8637a476186,adb7ddde133d8ac0b6c07e82d6d702017ed0bd10..01b06b9528a727cd3cb9642a16bffeb0e17954ea
@@@ -7,13 -7,13 +7,14 @@@ import { routerMiddleware, routerReduce
  import thunkMiddleware from 'redux-thunk';
  import { History } from "history";
  
- import projectsReducer, { ProjectState } from "./project/project-reducer";
- import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer';
- import authReducer, { AuthState } from "./auth/auth-reducer";
- import dataExplorerReducer, { DataExplorerState } from './data-explorer/data-explorer-reducer';
- import { projectPanelMiddleware } from '../store/project-panel/project-panel-middleware';
- import detailsPanelReducer, { DetailsPanelState } from './details-panel/details-panel-reducer';
+ import { projectsReducer, ProjectState } from "./project/project-reducer";
+ import { sidePanelReducer, SidePanelState } from './side-panel/side-panel-reducer';
+ import { authReducer, AuthState } from "./auth/auth-reducer";
+ import { dataExplorerReducer, DataExplorerState } from './data-explorer/data-explorer-reducer';
+ import { projectPanelMiddleware } from './project-panel/project-panel-middleware';
+ import { detailsPanelReducer, DetailsPanelState } from './details-panel/details-panel-reducer';
+ import { contextMenuReducer, ContextMenuState } from './context-menu/context-menu-reducer';
 +import { reducer as formReducer } from 'redux-form';
  
  const composeEnhancers =
      (process.env.NODE_ENV === 'development' &&
@@@ -36,7 -37,7 +38,8 @@@ const rootReducer = combineReducers(
      dataExplorer: dataExplorerReducer,
      sidePanel: sidePanelReducer,
      detailsPanel: detailsPanelReducer,
 -    contextMenu: contextMenuReducer
++    contextMenu: contextMenuReducer,
 +    form: formReducer
  });
  
  
index f75c459347500da68ea97b196d7be098691b8bbd,2f3e0b7fe319a349165da8b86affd683234ebbbf..43621bf73c0739edcc3d99bcf3077477e1b70d4f
@@@ -4,12 -4,10 +4,12 @@@
  
  import { connect } from "react-redux";
  import { Dispatch } from "redux";
 +import { SubmissionError } from "redux-form";
 +
  import { RootState } from "../../store/store";
- import DialogProjectCreate from "../dialog-create/dialog-project-create";
- import actions, { createProject, getProjectList } from "../../store/project/project-action";
- import dataExplorerActions from "../../store/data-explorer/data-explorer-action";
 -import { DialogProjectCreate as DialogProjectCreateComponent } from "../dialog-create/dialog-project-create";
++import  DialogProjectCreate from "../dialog-create/dialog-project-create";
+ import { projectActions, createProject, getProjectList } from "../../store/project/project-action";
+ import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
  import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
  
  const mapStateToProps = (state: RootState) => ({
@@@ -27,14 -25,11 +27,14 @@@ export const addProject = (data: { name
  
  const mapDispatchToProps = (dispatch: Dispatch) => ({
      handleClose: () => {
-         dispatch(actions.CLOSE_PROJECT_CREATOR());
+         dispatch(projectActions.CLOSE_PROJECT_CREATOR());
      },
      onSubmit: (data: { name: string, description: string }) => {
 -        dispatch<any>(submit(data));
 +        return dispatch<any>(addProject(data))
 +            .catch((e: any) => {
 +                throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Project with this name already exists." : "" });
 +            });
      }
  });
  
- export default connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate);
 -export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreateComponent);
++export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate);
index 6fb8a699df64e01f35dc5a60a49c35063cd827ff,aefb8159871677ce2de8fb8ba13f4442d3d7133d..34c655e2b9922ff9da8c338cac14e59785795c32
@@@ -122,7 -37,106 +122,7 @@@ const styles: StyleRulesCallback<CssRul
      }
  });
  
 -interface ProjectCreateProps {
 -    open: boolean;
 -    handleClose: () => void;
 -    onSubmit: (data: { name: string, description: string }) => void;
 -}
 -
 -interface DialogState {
 -    name: string;
 -    description: string;
 -    isNameValid: boolean;
 -    isDescriptionValid: boolean;
 -}
 -
 -export const DialogProjectCreate = withStyles(styles)(
 -    class extends React.Component<ProjectCreateProps & WithStyles<CssRules>> {
 -        state: DialogState = {
 -            name: '',
 -            description: '',
 -            isNameValid: false,
 -            isDescriptionValid: true
 -        };
 -
 -        render() {
 -            const { name, description } = this.state;
 -            const { classes, open, handleClose } = this.props;
 -
 -            return (
 -                <Dialog
 -                    open={open}
 -                    onClose={handleClose}>
 -                    <div className={classes.dialog}>
 -                        <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a project</DialogTitle>
 -                        <DialogContent className={classes.dialogContent}>
 -                            <Validator
 -                                value={name}
 -                                onChange={e => this.isNameValid(e)}
 -                                isRequired={true}
 -                                render={hasError =>
 -                                    <TextField
 -                                        margin="dense"
 -                                        className={classes.textField}
 -                                        id="name"
 -                                        onChange={e => this.handleProjectName(e)}
 -                                        label="Project name"
 -                                        error={hasError}
 -                                        fullWidth/>}/>
 -                            <Validator
 -                                value={description}
 -                                onChange={e => this.isDescriptionValid(e)}
 -                                isRequired={false}
 -                                render={hasError =>
 -                                    <TextField
 -                                        margin="dense"
 -                                        className={classes.textField}
 -                                        id="description"
 -                                        onChange={e => this.handleDescriptionValue(e)}
 -                                        label="Description - optional"
 -                                        error={hasError}
 -                                        fullWidth/>}/>
 -                        </DialogContent>
 -                        <DialogActions>
 -                            <Button onClick={handleClose} className={classes.button} color="primary">CANCEL</Button>
 -                            <Button onClick={this.handleSubmit} className={classes.lastButton} color="primary"
 -                                    disabled={!this.state.isNameValid || (!this.state.isDescriptionValid && description.length > 0)}
 -                                    variant="raised">CREATE A PROJECT</Button>
 -                        </DialogActions>
 -                    </div>
 -                </Dialog>
 -            );
 -        }
 -
 -        handleSubmit = () => {
 -            this.props.onSubmit({
 -                name: this.state.name,
 -                description: this.state.description
 -            });
 -        }
 -
 -        handleProjectName(e: React.ChangeEvent<HTMLInputElement>) {
 -            this.setState({
 -                name: e.target.value,
 -            });
 -        }
 -
 -        handleDescriptionValue(e: React.ChangeEvent<HTMLInputElement>) {
 -            this.setState({
 -                description: e.target.value,
 -            });
 -        }
 -
 -        isNameValid(value: boolean | string) {
 -            this.setState({
 -                isNameValid: value,
 -            });
 -        }
 -
 -        isDescriptionValid(value: boolean | string) {
 -            this.setState({
 -                isDescriptionValid: value,
 -            });
 -        }
 -    }
 -);
 +export default compose(
 +    reduxForm({ form: 'projectCreateDialog' }),
 +    withStyles(styles)
- )(DialogProjectCreate);
++)(DialogProjectCreate);
index b2bdac802f7fd5bc1d173fcd1085f41db04fe2e7,a62b713a52aeb7b4149e49248dc1ac4a7fe7fea1..b1e7cd78659efe4cfa88239adaa591d7cda4813b
@@@ -111,207 -104,157 +104,157 @@@ interface WorkbenchState 
      };
  }
  
- class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
-     state = {
-         contextMenu: {
-             anchorEl: undefined,
-             itemUuid: undefined
-         },
-         isCreationDialogOpen: false,
-         anchorEl: null,
-         searchText: "",
-         breadcrumbs: [],
-         menuItems: {
-             accountMenu: [
-                 {
-                     label: "Logout",
-                     action: () => this.props.dispatch(authActions.LOGOUT())
-                 },
-                 {
-                     label: "My account",
-                     action: () => this.props.dispatch(push("/my-account"))
-                 }
-             ],
-             helpMenu: [
-                 {
-                     label: "Help",
-                     action: () => this.props.dispatch(push("/help"))
+ export const Workbench = withStyles(styles)(
+     connect<WorkbenchDataProps>(
+         (state: RootState) => ({
+             projects: state.projects.items,
+             currentProjectId: state.projects.currentItemId,
+             user: state.auth.user,
+             sidePanelItems: state.sidePanel
+         })
+     )(
+         class extends React.Component<WorkbenchProps, WorkbenchState> {
+             state = {
+                 isCreationDialogOpen: false,
+                 anchorEl: null,
+                 searchText: "",
+                 breadcrumbs: [],
+                 menuItems: {
+                     accountMenu: [
+                         {
+                             label: "Logout",
+                             action: () => this.props.dispatch(authActions.LOGOUT())
+                         },
+                         {
+                             label: "My account",
+                             action: () => this.props.dispatch(push("/my-account"))
+                         }
+                     ],
+                     helpMenu: [
+                         {
+                             label: "Help",
+                             action: () => this.props.dispatch(push("/help"))
+                         }
+                     ],
+                     anonymousMenu: [
+                         {
+                             label: "Sign in",
+                             action: () => this.props.dispatch(authActions.LOGIN())
+                         }
+                     ]
                  }
-             ],
-             anonymousMenu: [
-                 {
-                     label: "Sign in",
-                     action: () => this.props.dispatch(authActions.LOGIN())
-                 }
-             ]
-         }
-     };
+             };
  
-     mainAppBarActions: MainAppBarActionProps = {
-         onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
-             this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
-             this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
-         },
-         onSearch: searchText => {
-             this.setState({ searchText });
-             this.props.dispatch(push(`/search?q=${searchText}`));
-         },
-         onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
-         onDetailsPanelToggle: () => {
-             this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
-         },
-         onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
-             this.openContextMenu(event, breadcrumb.itemId);
-         }
-     };
+             render() {
+                 const path = getTreePath(this.props.projects, this.props.currentProjectId);
+                 const breadcrumbs = path.map(item => ({
+                     label: item.data.name,
+                     itemId: item.data.uuid,
+                     status: item.status
+                 }));
  
-     toggleSidePanelOpen = (itemId: string) => {
-         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
-     }
+                 const { classes, user } = this.props;
+                 return (
+                     <div className={classes.root}>
+                         <div className={classes.appBar}>
+                             <MainAppBar
+                                 breadcrumbs={breadcrumbs}
+                                 searchText={this.state.searchText}
+                                 user={this.props.user}
+                                 menuItems={this.state.menuItems}
+                                 {...this.mainAppBarActions} />
+                         </div>
+                         {user &&
+                             <Drawer
+                                 variant="permanent"
+                                 classes={{
+                                     paper: classes.drawerPaper,
+                                 }}>
+                                 <div className={classes.toolbar} />
+                                 <SidePanel
+                                     toggleOpen={this.toggleSidePanelOpen}
+                                     toggleActive={this.toggleSidePanelActive}
+                                     sidePanelItems={this.props.sidePanelItems}
+                                     onContextMenu={(event) => this.openContextMenu(event, authService.getUuid() || "", ContextMenuKind.RootProject)}>
+                                     <ProjectTree
+                                         projects={this.props.projects}
+                                         toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
+                                         onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid, ContextMenuKind.Project)}
+                                         toggleActive={itemId => {
+                                             this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE));
+                                             this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
+                                             this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects));
+                                         }} />
+                                 </SidePanel>
+                             </Drawer>}
+                         <main className={classes.contentWrapper}>
+                             <div className={classes.content}>
+                                 <Switch>
+                                     <Route path="/projects/:id" render={this.renderProjectPanel} />
+                                 </Switch>
+                             </div>
 -                            { user && <DetailsPanel /> }
++                            {user && <DetailsPanel />}
+                         </main>
+                         <ContextMenu />
+                         <CreateProjectDialog />
+                     </div>
+                 );
+             }
  
-     toggleSidePanelActive = (itemId: string) => {
-         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
-         this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
-         this.props.dispatch(push("/"));
-     }
+             renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
+                 onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
+                 onContextMenu={(event, item) => this.openContextMenu(event, item.uuid, ContextMenuKind.Project)}
+                 onDialogOpen={this.handleCreationDialogOpen}
+                 onItemClick={item => {
+                     this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
+                 }}
+                 onItemDoubleClick={item => {
+                     this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
+                     this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.Project));
+                 }}
+                 {...props} />
  
-     handleCreationDialogOpen = (itemUuid: string) => {
-         this.closeContextMenu();
-         this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
-     }
+             mainAppBarActions: MainAppBarActionProps = {
+                 onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
+                     this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
+                     this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
+                 },
+                 onSearch: searchText => {
+                     this.setState({ searchText });
+                     this.props.dispatch(push(`/search?q=${searchText}`));
+                 },
+                 onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
+                 onDetailsPanelToggle: () => {
+                     this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+                 },
+                 onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
+                     this.openContextMenu(event, breadcrumb.itemId, ContextMenuKind.Project);
+                 }
+             };
  
+             toggleSidePanelOpen = (itemId: string) => {
+                 this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
+             }
  
-     openContextMenu = (event: React.MouseEvent<HTMLElement>, itemUuid: string) => {
-         event.preventDefault();
-         this.setState({
-             contextMenu: {
-                 anchorEl: mockAnchorFromMouseEvent(event),
-                 itemUuid
+             toggleSidePanelActive = (itemId: string) => {
+                 this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
+                 this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
+                 this.props.dispatch(push("/"));
              }
-         });
-     }
  
-     closeContextMenu = () => {
-         this.setState({ contextMenu: {} });
-     }
+             handleCreationDialogOpen = (itemUuid: string) => {
+                 this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
+             }
  
-     openCreateDialog = (item: ContextMenuAction) => {
-         const { itemUuid } = this.state.contextMenu;
-         if (item.openCreateDialog && itemUuid) {
-             this.handleCreationDialogOpen(itemUuid);
+             openContextMenu = (event: React.MouseEvent<HTMLElement>, itemUuid: string, kind: ContextMenuKind) => {
+                 event.preventDefault();
+                 this.props.dispatch(
+                     contextMenuActions.OPEN_CONTEXT_MENU({
+                         position: { x: event.clientX, y: event.clientY },
+                         resource: { uuid: itemUuid, kind }
+                     })
+                 );
+             }
          }
-     }
-     render() {
-         const path = getTreePath(this.props.projects, this.props.currentProjectId);
-         const breadcrumbs = path.map(item => ({
-             label: item.data.name,
-             itemId: item.data.uuid,
-             status: item.status
-         }));
-         const { classes, user } = this.props;
-         return (
-             <div className={classes.root}>
-                 <div className={classes.appBar}>
-                     <MainAppBar
-                         breadcrumbs={breadcrumbs}
-                         searchText={this.state.searchText}
-                         user={this.props.user}
-                         menuItems={this.state.menuItems}
-                         {...this.mainAppBarActions} />
-                 </div>
-                 {user &&
-                     <Drawer
-                         variant="permanent"
-                         classes={{
-                             paper: classes.drawerPaper,
-                         }}>
-                         <div className={classes.toolbar} />
-                         <SidePanel
-                             toggleOpen={this.toggleSidePanelOpen}
-                             toggleActive={this.toggleSidePanelActive}
-                             sidePanelItems={this.props.sidePanelItems}
-                             onContextMenu={(event) => this.openContextMenu(event, authService.getUuid() || "")}>
-                             <ProjectTree
-                                 projects={this.props.projects}
-                                 toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
-                                 onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid)}
-                                 toggleActive={itemId => {
-                                     this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE));
-                                     this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
-                                     this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects));
-                                 }} />
-                         </SidePanel>
-                     </Drawer>}
-                 <main className={classes.contentWrapper}>
-                     <div className={classes.content}>
-                         <Switch>
-                             <Route path="/projects/:id" render={this.renderProjectPanel} />
-                         </Switch>
-                     </div>
-                     <DetailsPanel />
-                 </main>
-                 <ContextMenu
-                     anchorEl={this.state.contextMenu.anchorEl}
-                     actions={contextMenuActions}
-                     onActionClick={this.openCreateDialog}
-                     onClose={this.closeContextMenu} />
-                 <DialogProjectCreate />
-             </div>
-         );
-     }
-     renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
-         onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
-         onContextMenu={(event, item) => this.openContextMenu(event, item.uuid)}
-         onDialogOpen={this.handleCreationDialogOpen}
-         onItemClick={item => {
-             this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
-         }}
-         onItemDoubleClick={item => {
-             this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
-             this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.Project));
-         }}
-         {...props} />
- }
- const contextMenuActions = [[{
-     icon: "fas fa-plus fa-fw",
-     name: "New project",
-     openCreateDialog: true
- }, {
-     icon: "fas fa-users fa-fw",
-     name: "Share"
- }, {
-     icon: "fas fa-sign-out-alt fa-fw",
-     name: "Move to"
- }, {
-     icon: "fas fa-star fa-fw",
-     name: "Add to favourite"
- }, {
-     icon: "fas fa-edit fa-fw",
-     name: "Rename"
- }, {
-     icon: "fas fa-copy fa-fw",
-     name: "Make a copy"
- }, {
-     icon: "fas fa-download fa-fw",
-     name: "Download"
- }], [{
-     icon: "fas fa-trash-alt fa-fw",
-     name: "Remove"
- }
- ]];
- export default connect<WorkbenchDataProps>(
-     (state: RootState) => ({
-         projects: state.projects.items,
-         currentProjectId: state.projects.currentItemId,
-         user: state.auth.user,
-         sidePanelItems: state.sidePanel
-     })
- )(
-     withStyles(styles)(Workbench)
+     )
  );
diff --cc yarn.lock
index 0034169d75448de4aa4c61606da529be798bb14c,6960aaf60099961087c01579ef83f78ffd28bdd9..3557ebefe81e4c1e197f30077f9e8c6892ce28a9
+++ b/yarn.lock
@@@ -3347,13 -3334,9 +3345,13 @@@ hmac-drbg@^1.0.0
      minimalistic-crypto-utils "^1.0.1"
  
  hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
-   version "2.5.4"
-   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.4.tgz#fc3b1ac05d2ae3abedec84eba846511b0d4fcc4f"
+   version "2.5.5"
+   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
  
 +hoist-non-react-statics@^2.5.4:
 +  version "2.5.5"
 +  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
 +
  home-or-tmp@^2.0.0:
    version "2.0.0"
    resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"