refs #13666 Merge branch 'origin/13666-data-explorer-mapper'
authorDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 26 Jun 2018 10:20:38 +0000 (12:20 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 26 Jun 2018 10:20:56 +0000 (12:20 +0200)
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

17 files changed:
src/index.tsx
src/models/resource.ts
src/services/auth-service/auth-service.ts
src/services/collection-service/collection-service.ts
src/services/project-service/project-service.ts
src/store/collection/collection-reducer.test.ts
src/store/collection/collection-reducer.ts
src/store/navigation/navigation-action.ts [new file with mode: 0644]
src/store/project/project-reducer.test.ts
src/store/project/project-reducer.ts
src/store/store.ts
src/views-components/project-explorer/project-explorer-item.ts
src/views-components/project-explorer/project-explorer.tsx
src/views/project-panel/project-panel-selectors.ts
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.tsx
tslint.json

index ba395e8b785ab49dd6255427ff90382b43ff6191..bc9f903d288765b70470fba499b416902df07204 100644 (file)
@@ -19,7 +19,11 @@ import { getProjectList } from "./store/project/project-action";
 const history = createBrowserHistory();
 
 const store = configureStore({
-    projects: [
+    projects: {
+        items: [],
+        currentItemId: ""
+    },
+    collections: [
     ],
     router: {
         location: null
index 39b4e915cad26f9b4919e38a8c25aadd6e077512..28bb349d82761c08a630b6fd1a8d752908434d04 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 export interface Resource {
     name: string;
     createdAt: string;
@@ -5,5 +9,23 @@ export interface Resource {
     uuid: string;
     ownerUuid: string;
     href: string;
-    kind: string;
+    kind: ResourceKind;
+}
+
+export enum ResourceKind {
+    PROJECT = "project",
+    COLLECTION = "collection",
+    PIPELINE = "pipeline",
+    LEVEL_UP = "levelup",
+    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;
+        default:
+            return ResourceKind.UNKNOWN;
+    }
 }
index d71f0299aa6cd866d30da96dd59fd69e17b94f03..e953a75d14aabbcd52a2d61fcf32e260d83717f3 100644 (file)
@@ -35,6 +35,10 @@ export default class AuthService {
         return localStorage.getItem(API_TOKEN_KEY) || undefined;
     }
 
+    public getUuid() {
+        return localStorage.getItem(USER_UUID_KEY) || undefined;
+    }
+
     public getOwnerUuid() {
         return localStorage.getItem(USER_OWNER_UUID_KEY) || undefined;
     }
index 171cd8565ffc503a2137f56476997356fc1e4b11..bc9128171254eebe3b9a42c5cfad5ea86ec56856 100644 (file)
@@ -6,6 +6,7 @@ import { serverApi } from "../../common/api/server-api";
 import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
 import { ArvadosResource } from "../response";
 import { Collection } from "../../models/collection";
+import { getResourceKind } from "../../models/resource";
 
 interface CollectionResource extends ArvadosResource {
     name: string;
@@ -42,7 +43,7 @@ export default class CollectionService {
                     href: g.href,
                     uuid: g.uuid,
                     ownerUuid: g.owner_uuid,
-                    kind: g.kind
+                    kind: getResourceKind(g.kind)
                 } as Collection));
                 return collections;
             });
index bc34081811fdbbdd6aaa14437087ab82549a08fa..5bfa544940434009ec28432966d260c2d62beefb 100644 (file)
@@ -7,6 +7,7 @@ import { Dispatch } from "redux";
 import { Project } from "../../models/project";
 import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
 import { ArvadosResource } from "../response";
+import { getResourceKind } from "../../models/resource";
 
 interface GroupResource extends ArvadosResource {
     name: string;
@@ -39,7 +40,7 @@ export default class ProjectService {
                     href: g.href,
                     uuid: g.uuid,
                     ownerUuid: g.owner_uuid,
-                    kind: g.kind
+                    kind: getResourceKind(g.kind)
                 } as Project));
                 return projects;
             });
