Merge branch '13678-connect-data-explorer-to-the-store'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Sun, 1 Jul 2018 20:43:53 +0000 (22:43 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Sun, 1 Jul 2018 20:43:53 +0000 (22:43 +0200)
refs #13678

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

20 files changed:
src/components/column-selector/column-selector.test.tsx
src/components/column-selector/column-selector.tsx
src/components/data-explorer/data-explorer.tsx
src/components/data-table/data-table.test.tsx
src/index.tsx
src/models/resource.ts
src/store/data-explorer/data-explorer-action.ts [new file with mode: 0644]
src/store/data-explorer/data-explorer-reducer.test.tsx [new file with mode: 0644]
src/store/data-explorer/data-explorer-reducer.ts [new file with mode: 0644]
src/store/navigation/navigation-action.ts
src/store/project/project-action.ts
src/store/project/project-reducer.ts
src/store/side-panel/side-panel-action.ts
src/store/store.ts
src/views-components/data-explorer/data-explorer.tsx [new file with mode: 0644]
src/views-components/project-explorer/project-explorer.tsx [deleted file]
src/views/project-panel/project-panel-item.ts [moved from src/views-components/project-explorer/project-explorer-item.ts with 81% similarity]
src/views/project-panel/project-panel-selectors.ts
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.tsx

index b6c544fb6b0ed880c82a32fc2939bae1f8036665..c2835ad7e9819d8b95bb596eb5b50d99940319f2 100644 (file)
@@ -6,14 +6,14 @@ import * as React from "react";
 import { mount, configure } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import ColumnSelector, { ColumnSelectorProps, ColumnSelectorTrigger } from "./column-selector";
-import { DataColumn } from "../data-table/data-column";
 import { ListItem, Checkbox } from "@material-ui/core";
+import { DataColumns } from "../data-table/data-table";
 
 configure({ adapter: new Adapter() });
 
 describe("<ColumnSelector />", () => {
     it("shows only configurable columns", () => {
-        const columns: Array<DataColumn<void>> = [
+        const columns: DataColumns<void> = [
             {
                 name: "Column 1",
                 render: () => <span />,
@@ -38,7 +38,7 @@ describe("<ColumnSelector />", () => {
     });
 
     it("renders checked checkboxes next to selected columns", () => {
-        const columns: Array<DataColumn<void>> = [
+        const columns: DataColumns<void> = [
             {
                 name: "Column 1",
                 render: () => <span />,
@@ -63,7 +63,7 @@ describe("<ColumnSelector />", () => {
     });
 
     it("calls onColumnToggle with clicked column", () => {
-        const columns: Array<DataColumn<void>> = [
+        const columns: DataColumns<void> = [
             {
                 name: "Column 1",
                 render: () => <span />,
index e2286b00189396eed805d3d588ae73b96547f78a..b5dd43b85a43c1eeb298ce977726c9c784a111b3 100644 (file)
@@ -8,9 +8,10 @@ import MenuIcon from "@material-ui/icons/Menu";
 import { DataColumn, isColumnConfigurable } from '../data-table/data-column';
 import Popover from "../popover/popover";
 import { IconButtonProps } from '@material-ui/core/IconButton';
+import { DataColumns } from '../data-table/data-table';
 
 export interface ColumnSelectorProps {
-    columns: Array<DataColumn<any>>;
+    columns: DataColumns<any>;
     onColumnToggle: (column: DataColumn<any>) => void;
 }
 
index 557b0158ee365a33f34f70e38a61f04071b46dcc..ff51c71c796fece83395d1b99a627dec1eb05d33 100644 (file)
@@ -7,7 +7,7 @@ import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, Theme, WithStyles
 import MoreVertIcon from "@material-ui/icons/MoreVert";
 import ContextMenu, { ContextMenuActionGroup, ContextMenuAction } from "../../components/context-menu/context-menu";
 import ColumnSelector from "../../components/column-selector/column-selector";
-import DataTable from "../../components/data-table/data-table";
+import DataTable, { DataColumns } from "../../components/data-table/data-table";
 import { mockAnchorFromMouseEvent } from "../../components/popover/helpers";
 import { DataColumn } from "../../components/data-table/data-column";
 import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
@@ -15,10 +15,11 @@ import SearchInput from '../search-input/search-input';
 
 interface DataExplorerProps<T> {
     items: T[];
-    columns: Array<DataColumn<T>>;
+    columns: DataColumns<T>;
     contextActions: ContextMenuActionGroup[];
     searchValue: string;
     rowsPerPage: number;
+    rowsPerPageOptions?: number[];
     page: number;
     onSearch: (value: string) => void;
     onRowClick: (item: T) => void;
@@ -62,9 +63,7 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
                 </Grid>
             </Toolbar>
             <DataTable
-                columns={[
-                    ...this.props.columns,
-                    this.contextMenuColumn]}
+                columns={[...this.props.columns, this.contextMenuColumn]}
                 items={this.props.items}
                 onRowClick={(_, item: T) => this.props.onRowClick(item)}
                 onRowContextMenu={this.openContextMenu}
@@ -76,6 +75,7 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
                         <TablePagination
                             count={this.props.items.length}
                             rowsPerPage={this.props.rowsPerPage}
+                            rowsPerPageOptions={this.props.rowsPerPageOptions}
                             page={this.props.page}
                             onChangePage={this.changePage}
                             onChangeRowsPerPage={this.changeRowsPerPage}
@@ -119,11 +119,21 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
 
     renderContextMenuTrigger = (item: T) =>
         <Grid container justify="flex-end">
-            <IconButton onClick={event => this.openContextMenu(event, item)}>
+            <IconButton onClick={event => this.openContextMenuTrigger(event, item)}>
                 <MoreVertIcon />
             </IconButton>
         </Grid>
 
+    openContextMenuTrigger = (event: React.MouseEvent<HTMLElement>, item: T) => {
+        event.preventDefault();
+        this.setState({
+            contextMenu: {
+                anchorEl: event.currentTarget,
+                item
+            }
+        });
+    }
+
     contextMenuColumn = {
         name: "Actions",
         selected: true,
index 726972e0282f7774b2b5592243cf964f7fcbbddb..b9d112520acc33ec0083d05a6e022161711c08fb 100644 (file)
@@ -6,15 +6,14 @@ import * as React from "react";
 import { mount, configure } from "enzyme";
 import { TableHead, TableCell, Typography, TableBody, Button, TableSortLabel } from "@material-ui/core";
 import * as Adapter from "enzyme-adapter-react-16";
-import DataTable from "./data-table";
-import { DataColumn } from "./data-column";
+import DataTable, { DataColumns } from "./data-table";
 import DataTableFilters from "../data-table-filters/data-table-filters";
 
 configure({ adapter: new Adapter() });
 
 describe("<DataTable />", () => {
     it("shows only selected columns", () => {
-        const columns: Array<DataColumn<string>> = [
+        const columns: DataColumns<string> = [
             {
                 name: "Column 1",
                 render: () => <span />,
@@ -42,7 +41,7 @@ describe("<DataTable />", () => {
     });
 
     it("renders column name", () => {
-        const columns: Array<DataColumn<string>> = [
+        const columns: DataColumns<string> = [
             {
                 name: "Column 1",
                 render: () => <span />,
@@ -60,7 +59,7 @@ describe("<DataTable />", () => {
     });
 
     it("uses renderHeader instead of name prop", () => {
-        const columns: Array<DataColumn<string>> = [
+        const columns: DataColumns<string> = [
             {
                 name: "Column 1",
                 renderHeader: () => <span>Column Header</span>,
@@ -79,7 +78,7 @@ describe("<DataTable />", () => {
     });
 
     it("passes column key prop to corresponding cells", () => {
-        const columns: Array<DataColumn<string>> = [
+        const columns: DataColumns<string> = [
             {
                 name: "Column 1",
                 key: "column-1-key",
@@ -99,7 +98,7 @@ describe("<DataTable />", () => {
     });
 
     it("renders items", () => {
-        const columns: Array<DataColumn<string>> = [
+        const columns: DataColumns<string> = [
             {
                 name: "Column 1",
                 render: (item) => <Typography>{item}</Typography>,
@@ -123,7 +122,7 @@ describe("<DataTable />", () => {
     });
 
     it("passes sorting props to <TableSortLabel />", () => {
-        const columns: Array<DataColumn<string>> = [{
+        const columns: DataColumns<string> = [{
             name: "Column 1",
             sortDirection: "asc",
             selected: true,
@@ -143,7 +142,7 @@ describe("<DataTable />", () => {
     });
 
     it("passes filter props to <DataTableFilter />", () => {
-        const columns: Array<DataColumn<string>> = [{
+        const columns: DataColumns<string> = [{
             name: "Column 1",
             sortDirection: "asc",
             selected: true,
index bc9f903d288765b70470fba499b416902df07204..580487846f05f9074068b20761c3b81579768f5b 100644 (file)
@@ -18,21 +18,7 @@ import { getProjectList } from "./store/project/project-action";
 
 const history = createBrowserHistory();
 
-const store = configureStore({
-    projects: {
-        items: [],
-        currentItemId: ""
-    },
-    collections: [
-    ],
-    router: {
-        location: null
-    },
-    auth: {
-        user: undefined
-    },
-    sidePanel: []
-}, history);
+const store = configureStore(history);
 
 store.dispatch(authActions.INIT());
 const rootUuid = authService.getRootUuid();
index 0f5fbc28f68cbd151857c31a59d9b634626a59a2..1dd5979b521a005af1ff94fbf219f6dee3138de8 100644 (file)
@@ -16,15 +16,18 @@ export enum ResourceKind {
     PROJECT = "project",
     COLLECTION = "collection",
     PIPELINE = "pipeline",
-    LEVEL_UP = "",
     UNKNOWN = "unknown"
 }
 
 export function getResourceKind(itemKind: string) {
     switch (itemKind) {
-        case "arvados#project": return ResourceKind.PROJECT;
-        case "arvados#collection": return ResourceKind.COLLECTION;
-        case "arvados#pipeline": return ResourceKind.PIPELINE;
+        case "arvados#project":
+        case "arvados#group":
+            return ResourceKind.PROJECT;
+        case "arvados#collection":
+            return ResourceKind.COLLECTION;
+        case "arvados#pipeline":
+            return ResourceKind.PIPELINE;
         default:
             return ResourceKind.UNKNOWN;
     }
diff --git a/src/store/data-explorer/data-explorer-action.ts b/src/store/data-explorer/data-explorer-action.ts
new file mode 100644 (file)
index 0000000..fd3a7af
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { default as unionize, ofType, UnionOf } from "unionize";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+import { DataColumns } from "../../components/data-table/data-table";
+
+const actions = unionize({
+    SET_COLUMNS: ofType<{id: string, columns: DataColumns<any> }>(),
+    SET_FILTERS: ofType<{id: string,columnName: string, filters: DataTableFilterItem[]}>(),
+    SET_ITEMS: ofType<{id: string,items: any[]}>(),
+    SET_PAGE: ofType<{id: string,page: number}>(),
+    SET_ROWS_PER_PAGE: ofType<{id: string,rowsPerPage: number}>(),
+    TOGGLE_COLUMN: ofType<{id: string, columnName: string }>(),
+    TOGGLE_SORT: ofType<{id: string, columnName: string }>(),
+    SET_SEARCH_VALUE: ofType<{id: string,searchValue: string}>()
+}, { tag: "type", value: "payload" });
+
+export type DataExplorerAction = UnionOf<typeof actions>;
+
+export default actions;
diff --git a/src/store/data-explorer/data-explorer-reducer.test.tsx b/src/store/data-explorer/data-explorer-reducer.test.tsx
new file mode 100644 (file)
index 0000000..0eb3c32
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import dataExplorerReducer, { initialDataExplorer } from "./data-explorer-reducer";
+import actions from "./data-explorer-action";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+import { DataColumns } from "../../components/data-table/data-table";
+
+describe('data-explorer-reducer', () => {
+    it('should set columns', () => {
+        const columns: DataColumns<any> = [{
+            name: "Column 1",
+            render: jest.fn(),
+            selected: true
+        }];
+        const state = dataExplorerReducer(undefined,
+            actions.SET_COLUMNS({ id: "Data explorer", columns }));
+        expect(state["Data explorer"].columns).toEqual(columns);
+    });
+
+    it('should toggle sorting', () => {
+        const columns: DataColumns<any> = [{
+            name: "Column 1",
+            render: jest.fn(),
+            selected: true,
+            sortDirection: "asc"
+        }, {
+            name: "Column 2",
+            render: jest.fn(),
+            selected: true,
+            sortDirection: "none",
+        }];
+        const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
+            actions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
+        expect(state["Data explorer"].columns[0].sortDirection).toEqual("none");
+        expect(state["Data explorer"].columns[1].sortDirection).toEqual("asc");
+    });
+
+    it('should set filters', () => {
+        const columns: DataColumns<any> = [{
+            name: "Column 1",
+            render: jest.fn(),
+            selected: true,
+        }];
+
+        const filters: DataTableFilterItem[] = [{
+            name: "Filter 1",
+            selected: true
+        }];
+        const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
+            actions.SET_FILTERS({ id: "Data explorer", columnName: "Column 1", filters }));
+        expect(state["Data explorer"].columns[0].filters).toEqual(filters);
+    });
+
+    it('should set items', () => {
+        const state = dataExplorerReducer({ "Data explorer": undefined },
+            actions.SET_ITEMS({ id: "Data explorer", items: ["Item 1", "Item 2"] }));
+        expect(state["Data explorer"].items).toEqual(["Item 1", "Item 2"]);
+    });
+
+    it('should set page', () => {
+        const state = dataExplorerReducer({ "Data explorer": undefined },
+            actions.SET_PAGE({ id: "Data explorer", page: 2 }));
+        expect(state["Data explorer"].page).toEqual(2);
+    });
+    
+    it('should set rows per page', () => {
+        const state = dataExplorerReducer({ "Data explorer": undefined },
+            actions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
+        expect(state["Data explorer"].rowsPerPage).toEqual(5);
+    });
+});
diff --git a/src/store/data-explorer/data-explorer-reducer.ts b/src/store/data-explorer/data-explorer-reducer.ts
new file mode 100644 (file)
index 0000000..efb45da
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataColumn, toggleSortDirection, resetSortDirection } from "../../components/data-table/data-column";
+import actions, { DataExplorerAction } from "./data-explorer-action";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+import { DataColumns } from "../../components/data-table/data-table";
+
+interface DataExplorer {
+    columns: DataColumns<any>;
+    items: any[];
+    page: number;
+    rowsPerPage: number;
+    rowsPerPageOptions?: number[];
+    searchValue: string;
+}
+
+export const initialDataExplorer: DataExplorer = {
+    columns: [],
+    items: [],
+    page: 0,
+    rowsPerPage: 10,
+    rowsPerPageOptions: [5, 10, 25, 50],
+    searchValue: ""
+};
+
+export type DataExplorerState = Record<string, DataExplorer | undefined>;
+
+const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
+    actions.match(action, {
+        SET_COLUMNS: ({ id, columns }) =>
+            update(state, id, setColumns(columns)),
+
+        SET_FILTERS: ({ id, columnName, filters }) =>
+            update(state, id, mapColumns(setFilters(columnName, filters))),
+
+        SET_ITEMS: ({ id, items }) =>
+            update(state, id, explorer => ({ ...explorer, items })),
+
+        SET_PAGE: ({ id, page }) =>
+            update(state, id, explorer => ({ ...explorer, page })),
+
+        SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) =>
+            update(state, id, explorer => ({ ...explorer, rowsPerPage })),
+
+        TOGGLE_SORT: ({ id, columnName }) =>
+            update(state, id, mapColumns(toggleSort(columnName))),
+
+        TOGGLE_COLUMN: ({ id, columnName }) =>
+            update(state, id, mapColumns(toggleColumn(columnName))),
+
+        default: () => state
+    });
+
+export default dataExplorerReducer;
+
+export const getDataExplorer = (state: DataExplorerState, id: string) =>
+    state[id] || initialDataExplorer;
+
+const update = (state: DataExplorerState, id: string, updateFn: (dataExplorer: DataExplorer) => DataExplorer) =>
+    ({ ...state, [id]: updateFn(getDataExplorer(state, id)) });
+
+const setColumns = (columns: DataColumns<any>) =>
+    (dataExplorer: DataExplorer) =>
+        ({ ...dataExplorer, columns });
+
+const mapColumns = (mapFn: (column: DataColumn<any>) => DataColumn<any>) =>
+    (dataExplorer: DataExplorer) =>
+        ({ ...dataExplorer, columns: dataExplorer.columns.map(mapFn) });
+
+const toggleSort = (columnName: string) =>
+    (column: DataColumn<any>) => column.name === columnName
+        ? toggleSortDirection(column)
+        : resetSortDirection(column);
+
+const toggleColumn = (columnName: string) =>
+    (column: DataColumn<any>) => column.name === columnName
+        ? { ...column, selected: !column.selected }
+        : column;
+
+const setFilters = (columnName: string, filters: DataTableFilterItem[]) =>
+    (column: DataColumn<any>) => column.name === columnName
+        ? { ...column, filters }
+        : column;
index 0b4bcdf87ab033421319eaa472a3ea15449ea296..daeb26fd2a9ee0d9e0b4ab7f8dd5441e93b77412 100644 (file)
@@ -5,20 +5,22 @@
 import { Dispatch } from "redux";
 import projectActions, { getProjectList } from "../project/project-action";
 import { push } from "react-router-redux";
-import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
+import { TreeItemStatus } from "../../components/tree/tree";
 import { getCollectionList } from "../collection/collection-action";
 import { findTreeItem } from "../project/project-reducer";
-import { Project } from "../../models/project";
 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";
+import { sidePanelData } from "../side-panel/side-panel-reducer";
 
 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 "#";
+        default: return "";
     }
 };
 
@@ -28,36 +30,49 @@ export enum ItemMode {
     ACTIVE
 }
 
-export const setProjectItem = (projects: Array<TreeItem<Project>>, itemId: string, itemKind: ResourceKind, itemMode: ItemMode) => (dispatch: Dispatch) => {
+export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const { projects, router, sidePanel } = getState();
+        const treeItem = findTreeItem(projects.items, itemId);
 
-    const openProjectItem = (resource: Resource) => {
-        if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
-            dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(resource.uuid));
-        }
+        if (treeItem) {
 
-        if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
-            dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(resource.uuid));
-        }
+            dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY());
+            const projectsItem = sidePanelData[0];
+            if(sidePanel.some(item => item.id === projectsItem.id && !item.open)){
+                dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(projectsItem.id));
+            }
 
-        dispatch(push(getResourceUrl({...resource, kind: itemKind})));
-    };
+            if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
+                dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(treeItem.data.uuid));
+            }
 
-    let treeItem = findTreeItem(projects, itemId);
-    if (treeItem && itemKind === ResourceKind.LEVEL_UP) {
-        treeItem = findTreeItem(projects, treeItem.data.ownerUuid);
-    }
+            const resourceUrl = getResourceUrl({ ...treeItem.data });
 
-    if (treeItem) {
-        dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
+            if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
+                if (router.location && !router.location.pathname.includes(resourceUrl)) {
+                    dispatch(push(resourceUrl));
+                }
+                dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
+            }
+
+            const promise = treeItem.status === TreeItemStatus.Loaded
+                ? Promise.resolve()
+                : dispatch<any>(getProjectList(itemId));
+
+            promise
+                .then(() => dispatch<any>(getCollectionList(itemId)))
+                .then(() => dispatch<any>(() => {
+                    const { projects, collections } = getState();
+                    dispatch(dataExplorerActions.SET_ITEMS({
+                        id: PROJECT_PANEL_ID,
+                        items: projectPanelItems(
+                            projects.items,
+                            treeItem.data.uuid,
+                            collections
+                        )
+                    }));
+                }));
 
-        if (treeItem.status === TreeItemStatus.Loaded) {
-            openProjectItem(treeItem.data);
-        } else {
-            dispatch<any>(getProjectList(itemId))
-                .then(() => openProjectItem(treeItem!.data));
-        }
-        if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
-            dispatch<any>(getCollectionList(itemId));
         }
-    }
-};
+    };
index 3c264d3ef9fcbba5089a9294a65b3aec0d863c5b..35ff445e43d0d09c660c8ea22f68e2d3cc9d6030 100644 (file)
@@ -14,7 +14,7 @@ const actions = unionize({
     PROJECTS_SUCCESS: ofType<{ projects: Project[], parentItemId?: string }>(),
     TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType<string>(),
     TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType<string>(),
-    RESET_PROJECT_TREE_ACTIVITY: ofType<string>(),
+    RESET_PROJECT_TREE_ACTIVITY: ofType<string>()
 }, {
         tag: 'type',
         value: 'payload'
index 0e2018b41c3830784c24b9615ad8b08db40cf70c..efef80992189c39a2d748a477cc972f3b1bf696b 100644 (file)
@@ -132,6 +132,7 @@ const projectsReducer = (state: ProjectState = { items: [], currentItemId: "" },
             resetTreeActivity(items);
             const item = findTreeItem(items, itemId);
             if (item) {
+                item.toggled = true;
                 item.active = true;
             }
             return {
index 32fa653bfe0427638fa03ac63ddb46a68c77b54d..6a83946cc8fcdf75e1ebbd7bb4abcce3951a3d41 100644 (file)
@@ -7,7 +7,7 @@ import { default as unionize, ofType, UnionOf } from "unionize";
 const actions = unionize({
     TOGGLE_SIDE_PANEL_ITEM_OPEN: ofType<string>(),
     TOGGLE_SIDE_PANEL_ITEM_ACTIVE: ofType<string>(),
-    RESET_SIDE_PANEL_ACTIVITY: ofType<string>(),
+    RESET_SIDE_PANEL_ACTIVITY: ofType<{}>(),
 }, {
     tag: 'type',
     value: 'payload'
index 40b24a049d7bcb05cf320e466307a773e848a691..68c5d8238c74894857e08f7d34f8e0df90bcfb9b 100644 (file)
@@ -10,6 +10,7 @@ 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 collectionsReducer, { CollectionState } from "./collection/collection-reducer";
 
 const composeEnhancers =
@@ -22,6 +23,7 @@ export interface RootState {
     projects: ProjectState;
     collections: CollectionState;
     router: RouterState;
+    dataExplorer: DataExplorerState;
     sidePanel: SidePanelState;
 }
 
@@ -30,15 +32,16 @@ const rootReducer = combineReducers({
     projects: projectsReducer,
     collections: collectionsReducer,
     router: routerReducer,
+    dataExplorer: dataExplorerReducer,
     sidePanel: sidePanelReducer
 });
 
 
-export default function configureStore(initialState: RootState, history: History) {
+export default function configureStore(history: History) {
     const middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware
     ];
     const enhancer = composeEnhancers(applyMiddleware(...middlewares));
-    return createStore(rootReducer, initialState!, enhancer);
+    return createStore(rootReducer, enhancer);
 }
diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx
new file mode 100644 (file)
index 0000000..d9d1fc4
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from "react-redux";
+import { RootState } from "../../store/store";
+import DataExplorer from "../../components/data-explorer/data-explorer";
+import { getDataExplorer } from "../../store/data-explorer/data-explorer-reducer";
+
+export default connect((state: RootState, props: { id: string }) =>
+    getDataExplorer(state.dataExplorer, props.id)
+)(DataExplorer);
diff --git a/src/views-components/project-explorer/project-explorer.tsx b/src/views-components/project-explorer/project-explorer.tsx
deleted file mode 100644 (file)
index 556b23b..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { ProjectExplorerItem } from './project-explorer-item';
-import { Grid, Typography } from '@material-ui/core';
-import { formatDate, formatFileSize } from '../../common/formatters';
-import DataExplorer from '../../components/data-explorer/data-explorer';
-import { DataColumn, toggleSortDirection, resetSortDirection } 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 { ResourceKind } from "../../models/resource";
-
-export interface ProjectExplorerContextActions {
-    onAddToFavourite: (item: ProjectExplorerItem) => void;
-    onCopy: (item: ProjectExplorerItem) => void;
-    onDownload: (item: ProjectExplorerItem) => void;
-    onMoveTo: (item: ProjectExplorerItem) => void;
-    onRemove: (item: ProjectExplorerItem) => void;
-    onRename: (item: ProjectExplorerItem) => void;
-    onShare: (item: ProjectExplorerItem) => void;
-}
-
-interface ProjectExplorerProps {
-    items: ProjectExplorerItem[];
-    onRowClick: (item: ProjectExplorerItem) => void;
-    onToggleSort: (toggledColumn: DataColumn<ProjectExplorerItem>) => void;
-    onChangeFilters: (filters: DataTableFilterItem[]) => void;
-}
-
-interface ProjectExplorerState {
-    columns: Array<DataColumn<ProjectExplorerItem>>;
-    searchValue: string;
-    page: number;
-    rowsPerPage: number;
-}
-
-class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplorerState> {
-    state: ProjectExplorerState = {
-        searchValue: "",
-        page: 0,
-        rowsPerPage: 10,
-        columns: [{
-            name: "Name",
-            selected: true,
-            sortDirection: "desc",
-            render: renderName,
-            width: "450px"
-        }, {
-            name: "Status",
-            selected: true,
-            render: renderStatus,
-            width: "75px"
-        }, {
-            name: "Type",
-            selected: true,
-            filters: [{
-                name: "Collection",
-                selected: true
-            }, {
-                name: "Project",
-                selected: true
-            }],
-            render: item => renderType(item.kind),
-            width: "125px"
-        }, {
-            name: "Owner",
-            selected: true,
-            render: item => renderOwner(item.owner),
-            width: "200px"
-        }, {
-            name: "File size",
-            selected: true,
-            render: item => renderFileSize(item.fileSize),
-            width: "50px"
-        }, {
-            name: "Last modified",
-            selected: true,
-            sortDirection: "none",
-            render: item => renderDate(item.lastModified),
-            width: "150px"
-        }]
-    };
-
-    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"
-    }
-    ]];
-
-    render() {
-        return <DataExplorer
-            items={this.props.items}
-            columns={this.state.columns}
-            contextActions={this.contextMenuActions}
-            searchValue={this.state.searchValue}
-            page={this.state.page}
-            rowsPerPage={this.state.rowsPerPage}
-            onColumnToggle={this.toggleColumn}
-            onFiltersChange={this.changeFilters}
-            onRowClick={this.props.onRowClick}
-            onSortToggle={this.toggleSort}
-            onSearch={this.search}
-            onContextAction={this.executeAction}
-            onChangePage={this.changePage}
-            onChangeRowsPerPage={this.changeRowsPerPage} />;
-    }
-
-    toggleColumn = (toggledColumn: DataColumn<ProjectExplorerItem>) => {
-        this.setState({
-            columns: this.state.columns.map(column =>
-                column.name === toggledColumn.name
-                    ? { ...column, selected: !column.selected }
-                    : column
-            )
-        });
-    }
-
-    toggleSort = (column: DataColumn<ProjectExplorerItem>) => {
-        const columns = this.state.columns.map(c =>
-            c.name === column.name
-                ? toggleSortDirection(c)
-                : resetSortDirection(c)
-        );
-        this.setState({ columns });
-        const toggledColumn = columns.find(c => c.name === column.name);
-        if (toggledColumn) {
-            this.props.onToggleSort(toggledColumn);
-        }
-    }
-
-    changeFilters = (filters: DataTableFilterItem[], updatedColumn: DataColumn<ProjectExplorerItem>) => {
-        this.setState({
-            columns: this.state.columns.map(column =>
-                column.name === updatedColumn.name
-                    ? { ...column, filters }
-                    : column
-            )
-        });
-        this.props.onChangeFilters(filters);
-    }
-
-    executeAction = (action: ContextMenuAction, item: ProjectExplorerItem) => {
-        alert(`Executing ${action.name} on ${item.name}`);
-    }
-
-    search = (searchValue: string) => {
-        this.setState({ searchValue });
-    }
-
-    changePage = (page: number) => {
-        this.setState({ page });
-    }
-
-    changeRowsPerPage = (rowsPerPage: number) => {
-        this.setState({ rowsPerPage });
-    }
-}
-
-const renderName = (item: ProjectExplorerItem) =>
-    <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.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 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) =>
-    <Typography noWrap align="center">
-        {item.status || "-"}
-    </Typography>;
-
-export default ProjectExplorer;
similarity index 81%
rename from src/views-components/project-explorer/project-explorer-item.ts
rename to src/views/project-panel/project-panel-item.ts
index 4fa3d3d67ceccfabf20462450a8367a1e9199b2b..e0eb84f05ad4c16c810dd6a8b9e477b89ae11df7 100644 (file)
@@ -2,9 +2,11 @@
 //
 // 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 ProjectExplorerItem {
+export interface ProjectPanelItem {
     uuid: string;
     name: string;
     kind: ResourceKind;
index 83bfd603898700502884c20529f7da671e031118..ee039a807a388c087584398c1092b851de07dfe6 100644 (file)
@@ -8,22 +8,13 @@ import { findTreeItem } from "../../store/project/project-reducer";
 import { ResourceKind } from "../../models/resource";
 import { Collection } from "../../models/collection";
 import { getResourceUrl } from "../../store/navigation/navigation-action";
-import { ProjectExplorerItem } from "../../views-components/project-explorer/project-explorer-item";
+import { ProjectPanelItem } from "./project-panel-item";
 
-export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeItemId: string, collections: Array<Collection>): ProjectExplorerItem[] => {
-    const dataItems: 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 = {
@@ -33,7 +24,7 @@ export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeIte
                     owner: p.data.ownerUuid,
                     uuid: p.data.uuid,
                     lastModified: p.data.modifiedAt
-                } as ProjectExplorerItem;
+                } as ProjectPanelItem;
 
                 dataItems.push(item);
             });
@@ -48,7 +39,7 @@ export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeIte
             owner: c.ownerUuid,
             uuid: c.uuid,
             lastModified: c.modifiedAt
-        } as ProjectExplorerItem;
+        } as ProjectPanelItem;
 
         dataItems.push(item);
     });
index df9721fda775546308052ce26625c1d4480b14dc..6bfa61e0322de57f6040207448a5c8855759ad19 100644 (file)
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+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 "../../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 { DataColumns } from '../../components/data-table/data-table';
+import { ResourceKind } from "../../models/resource";
 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 { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
 
-interface ProjectPanelDataProps {
-    projects: ProjectState;
-    collections: CollectionState;
+export const PROJECT_PANEL_ID = "projectPanel";
+
+type ProjectPanelProps = {
+    currentItemId: string,
+    onItemClick: (item: ProjectPanelItem) => void,
+    onItemRouteChange: (itemId: string) => void
 }
+    & DispatchProp
+    & WithStyles<CssRules>
+    & RouteComponentProps<{ id: string }>;
+class ProjectPanel extends React.Component<ProjectPanelProps> {
+    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_PANEL_ID}
+                contextActions={contextMenuActions}
+                onColumnToggle={this.toggleColumn}
+                onFiltersChange={this.changeFilters}
+                onRowClick={this.props.onItemClick}
+                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_PANEL_ID, columns }));
+    }
 
-interface ProjectPanelState {
-    sort: {
-        columnName: string;
-        direction: SortDirection;
-    };
-    filters: string[];
-}
+    componentWillReceiveProps({ match, currentItemId }: ProjectPanelProps) {
+        if (match.params.id !== currentItemId) {
+            this.props.onItemRouteChange(match.params.id);
+        }
+    }
 
-class ProjectPanel extends React.Component<ProjectPanelProps & WithStyles<CssRules>, ProjectPanelState> {
-    state: ProjectPanelState = {
-        sort: {
-            columnName: "Name",
-            direction: "desc"
-        },
-        filters: ['collection', 'project']
-    };
+    toggleColumn = (toggledColumn: DataColumn<ProjectPanelItem>) => {
+        this.props.dispatch(actions.TOGGLE_COLUMN({ id: PROJECT_PANEL_ID, columnName: toggledColumn.name }));
+    }
 
-    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>
-        );
+    toggleSort = (column: DataColumn<ProjectPanelItem>) => {
+        this.props.dispatch(actions.TOGGLE_SORT({ id: PROJECT_PANEL_ID, columnName: column.name }));
     }
 
-    goToItem = (item: ProjectExplorerItem) => {
-        this.props.dispatch<any>(setProjectItem(this.props.projects.items, item.uuid, item.kind, ItemMode.BOTH));
+    changeFilters = (filters: DataTableFilterItem[], column: DataColumn<ProjectPanelItem>) => {
+        this.props.dispatch(actions.SET_FILTERS({ id: PROJECT_PANEL_ID, columnName: column.name, filters }));
     }
 
-    toggleSort = (column: DataColumn<ProjectExplorerItem>) => {
-        this.setState({
-            sort: {
-                columnName: column.name,
-                direction: column.sortDirection || "none"
-            }
-        });
+    executeAction = (action: ContextMenuAction, item: ProjectPanelItem) => {
+        alert(`Executing ${action.name} on ${item.name}`);
     }
 
-    changeFilters = (filters: DataTableFilterItem[]) => {
-        this.setState({ filters: filters.filter(f => f.selected).map(f => f.name.toLowerCase()) });
+    search = (searchValue: string) => {
+        this.props.dispatch(actions.SET_SEARCH_VALUE({ id: PROJECT_PANEL_ID, searchValue }));
     }
-}
 
-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;
-};
+    changePage = (page: number) => {
+        this.props.dispatch(actions.SET_PAGE({ id: PROJECT_PANEL_ID, page }));
+    }
+
+    changeRowsPerPage = (rowsPerPage: number) => {
+        this.props.dispatch(actions.SET_ROWS_PER_PAGE({ id: PROJECT_PANEL_ID, rowsPerPage }));
+    }
+
+}
 
 type CssRules = "toolbar" | "button";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     toolbar: {
-        marginBottom: theme.spacing.unit * 3,
-        display: "flex",
-        justifyContent: "flex-end"
+        paddingBottom: theme.spacing.unit * 3,
+        textAlign: "right"
     },
     button: {
         marginLeft: theme.spacing.unit
     }
 });
 
+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: ProjectPanelItem) => {
+    switch (item.kind) {
+        case ResourceKind.PROJECT:
+            return <i className="fas fa-folder fa-lg" />;
+        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: ProjectPanelItem) =>
+    <Typography noWrap align="center">
+        {item.status || "-"}
+    </Typography>;
+
+const columns: DataColumns<ProjectPanelItem> = [{
+    name: "Name",
+    selected: true,
+    sortDirection: "desc",
+    render: renderName,
+    width: "450px"
+}, {
+    name: "Status",
+    selected: true,
+    render: renderStatus,
+    width: "75px"
+}, {
+    name: "Type",
+    selected: true,
+    filters: [{
+        name: "Collection",
+        selected: true
+    }, {
+        name: "Project",
+        selected: true
+    }],
+    render: item => renderType(item.kind),
+    width: "125px"
+}, {
+    name: "Owner",
+    selected: true,
+    render: item => renderOwner(item.owner),
+    width: "200px"
+}, {
+    name: "File size",
+    selected: true,
+    render: item => renderFileSize(item.fileSize),
+    width: "50px"
+}, {
+    name: "Last modified",
+    selected: true,
+    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(
-        (state: RootState) => ({
-            projects: state.projects,
-            collections: state.collections
-        })
-    )(ProjectPanel));
+    connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
+        ProjectPanel));
index 1069de530c02986b78d86859d3910d9cfab7b72d..b8baeadc630988f70a8c6234eefb9df2a5ca4cb0 100644 (file)
@@ -6,8 +6,9 @@ import * as React from 'react';
 import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles';
 import Drawer from '@material-ui/core/Drawer';
 import { connect, DispatchProp } from "react-redux";
-import { Route, Switch } from "react-router";
+import { Route, Switch, RouteComponentProps, withRouter } 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, {
@@ -19,13 +20,14 @@ import { push } from 'react-router-redux';
 import ProjectTree from '../../views-components/project-tree/project-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 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";
+import { sidePanelData } from '../../store/side-panel/side-panel-reducer';
 
 const drawerWidth = 240;
 const appBarHeight = 102;
@@ -132,9 +134,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
 
     mainAppBarActions: MainAppBarActionProps = {
         onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
-            this.props.dispatch<any>(
-                setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.BOTH)
-            );
+            this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
         },
         onSearch: searchText => {
             this.setState({ searchText });
@@ -153,8 +153,8 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
     }
 
     render() {
-        const branch = getTreePath(this.props.projects, this.props.currentProjectId);
-        const breadcrumbs = branch.map(item => ({
+        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
@@ -185,27 +185,27 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
                             sidePanelItems={this.props.sidePanelItems}>
                             <ProjectTree
                                 projects={this.props.projects}
-                                toggleOpen={itemId =>
-                                    this.props.dispatch<any>(
-                                        setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.OPEN)
-                                    )}
-                                toggleActive={itemId =>
-                                    this.props.dispatch<any>(
-                                        setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.ACTIVE)
-                                    )}
+                                toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
+                                toggleActive={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
                             />
                         </SidePanel>
                     </Drawer>}
                 <main className={classes.contentWrapper}>
                     <div className={classes.content}>
                         <Switch>
-                            <Route path="/projects/:name" component={ProjectPanel} />
+                            <Route path="/projects/:id" render={this.renderProjectPanel} />
                         </Switch>
                     </div>
                 </main>
             </div>
         );
     }
+
+    renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
+        onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
+        onItemClick={item => this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE))}
+        {...props} />
+
 }
 
 export default connect<WorkbenchDataProps>(