Merge branch 'master'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 26 Jun 2018 16:42:24 +0000 (18:42 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 26 Jun 2018 16:42:24 +0000 (18:42 +0200)
Feature #13678

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

1  2 
src/components/data-explorer/data-explorer.tsx
src/components/data-table/data-table.test.tsx
src/store/navigation/navigation-action.ts
src/store/store.ts
src/views/project-panel/project-panel-item.ts
src/views/project-panel/project-panel-selectors.ts
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.tsx

index 77979af6901ea19b3c6d3ce7b702f5289b09dd4f,726972e0282f7774b2b5592243cf964f7fcbbddb..b9d112520acc33ec0083d05a6e022161711c08fb
@@@ -97,26 -98,8 +97,8 @@@ describe("<DataTable />", () => 
          expect(dataTable.find(TableBody).find(TableCell).key()).toBe("column-1-key");
      });
  
-     it("shows information that items array is empty", () => {
-         const columns: DataColumns<string> = [
-             {
-                 name: "Column 1",
-                 render: () => <span />,
-                 selected: true
-             }
-         ];
-         const dataTable = mount(<DataTable
-             columns={columns}
-             items={[]}
-             onFiltersChange={jest.fn()}
-             onRowClick={jest.fn()}
-             onRowContextMenu={jest.fn()}
-             onSortToggle={jest.fn()} />);
-         expect(dataTable.find(Typography).text()).toBe("No items");
-     });
      it("renders items", () => {
 -        const columns: Array<DataColumn<string>> = [
 +        const columns: DataColumns<string> = [
              {
                  name: "Column 1",
                  render: (item) => <Typography>{item}</Typography>,
index 0000000000000000000000000000000000000000,0b4bcdf87ab033421319eaa472a3ea15449ea296..b811f9a292b8b42bf9cfb6ba8fcdf93db017351d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,63 +1,80 @@@
 -import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { Dispatch } from "redux";
+ import projectActions, { getProjectList } from "../project/project-action";
+ import { push } from "react-router-redux";
 -import { Project } from "../../models/project";
++import { TreeItemStatus } from "../../components/tree/tree";
+ import { getCollectionList } from "../collection/collection-action";
+ import { findTreeItem } from "../project/project-reducer";
 -export const setProjectItem = (projects: Array<TreeItem<Project>>, itemId: string, itemKind: ResourceKind, itemMode: ItemMode) => (dispatch: Dispatch) => {
+ import { Resource, ResourceKind } from "../../models/resource";
+ import sidePanelActions from "../side-panel/side-panel-action";
++import dataExplorerActions from "../data-explorer/data-explorer-action";
++import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
++import { projectPanelItems } from "../../views/project-panel/project-panel-selectors";
++import { RootState } from "../store";
+ export const getResourceUrl = (resource: Resource): string => {
+     switch (resource.kind) {
+         case ResourceKind.LEVEL_UP: return `/projects/${resource.ownerUuid}`;
+         case ResourceKind.PROJECT: return `/projects/${resource.uuid}`;
+         case ResourceKind.COLLECTION: return `/collections/${resource.uuid}`;
+         default:
+             return "#";
+     }
+ };
+ export enum ItemMode {
+     BOTH,
+     OPEN,
+     ACTIVE
+ }
 -    const openProjectItem = (resource: Resource) => {
 -        if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
 -            dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(resource.uuid));
++export const setProjectItem = (itemId: string, itemKind = ResourceKind.PROJECT, itemMode = ItemMode.OPEN) =>
++    (dispatch: Dispatch, getState: () => RootState) => {
++        const { projects } = getState();
 -        if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
 -            dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(resource.uuid));
 -        }
++        let treeItem = findTreeItem(projects.items, itemId);
++        if (treeItem && itemKind === ResourceKind.LEVEL_UP) {
++            treeItem = findTreeItem(projects.items, treeItem.data.ownerUuid);
+         }
 -        dispatch(push(getResourceUrl({...resource, kind: itemKind})));
++        if (treeItem) {
++            dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
 -    let treeItem = findTreeItem(projects, itemId);
 -    if (treeItem && itemKind === ResourceKind.LEVEL_UP) {
 -        treeItem = findTreeItem(projects, treeItem.data.ownerUuid);
 -    }
++            if (treeItem.status === TreeItemStatus.Loaded) {
++                dispatch<any>(openProjectItem(treeItem.data, itemKind, itemMode));
++            } else {
++                dispatch<any>(getProjectList(itemId))
++                    .then(() => dispatch<any>(openProjectItem(treeItem!.data, itemKind, itemMode)));
++            }
++            if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
++                dispatch<any>(getCollectionList(itemId));
++            }
++        }
+     };
 -    if (treeItem) {
 -        dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
++const openProjectItem = (resource: Resource, itemKind: ResourceKind, itemMode: ItemMode) =>
++    (dispatch: Dispatch, getState: () => RootState) => {
 -        if (treeItem.status === TreeItemStatus.Loaded) {
 -            openProjectItem(treeItem.data);
 -        } else {
 -            dispatch<any>(getProjectList(itemId))
 -                .then(() => openProjectItem(treeItem!.data));
++        const { collections, projects } = getState();
 -            dispatch<any>(getCollectionList(itemId));
++        if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
++            dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(resource.uuid));
+         }
++
+         if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
 -    }
 -};
++            dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(resource.uuid));
+         }
++
++        dispatch(push(getResourceUrl({ ...resource, kind: itemKind })));
++        dispatch(dataExplorerActions.SET_ITEMS({
++            id: PROJECT_PANEL_ID,
++            items: projectPanelItems(
++                projects.items,
++                resource.uuid,
++                collections
++            )
++        }));
++    };
index 7092c1d9e80c9740d22f73f308e21b999ae894f9,40b24a049d7bcb05cf320e466307a773e848a691..68c5d8238c74894857e08f7d34f8e0df90bcfb9b
@@@ -10,8 -10,7 +10,8 @@@ 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 collectionsReducer from "./collection/collection-reducer";
 +import dataExplorerReducer, { DataExplorerState } from './data-explorer/data-explorer-reducer';