index 7b57ba7264430e50c9ec29a527f780157ccea2ce..7bc1aec994eb3271690e5a665f0772e9df901418 100644 (file)
@@ -4,6 +4,7 @@
 
 import collectionsReducer from "./collection-reducer";
 import actions from "./collection-action";
+import { ResourceKind } from "../../models/resource";
 
 describe('collection-reducer', () => {
     it('should add new collection to the list', () => {
@@ -15,7 +16,7 @@ describe('collection-reducer', () => {
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
             uuid: 'test123',
-            kind: ""
+            kind: ResourceKind.COLLECTION
         };
 
         const state = collectionsReducer(initialState, actions.CREATE_COLLECTION(collection));
@@ -31,7 +32,7 @@ describe('collection-reducer', () => {
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
             uuid: 'test123',
-            kind: ""
+            kind: ResourceKind.COLLECTION
         };
 
         const collections = [collection, collection];
index 939ca625bf5627f84e1bb509a6f9049725dd8eb4..5c257ea43bf0cb89dbbb05f3c71dc3131cfb3096 100644 (file)
@@ -13,7 +13,7 @@ const collectionsReducer = (state: CollectionState = [], action: CollectionActio
         CREATE_COLLECTION: collection => [...state, collection],
         REMOVE_COLLECTION: () => state,
         COLLECTIONS_REQUEST: () => {
-            return state;
+            return [];
         },
         COLLECTIONS_SUCCESS: ({ collections }) => {
             return collections;
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
new file mode 100644 (file)
index 0000000..0b4bcdf
--- /dev/null
@@ -0,0 +1,63 @@
+// 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 { TreeItem, 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";
+
+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
+}
+
+export const setProjectItem = (projects: Array<TreeItem<Project>>, itemId: string, itemKind: ResourceKind, itemMode: ItemMode) => (dispatch: Dispatch) => {
+
+    const openProjectItem = (resource: Resource) => {
+        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})));
+    };
+
+    let treeItem = findTreeItem(projects, itemId);
+    if (treeItem && itemKind === ResourceKind.LEVEL_UP) {
+        treeItem = findTreeItem(projects, treeItem.data.ownerUuid);
+    }
+
+    if (treeItem) {
+        dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(treeItem.data.uuid));
+
+        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 e8d6afc6154dd004af9729042bbd9d48f7265bff..65a856bddb8720109cf8d75f044d346c955c4de6 100644 (file)
@@ -5,6 +5,7 @@
 import projectsReducer, { getTreePath } from "./project-reducer";
 import actions from "./project-action";
 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
+import { ResourceKind } from "../../models/resource";
 
 describe('project-reducer', () => {
     it('should add new project to the list', () => {
@@ -16,7 +17,7 @@ describe('project-reducer', () => {
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
             uuid: 'test123',
-            kind: ""
+            kind: ResourceKind.PROJECT
         };
 
         const state = projectsReducer(initialState, actions.CREATE_PROJECT(project));
@@ -32,7 +33,7 @@ describe('project-reducer', () => {
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
             uuid: 'test123',
-            kind: ""
+            kind: ResourceKind.PROJECT
         };
 
         const projects = [project, project];
@@ -56,8 +57,8 @@ describe('project-reducer', () => {
     });
 
     it('should remove activity on projects list', () => {
-        const initialState = [
-            {
+        const initialState = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -65,16 +66,17 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: true,
                 active: true,
                 status: 1
-            }
-        ];
-        const project = [
-            {
+            }],
+            currentItemId: "1"
+        };
+        const project = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -82,22 +84,23 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: true,
                 active: false,
                 status: 1
-            }
-        ];
+            }],
+            currentItemId: "1"
+        };
 
         const state = projectsReducer(initialState, actions.RESET_PROJECT_TREE_ACTIVITY(initialState[0].id));
         expect(state).toEqual(project);
     });
 
     it('should toggle project tree item activity', () => {
-        const initialState = [
-            {
+        const initialState = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -105,16 +108,17 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: true,
                 active: false,
                 status: 1
-            }
-        ];
-        const project = [
-            {
+            }],
+            currentItemId: "1"
+        };
+        const project = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -122,14 +126,15 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: true,
                 active: true,
                 status: 1
-            }
-        ];
+            }],
+            currentItemId: "1"
+        };
 
         const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState[0].id));
         expect(state).toEqual(project);
@@ -137,8 +142,8 @@ describe('project-reducer', () => {
 
 
     it('should close project tree item ', () => {
-        const initialState = [
-            {
+        const initialState = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -146,17 +151,18 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: true,
                 active: false,
                 status: 1,
                 toggled: false,
-            }
-        ];
-        const project = [
-            {
+            }],
+            currentItemId: "1"
+        };
+        const project = {
+            items: [{
                 data: {
                     name: 'test',
                     href: 'href',
@@ -164,15 +170,16 @@ describe('project-reducer', () => {
                     modifiedAt: '2018-01-01',
                     ownerUuid: 'owner-test123',
                     uuid: 'test123',
-                    kind: 'example'
+                    kind: ResourceKind.PROJECT
                 },
                 id: "1",
                 open: false,
                 active: false,
                 status: 1,
                 toggled: true
-            }
-        ];
+            }],
+            currentItemId: "1"
+        };
 
         const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState[0].id));
         expect(state).toEqual(project);
@@ -180,7 +187,6 @@ describe('project-reducer', () => {
 });
 
 describe("findTreeBranch", () => {
-
     const createTreeItem = (id: string, items?: Array<TreeItem<string>>): TreeItem<string> => ({
         id,
         items,
index 48db05df77eb6051fcc5c84509fe317777ad7f26..0e2018b41c3830784c24b9615ad8b08db40cf70c 100644 (file)
@@ -8,7 +8,10 @@ import { Project } from "../../models/project";
 import actions, { ProjectAction } from "./project-action";
 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
 
-export type ProjectState = Array<TreeItem<Project>>;
+export type ProjectState = {
+    items: Array<TreeItem<Project>>,
+    currentItemId: string
+};
 
 export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
     let item;
@@ -23,8 +26,21 @@ export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeI
     return item;
 }
 
+export function getActiveTreeItem<T>(tree: Array<TreeItem<T>>): TreeItem<T> | undefined {
+    let item;
+    for (const t of tree) {
+        item = t.active
+            ? t
+            : getActiveTreeItem(t.items ? t.items : []);
+        if (item) {
+            break;
+        }
+    }
+    return item;
+}
+
 export function getTreePath<T>(tree: Array<TreeItem<T>>, itemId: string): Array<TreeItem<T>> {
-    for(const item of tree){
+    for (const item of tree){
         if(item.id === itemId){
             return [item];
         } else {
@@ -52,7 +68,7 @@ function updateProjectTree(tree: Array<TreeItem<Project>>, projects: Project[],
             treeItem.status = TreeItemStatus.Loaded;
         }
     }
-    const items = projects.map((p, idx) => ({
+    const items = projects.map(p => ({
         id: p.uuid,
         open: false,
         active: false,
@@ -69,43 +85,67 @@ function updateProjectTree(tree: Array<TreeItem<Project>>, projects: Project[],
     return items;
 }
 
-const projectsReducer = (state: ProjectState = [], action: ProjectAction) => {
+const projectsReducer = (state: ProjectState = { items: [], currentItemId: "" }, action: ProjectAction) => {
     return actions.match(action, {
-        CREATE_PROJECT: project => [...state, project],
+        CREATE_PROJECT: project => ({
+            ...state,
+            items: state.items.concat({
+                id: project.uuid,
+                open: false,
+                active: false,
+                status: TreeItemStatus.Loaded,
+                toggled: false,
+                items: [],
+                data: project
+            })
+        }),
         REMOVE_PROJECT: () => state,
         PROJECTS_REQUEST: itemId => {
-            const tree = _.cloneDeep(state);
-            const item = findTreeItem(tree, itemId);
+            const items = _.cloneDeep(state.items);
+            const item = findTreeItem(items, itemId);
             if (item) {
                 item.status = TreeItemStatus.Pending;
+                state.items = items;
             }
-            return tree;
+            return state;
         },
         PROJECTS_SUCCESS: ({ projects, parentItemId }) => {
-            return updateProjectTree(state, projects, parentItemId);
+            return {
+                ...state,
+                items: updateProjectTree(state.items, projects, parentItemId)
+            };
         },
         TOGGLE_PROJECT_TREE_ITEM_OPEN: itemId => {
-            const tree = _.cloneDeep(state);
-            const item = findTreeItem(tree, itemId);
+            const items = _.cloneDeep(state.items);
+            const item = findTreeItem(items, itemId);
             if (item) {
                 item.toggled = true;
                 item.open = !item.open;
             }
-            return tree;
+            return {
+                items,
+                currentItemId: itemId
+            };
         },
         TOGGLE_PROJECT_TREE_ITEM_ACTIVE: itemId => {
-            const tree = _.cloneDeep(state);
-            resetTreeActivity(tree);
-            const item = findTreeItem(tree, itemId);
+            const items = _.cloneDeep(state.items);
+            resetTreeActivity(items);
+            const item = findTreeItem(items, itemId);
             if (item) {
                 item.active = true;
             }
-            return tree;
+            return {
+                items,
+                currentItemId: itemId
+            };
         },
         RESET_PROJECT_TREE_ACTIVITY: () => {
-            const tree = _.cloneDeep(state);
-            resetTreeActivity(tree);
-            return tree;
+            const items = _.cloneDeep(state.items);
+            resetTreeActivity(items);
+            return {
+                items,
+                currentItemId: ""
+            };
         },
         default: () => state
     });
index 6089caf35cdf409d77ceb5ede5ced2ebc4083967..40b24a049d7bcb05cf320e466307a773e848a691 100644 (file)
@@ -10,7 +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 collectionsReducer from "./collection/collection-reducer";
+import collectionsReducer, { CollectionState } from "./collection/collection-reducer";
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -20,6 +20,7 @@ const composeEnhancers =
 export interface RootState {
     auth: AuthState;
     projects: ProjectState;
+    collections: CollectionState;
     router: RouterState;
     sidePanel: SidePanelState;
 }
index 055c22cfeabe89adf593fb4e4e8e435a7ea0006d..4fa3d3d67ceccfabf20462450a8367a1e9199b2b 100644 (file)
@@ -2,12 +2,26 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { getResourceKind, Resource, ResourceKind } from "../../models/resource";
+
 export interface ProjectExplorerItem {
     uuid: string;
     name: string;
-    type: 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 4931c09a5149b67c1b3f5b300a6bf3b8798da093..1018ef5e5fa428a351226ff8cf4efdc21b6d2412 100644 (file)
@@ -10,6 +10,7 @@ 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;
@@ -23,6 +24,7 @@ export interface ProjectExplorerContextActions {
 
 interface ProjectExplorerProps {
     items: ProjectExplorerItem[];
+    onRowClick: (item: ProjectExplorerItem) => void;
 }
 
 interface ProjectExplorerState {
@@ -63,7 +65,7 @@ class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplo
                 name: "Group",
                 selected: true
             }],
-            render: item => renderType(item.type)
+            render: item => renderType(item.kind)
         }, {
             name: "Owner",
             selected: true,
@@ -114,7 +116,7 @@ class ProjectExplorer extends React.Component<ProjectExplorerProps, ProjectExplo
             rowsPerPage={this.state.rowsPerPage}
             onColumnToggle={this.toggleColumn}
             onFiltersChange={this.changeFilters}
-            onRowClick={console.log}
+            onRowClick={this.props.onRowClick}
             onSortToggle={this.toggleSort}
             onSearch={this.search}
             onContextAction={this.executeAction}
@@ -185,12 +187,15 @@ const renderName = (item: ProjectExplorerItem) =>
         </Grid>
     </Grid>;
 
+
 const renderIcon = (item: ProjectExplorerItem) => {
-    switch (item.type) {
-        case "arvados#group":
-            return <i className="fas fa-folder fa-lg" />;
-        case "arvados#groupList":
-            return <i className="fas fa-th fa-lg" />;
+    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 />;
     }
index 610f2fa95522178ba1a0b4f2ff63bf1eb1a5ecd9..c798ec3db2976396685bddd5bb60c63ba7312258 100644 (file)
@@ -4,12 +4,55 @@
 
 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";
 import { ProjectExplorerItem } from "../../views-components/project-explorer/project-explorer-item";
 
-export const mapProjectTreeItem = (item: TreeItem<Project>): ProjectExplorerItem => ({
-    name: item.data.name,
-    type: item.data.kind,
-    owner: item.data.ownerUuid,
-    lastModified: item.data.modifiedAt,
-    uuid: item.data.uuid
-});
+export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeItemId: string, collections: Array<Collection>): ProjectExplorerItem[] => {
+    const dataItems: ProjectExplorerItem[] = [];
+
+    const treeItem = findTreeItem(projects, treeItemId);
+    if (treeItem) {
+        dataItems.push({
+            name: "..",
+            url: getResourceUrl(treeItem.data),
+            kind: ResourceKind.LEVEL_UP,
+            owner: treeItem.data.ownerUuid,
+            uuid: treeItem.data.uuid,
+            lastModified: treeItem.data.modifiedAt
+        });
+
+        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;
+
+                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 ProjectExplorerItem;
+
+        dataItems.push(item);
+    });
+
+    return dataItems;
+};
+
index f9e6c8b8e2c0b7f50ce690d0a445de582f71460c..534f843f31499b064500f7751f18c61afe2198ef 100644 (file)
@@ -3,32 +3,46 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-import { DispatchProp, connect } from 'react-redux';
-import { ProjectState, findTreeItem } from '../../store/project/project-reducer';
-import ProjectExplorer from '../../views-components/project-explorer/project-explorer';
+import { RouteComponentProps } from 'react-router';
+import { ProjectState } from '../../store/project/project-reducer';
 import { RootState } from '../../store/store';
-import { mapProjectTreeItem } from './project-panel-selectors';
+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";
 
 interface ProjectPanelDataProps {
     projects: ProjectState;
+    collections: CollectionState;
 }
 
 type ProjectPanelProps = ProjectPanelDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
 
 class ProjectPanel extends React.Component<ProjectPanelProps> {
-
     render() {
-        const project = findTreeItem(this.props.projects, this.props.match.params.name);
-        const projectItems = project && project.items || [];
+        const items = projectExplorerItems(
+            this.props.projects.items,
+            this.props.projects.currentItemId,
+            this.props.collections
+        );
         return (
-            <ProjectExplorer items={projectItems.map(mapProjectTreeItem)} />
+            <ProjectExplorer
+                items={items}
+                onRowClick={this.goToItem}
+            />
         );
     }
+
+    goToItem = (item: ProjectExplorerItem) => {
+        this.props.dispatch<any>(setProjectItem(this.props.projects.items, item.uuid, item.kind, ItemMode.BOTH));
+    }
 }
 
 export default connect(
     (state: RootState) => ({
-        projects: state.projects
+        projects: state.projects,
+        collections: state.collections
     })
 )(ProjectPanel);
index 4f9843cb0a3e952786ef3489de8ac29b477796ee..1069de530c02986b78d86859d3910d9cfab7b72d 100644 (file)
@@ -10,18 +10,22 @@ import { Route, Switch } from "react-router";
 import authActions from "../../store/auth/auth-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 sidePanelActions from '../../store/side-panel/side-panel-action';
-import { projectService } from '../../services/services';
 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";
 
 const drawerWidth = 240;
 const appBarHeight = 102;
@@ -67,6 +71,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
 
 interface WorkbenchDataProps {
     projects: Array<TreeItem<Project>>;
+    currentProjectId: string;
     user?: User;
     sidePanelItems: SidePanelItem[];
 }
@@ -78,7 +83,6 @@ type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp &
 
 interface NavBreadcrumb extends Breadcrumb {
     itemId: string;
-    status: TreeItemStatus;
 }
 
 interface NavMenuItem extends MainAppBarMenuItem {
@@ -87,7 +91,6 @@ interface NavMenuItem extends MainAppBarMenuItem {
 
 interface WorkbenchState {
     anchorEl: any;
-    breadcrumbs: NavBreadcrumb[];
     searchText: string;
     menuItems: {
         accountMenu: NavMenuItem[],
@@ -127,10 +130,11 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         }
     };
 
-
     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)
+            );
         },
         onSearch: searchText => {
             this.setState({ searchText });
@@ -139,36 +143,6 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action()
     };
 
-    toggleProjectTreeItemOpen = (itemId: string, status: TreeItemStatus) => {
-        if (status === TreeItemStatus.Loaded) {
-            this.openProjectItem(itemId);
-            this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId));
-            this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
-        } else {
-            this.props.dispatch<any>(getProjectList(itemId))
-                .then(() => {
-                    this.openProjectItem(itemId);
-                    this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId));
-                    this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
-                });
-        }
-    }
-
-    toggleProjectTreeItemActive = (itemId: string, status: TreeItemStatus) => {
-        if (status === TreeItemStatus.Loaded) {
-            this.openProjectItem(itemId);
-            this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
-            this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId));
-        } else {
-            this.props.dispatch<any>(getProjectList(itemId))
-                .then(() => {
-                    this.openProjectItem(itemId);
-                    this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
-                    this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId));
-                });
-        }
-    }
-
     toggleSidePanelOpen = (itemId: string) => {
         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
     }
@@ -178,26 +152,20 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
     }
 
-    openProjectItem = (itemId: string) => {
-        const branch = getTreePath(this.props.projects, itemId);
-        this.setState({
-            breadcrumbs: branch.map(item => ({
-                label: item.data.name,
-                itemId: item.data.uuid,
-                status: item.status
-            }))
-        });
-        this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
-        this.props.dispatch(push(`/project/${itemId}`));
-    }
-
     render() {
-        const { classes, user, projects, sidePanelItems } = this.props;
+        const branch = getTreePath(this.props.projects, this.props.currentProjectId);
+        const breadcrumbs = branch.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={this.state.breadcrumbs}
+                        breadcrumbs={breadcrumbs}
                         searchText={this.state.searchText}
                         user={this.props.user}
                         menuItems={this.state.menuItems}
@@ -214,17 +182,24 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
                         <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)
+                                    )}
+                                toggleActive={itemId =>
+                                    this.props.dispatch<any>(
+                                        setProjectItem(this.props.projects, itemId, ResourceKind.PROJECT, ItemMode.ACTIVE)
+                                    )}
+                            />
                         </SidePanel>
                     </Drawer>}
                 <main className={classes.contentWrapper}>
                     <div className={classes.content}>
                         <Switch>
-                            <Route path="/project/:name" component={ProjectPanel} />
+                            <Route path="/projects/:name" component={ProjectPanel} />
                         </Switch>
                     </div>
                 </main>
@@ -235,9 +210,10 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
 
 export default connect<WorkbenchDataProps>(
     (state: RootState) => ({
-        projects: state.projects,
+        projects: state.projects.items,
+        currentProjectId: state.projects.currentItemId,
         user: state.auth.user,
-        sidePanelItems: state.sidePanel,
+        sidePanelItems: state.sidePanel
     })
 )(
     withStyles(styles)(Workbench)
index 4845e4d2c8ad4a60d59e7dc53d785a3227b171f0..0ddfc627b98e0fa2a637b4a0779d4e1101f6d9c7 100644 (file)
@@ -12,7 +12,9 @@
     "no-debugger": false,
     "no-console": false,
     "no-shadowed-variable": false,
-    "semicolon": true
+    "semicolon": true,
+    "array-type": false,
+    "interface-over-type-literal": false
   },
   "linterOptions": {
     "exclude": [