+ import collectionsReducer, { CollectionState } from "./collection/collection-reducer";
  
  const composeEnhancers =
      (process.env.NODE_ENV === 'development' &&
@@@ -21,8 -20,8 +21,9 @@@
  export interface RootState {
      auth: AuthState;
      projects: ProjectState;
+     collections: CollectionState;
      router: RouterState;
 +    dataExplorer: DataExplorerState;
      sidePanel: SidePanelState;
  }
  
index 0000000000000000000000000000000000000000,4fa3d3d67ceccfabf20462450a8367a1e9199b2b..e0eb84f05ad4c16c810dd6a8b9e477b89ae11df7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,27 +1,29 @@@
 -export interface ProjectExplorerItem {
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
++import { TreeItem } from "../../components/tree/tree";
++import { Project } from "../../models/project";
+ import { getResourceKind, Resource, ResourceKind } from "../../models/resource";
++export interface ProjectPanelItem {
+     uuid: string;
+     name: string;
+     kind: ResourceKind;
+     url: string;
+     owner: string;
+     lastModified: string;
+     fileSize?: number;
+     status?: string;
+ }
+ function resourceToDataItem(r: Resource, kind?: ResourceKind) {
+     return {
+         uuid: r.uuid,
+         name: r.name,
+         kind: kind ? kind : getResourceKind(r.kind),
+         owner: r.ownerUuid,
+         lastModified: r.modifiedAt
+     };
+ }
index 0000000000000000000000000000000000000000,83bfd603898700502884c20529f7da671e031118..5571e91257653a9f3bfb297fa0601eefa931d262
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,58 +1,58 @@@
 -import { ProjectExplorerItem } from "../../views-components/project-explorer/project-explorer-item";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { TreeItem } from "../../components/tree/tree";
+ import { Project } from "../../models/project";
+ import { findTreeItem } from "../../store/project/project-reducer";
+ import { ResourceKind } from "../../models/resource";
+ import { Collection } from "../../models/collection";
+ import { getResourceUrl } from "../../store/navigation/navigation-action";
 -export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeItemId: string, collections: Array<Collection>): ProjectExplorerItem[] => {
 -    const dataItems: ProjectExplorerItem[] = [];
++import { ProjectPanelItem } from "./project-panel-item";
 -                } as ProjectExplorerItem;
++export const projectPanelItems = (projects: Array<TreeItem<Project>>, treeItemId: string, collections: Array<Collection>): ProjectPanelItem[] => {
++    const dataItems: ProjectPanelItem[] = [];
+     const treeItem = findTreeItem(projects, treeItemId);
+     if (treeItem) {
+         dataItems.push({
+             name: "..",
+             url: getResourceUrl(treeItem.data),
+             kind: ResourceKind.LEVEL_UP,
+             owner: "",
+             uuid: treeItem.data.uuid,
+             lastModified: ""
+         });
+         if (treeItem.items) {
+             treeItem.items.forEach(p => {
+                 const item = {
+                     name: p.data.name,
+                     kind: ResourceKind.PROJECT,
+                     url: getResourceUrl(treeItem.data),
+                     owner: p.data.ownerUuid,
+                     uuid: p.data.uuid,
+                     lastModified: p.data.modifiedAt
 -        } as ProjectExplorerItem;
++                } as ProjectPanelItem;
+                 dataItems.push(item);
+             });
+         }
+     }
+     collections.forEach(c => {
+         const item = {
+             name: c.name,
+             kind: ResourceKind.COLLECTION,
+             url: getResourceUrl(c),
+             owner: c.ownerUuid,
+             uuid: c.uuid,
+             lastModified: c.modifiedAt
++        } as ProjectPanelItem;
+         dataItems.push(item);
+     });
+     return dataItems;
+ };
index 16f670cb008d76b8d4cc3baaebe4ef57df32d843,df9721fda775546308052ce26625c1d4480b14dc..dbff20e18717d227909f524ec5748225cbe484eb
  // SPDX-License-Identifier: AGPL-3.0
  
  import * as React from 'react';
- import { ProjectExplorerItem } from './project-explorer-item';
 -import { RouteComponentProps } from 'react-router';
 -import { ProjectState } from '../../store/project/project-reducer';
 -import { RootState } from '../../store/store';
 -import { connect, DispatchProp } from 'react-redux';
 -import { CollectionState } from "../../store/collection/collection-reducer";
 -import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
 -import ProjectExplorer from "../../views-components/project-explorer/project-explorer";
 -import { projectExplorerItems } from "./project-panel-selectors";
 -import { ProjectExplorerItem } from "../../views-components/project-explorer/project-explorer-item";
 -import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 -import { DataColumn, SortDirection } from '../../components/data-table/data-column';
++import { ProjectPanelItem } from './project-panel-item';
 +import { Grid, Typography, Button, Toolbar, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 +import { formatDate, formatFileSize } from '../../common/formatters';
- import DataExplorer from '../data-explorer/data-explorer';
- import { DataColumn } from '../../components/data-table/data-column';
++import DataExplorer from "../../views-components/data-explorer/data-explorer";
++import { DataColumn, toggleSortDirection } from '../../components/data-table/data-column';
  import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
 +import { ContextMenuAction } from '../../components/context-menu/context-menu';
 +import { DispatchProp, connect } from 'react-redux';
 +import actions from "../../store/data-explorer/data-explorer-action";
++import { setProjectItem } from "../../store/navigation/navigation-action";
 +import { DataColumns } from '../../components/data-table/data-table';
++import { ResourceKind } from "../../models/resource";
  
- export const PROJECT_EXPLORER_ID = "projectExplorer";
- class ProjectExplorer extends React.Component<DispatchProp & WithStyles<CssRules>> {
 -interface ProjectPanelDataProps {
 -    projects: ProjectState;
 -    collections: CollectionState;
 -}
++export const PROJECT_PANEL_ID = "projectPanel";
++class ProjectPanel extends React.Component<DispatchProp & WithStyles<CssRules>> {
 +    render() {
 +        return <div>
 +            <div className={this.props.classes.toolbar}>
 +                <Button color="primary" variant="raised" className={this.props.classes.button}>
 +                    Create a collection
 +                </Button>
 +                <Button color="primary" variant="raised" className={this.props.classes.button}>
 +                    Run a process
 +                </Button>
 +                <Button color="primary" variant="raised" className={this.props.classes.button}>
 +                    Create a project
 +                </Button>
 +            </div>
 +            <DataExplorer
-                 id={PROJECT_EXPLORER_ID}
++                id={PROJECT_PANEL_ID}
 +                contextActions={contextMenuActions}
 +                onColumnToggle={this.toggleColumn}
 +                onFiltersChange={this.changeFilters}
-                 onRowClick={console.log}
++                onRowClick={this.openProject}
 +                onSortToggle={this.toggleSort}
 +                onSearch={this.search}
 +                onContextAction={this.executeAction}
 +                onChangePage={this.changePage}
 +                onChangeRowsPerPage={this.changeRowsPerPage} />;
 +        </div>;
 +    }
  
 -type ProjectPanelProps = ProjectPanelDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
 +    componentDidMount() {
-         this.props.dispatch(actions.SET_COLUMNS({ id: PROJECT_EXPLORER_ID, columns }));
++        this.props.dispatch(actions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
 +    }
  
-     toggleColumn = (toggledColumn: DataColumn<ProjectExplorerItem>) => {
-         this.props.dispatch(actions.TOGGLE_COLUMN({ id: PROJECT_EXPLORER_ID, columnName: toggledColumn.name }));
 -interface ProjectPanelState {
 -    sort: {
 -        columnName: string;
 -        direction: SortDirection;
 -    };
 -    filters: string[];
 -}
++    toggleColumn = (toggledColumn: DataColumn<ProjectPanelItem>) => {
++        this.props.dispatch(actions.TOGGLE_COLUMN({ id: PROJECT_PANEL_ID, columnName: toggledColumn.name }));
 +    }
  
-     toggleSort = (toggledColumn: DataColumn<ProjectExplorerItem>) => {
-         this.props.dispatch(actions.TOGGLE_SORT({ id: PROJECT_EXPLORER_ID, columnName: toggledColumn.name }));
 -class ProjectPanel extends React.Component<ProjectPanelProps & WithStyles<CssRules>, ProjectPanelState> {
 -    state: ProjectPanelState = {
 -        sort: {
 -            columnName: "Name",
 -            direction: "desc"
 -        },
 -        filters: ['collection', 'project']
 -    };
++    toggleSort = (column: DataColumn<ProjectPanelItem>) => {
++        this.props.dispatch(actions.TOGGLE_SORT({ id: PROJECT_PANEL_ID, columnName: column.name }));
 +    }
  
-     changeFilters = (filters: DataTableFilterItem[], updatedColumn: DataColumn<ProjectExplorerItem>) => {
-         this.props.dispatch(actions.SET_FILTERS({ id: PROJECT_EXPLORER_ID, columnName: updatedColumn.name, filters }));
 -    render() {
 -        const items = projectExplorerItems(
 -            this.props.projects.items,
 -            this.props.projects.currentItemId,
 -            this.props.collections
 -        );
 -        const [goBackItem, ...otherItems] = items;
 -        const filteredItems = otherItems.filter(i => this.state.filters.some(f => f === i.kind));
 -        const sortedItems = sortItems(this.state.sort, filteredItems);
 -        return (
 -            <div>
 -                <div className={this.props.classes.toolbar}>
 -                    <Button color="primary" variant="raised" className={this.props.classes.button}>
 -                        Create a collection
 -                    </Button>
 -                    <Button color="primary" variant="raised" className={this.props.classes.button}>
 -                        Run a process
 -                    </Button>
 -                    <Button color="primary" variant="raised" className={this.props.classes.button}>
 -                        Create a project
 -                    </Button>
 -                </div>
 -                <ProjectExplorer
 -                    items={goBackItem ? [goBackItem, ...sortedItems] : sortedItems}
 -                    onRowClick={this.goToItem}
 -                    onToggleSort={this.toggleSort}
 -                    onChangeFilters={this.changeFilters}
 -                />
 -            </div>
 -        );
++    changeFilters = (filters: DataTableFilterItem[], column: DataColumn<ProjectPanelItem>) => {
++        this.props.dispatch(actions.SET_FILTERS({ id: PROJECT_PANEL_ID, columnName: column.name, filters }));
      }
  
-     executeAction = (action: ContextMenuAction, item: ProjectExplorerItem) => {
 -    goToItem = (item: ProjectExplorerItem) => {
 -        this.props.dispatch<any>(setProjectItem(this.props.projects.items, item.uuid, item.kind, ItemMode.BOTH));
++    executeAction = (action: ContextMenuAction, item: ProjectPanelItem) => {
 +        alert(`Executing ${action.name} on ${item.name}`);
      }
  
 -    toggleSort = (column: DataColumn<ProjectExplorerItem>) => {
 -        this.setState({
 -            sort: {
 -                columnName: column.name,
 -                direction: column.sortDirection || "none"
 -            }
 -        });
 +    search = (searchValue: string) => {
-         this.props.dispatch(actions.SET_SEARCH_VALUE({ id: PROJECT_EXPLORER_ID, searchValue }));
++        this.props.dispatch(actions.SET_SEARCH_VALUE({ id: PROJECT_PANEL_ID, searchValue }));
      }
  
 -    changeFilters = (filters: DataTableFilterItem[]) => {
 -        this.setState({ filters: filters.filter(f => f.selected).map(f => f.name.toLowerCase()) });
 +    changePage = (page: number) => {
-         this.props.dispatch(actions.SET_PAGE({ id: PROJECT_EXPLORER_ID, page }));
++        this.props.dispatch(actions.SET_PAGE({ id: PROJECT_PANEL_ID, page }));
      }
 -}
  
 -const sortItems = (sort: { columnName: string, direction: SortDirection }, items: ProjectExplorerItem[]) => {
 -    const sortedItems = items.slice(0);
 -    const direction = sort.direction === "asc" ? -1 : 1;
 -    sortedItems.sort((a, b) => {
 -        if (sort.columnName === "Last modified") {
 -            return ((new Date(a.lastModified)).getTime() - (new Date(b.lastModified)).getTime()) * direction;
 -        } else {
 -            return a.name.localeCompare(b.name) * direction;
 -        }
 -    });
 -    return sortedItems;
 -};
 +    changeRowsPerPage = (rowsPerPage: number) => {
-         this.props.dispatch(actions.SET_ROWS_PER_PAGE({ id: PROJECT_EXPLORER_ID, rowsPerPage }));
++        this.props.dispatch(actions.SET_ROWS_PER_PAGE({ id: PROJECT_PANEL_ID, rowsPerPage }));
++    }
++
++    openProject = (item: ProjectPanelItem) => {
++        this.props.dispatch<any>(setProjectItem(item.uuid));
 +    }
 +}
  
  type CssRules = "toolbar" | "button";
  
@@@ -88,122 -116,10 +94,124 @@@ const styles: StyleRulesCallback<CssRul
      }
  });
  
- const renderName = (item: ProjectExplorerItem) =>
 -export default withStyles(styles)(
 -    connect(
 -        (state: RootState) => ({
 -            projects: state.projects,
 -            collections: state.collections
 -        })
 -    )(ProjectPanel));
++const renderName = (item: ProjectPanelItem) =>
 +    <Grid
 +        container
 +        alignItems="center"
 +        wrap="nowrap"
 +        spacing={16}>
 +        <Grid item>
 +            {renderIcon(item)}
 +        </Grid>
 +        <Grid item>
 +            <Typography color="primary">
 +                {item.name}
 +            </Typography>
 +        </Grid>
 +    </Grid>;
 +
- const renderIcon = (item: ProjectExplorerItem) => {
-     switch (item.type) {
-         case "arvados#group":
++
++const renderIcon = (item: ProjectPanelItem) => {
++    switch (item.kind) {
++        case ResourceKind.LEVEL_UP:
++            return <i className="icon-level-up" style={{ fontSize: "1rem" }} />;
++        case ResourceKind.PROJECT:
 +            return <i className="fas fa-folder fa-lg" />;
-         case "arvados#groupList":
++        case ResourceKind.COLLECTION:
 +            return <i className="fas fa-th fa-lg" />;
 +        default:
 +            return <i />;
 +    }
 +};
 +
 +const renderDate = (date: string) =>
 +    <Typography noWrap>
 +        {formatDate(date)}
 +    </Typography>;
 +
 +const renderFileSize = (fileSize?: number) =>
 +    <Typography noWrap>
 +        {formatFileSize(fileSize)}
 +    </Typography>;
 +
 +const renderOwner = (owner: string) =>
 +    <Typography noWrap color="primary">
 +        {owner}
 +    </Typography>;
 +
 +const renderType = (type: string) =>
 +    <Typography noWrap>
 +        {type}
 +    </Typography>;
 +
- const renderStatus = (item: ProjectExplorerItem) =>
++const renderStatus = (item: ProjectPanelItem) =>
 +    <Typography noWrap align="center">
 +        {item.status || "-"}
 +    </Typography>;
 +
- const columns: DataColumns<ProjectExplorerItem> = [{
++const columns: DataColumns<ProjectPanelItem> = [{
 +    name: "Name",
 +    selected: true,
-     sortDirection: "asc",
-     render: renderName
++    sortDirection: "desc",
++    render: renderName,
++    width: "450px"
 +}, {
 +    name: "Status",
 +    selected: true,
-     filters: [{
-         name: "In progress",
-         selected: true
-     }, {
-         name: "Complete",
-         selected: true
-     }],
-     render: renderStatus
++    render: renderStatus,
++    width: "75px"
 +}, {
 +    name: "Type",
 +    selected: true,
 +    filters: [{
 +        name: "Collection",
 +        selected: true
 +    }, {
-         name: "Group",
++        name: "Project",
 +        selected: true
 +    }],
-     render: item => renderType(item.type)
++    render: item => renderType(item.kind),
++    width: "125px"
 +}, {
 +    name: "Owner",
 +    selected: true,
-     render: item => renderOwner(item.owner)
++    render: item => renderOwner(item.owner),
++    width: "200px"
 +}, {
 +    name: "File size",
 +    selected: true,
-     sortDirection: "none",
-     render: item => renderFileSize(item.fileSize)
++    render: item => renderFileSize(item.fileSize),
++    width: "50px"
 +}, {
 +    name: "Last modified",
 +    selected: true,
-     render: item => renderDate(item.lastModified)
++    sortDirection: "none",
++    render: item => renderDate(item.lastModified),
++    width: "150px"
 +}];
 +
 +const contextMenuActions = [[{
 +    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 withStyles(styles)(connect()(ProjectExplorer));
++export default withStyles(styles)(connect()(ProjectPanel));
index 92cbc5d0995b757ab31579eff83c1489afe808e1,1069de530c02986b78d86859d3910d9cfab7b72d..72eb0ddcfefe759677f017667532cf43ce075d22
@@@ -8,21 -8,24 +8,25 @@@ import Drawer from '@material-ui/core/D
  import { connect, DispatchProp } from "react-redux";
  import { Route, Switch } from "react-router";
  import authActions from "../../store/auth/auth-action";
 +import dataExplorerActions from "../../store/data-explorer/data-explorer-action";
  import { User } from "../../models/user";
  import { RootState } from "../../store/store";
- import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
+ import MainAppBar, {
+     MainAppBarActionProps,
+     MainAppBarMenuItem
+ } from '../../views-components/main-app-bar/main-app-bar';
  import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
  import { push } from 'react-router-redux';
- import projectActions, { getProjectList } from "../../store/project/project-action";
  import ProjectTree from '../../views-components/project-tree/project-tree';
- import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
+ import { TreeItem } from "../../components/tree/tree";
  import { Project } from "../../models/project";
 -import { getTreePath } from '../../store/project/project-reducer';
 -import ProjectPanel from '../project-panel/project-panel';
 +import { getTreePath, findTreeItem } from '../../store/project/project-reducer';
- import ProjectExplorer, { PROJECT_EXPLORER_ID } from '../../views-components/project-explorer/project-explorer';
- import { ProjectExplorerItem, mapProjectTreeItem } from '../../views-components/project-explorer/project-explorer-item';
  import sidePanelActions from '../../store/side-panel/side-panel-action';
  import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel';
+ import { ResourceKind } from "../../models/resource";
+ import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
+ import projectActions from "../../store/project/project-action";
++import ProjectPanel from "../project-panel/project-panel";
  
  const drawerWidth = 240;
  const appBarHeight = 102;
@@@ -128,10 -130,11 +131,9 @@@ class Workbench extends React.Component
          }
      };
  
      mainAppBarActions: MainAppBarActionProps = {
-         onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => {
-             this.toggleProjectTreeItemOpen(itemId, status);
+         onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
 -            this.props.dispatch<any>(
 -                setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.BOTH)
 -            );
++            this.props.dispatch<any>(setProjectItem(itemId, ResourceKind.PROJECT, ItemMode.BOTH));
          },
          onSearch: searchText => {
              this.setState({ searchText });
                          <SidePanel
                              toggleOpen={this.toggleSidePanelOpen}
                              toggleActive={this.toggleSidePanelActive}
-                             sidePanelItems={sidePanelItems}>
+                             sidePanelItems={this.props.sidePanelItems}>
                              <ProjectTree
-                                 projects={projects}
-                                 toggleOpen={this.toggleProjectTreeItemOpen}
-                                 toggleActive={this.toggleProjectTreeItemActive} />
+                                 projects={this.props.projects}
+                                 toggleOpen={itemId =>
+                                     this.props.dispatch<any>(
 -                                        setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.OPEN)
++                                        setProjectItem(itemId, ResourceKind.PROJECT, ItemMode.OPEN)
+                                     )}
+                                 toggleActive={itemId =>
+                                     this.props.dispatch<any>(
 -                                        setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.ACTIVE)
++                                        setProjectItem(itemId, ResourceKind.PROJECT, ItemMode.ACTIVE)
+                                     )}
+                             />
                          </SidePanel>
                      </Drawer>}
                  <main className={classes.contentWrapper}>