Create tree picker with support for collections and files, Create User content tree...
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 9 Oct 2018 11:27:10 +0000 (13:27 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 9 Oct 2018 11:27:10 +0000 (13:27 +0200)
Feature #13862

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

src/index.tsx
src/store/tree-picker/tree-picker-actions.ts
src/views-components/projects-tree-picker/projects-tree-picker.tsx [new file with mode: 0644]
src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx [new file with mode: 0644]
src/views-components/projects-tree-picker/user-projects-tree-picker.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx

index 16533edab1b708137b74f5eb7de569fe7260d9e3..84b765792bd7411043bf0d616a1fb1eb47fe8335 100644 (file)
@@ -42,8 +42,8 @@ import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
 import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set';
 import { ContainerRequestState } from '~/models/container-request';
 import { MountKind } from '~/models/mount-types';
-import { receiveTreePickerData } from '~/store/tree-picker/tree-picker-actions';
-import { loadProject, loadCollection } from './store/tree-picker/tree-picker-actions';
+import { receiveTreePickerData, loadUserProject } from '~/store/tree-picker/tree-picker-actions';
+import { loadProject, loadCollection, initUserProject } from './store/tree-picker/tree-picker-actions';
 import { ResourceKind } from '~/models/resource';
 
 const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
@@ -117,21 +117,13 @@ const initListener = (history: History, store: RootStore, services: ServiceRepos
             await store.dispatch(loadWorkbench());
             addRouteChangeHandlers(history, store);
             // createEnumCollectorWorkflow(services);
-            store.dispatch(receiveTreePickerData({
-                id: '',
-                pickerId: 'testPicker',
-                data: [{name: 'Projects', uuid: 'c97qk-tpzed-1k14sp44jonn0n3'}],
-                extractNodeData: value => ({id: value.uuid, value}),
-            }));
-            await store.dispatch(loadProject(
-                'c97qk-tpzed-1k14sp44jonn0n3',
-                'testPicker',
-                [ResourceKind.COLLECTION],
-            ));
-            await store.dispatch(loadCollection(
-                'c97qk-4zz18-9sn8ygaf62chkkd',
-                'testPicker',
-            ));
+            store.dispatch(initUserProject('testPicker1'));
+            store.dispatch(initUserProject('testPicker2'));
+            store.dispatch(initUserProject('testPicker3'));
+            // await store.dispatch(loadCollection(
+            //     'c97qk-4zz18-9sn8ygaf62chkkd',
+            //     'testPicker',
+            // ));
         }
     };
 };
index f0f6bfcd63a02bf384b0d9af6cb7e6005368018a..3c3b052af3aefc7e1ef4f76676d55a40815fd69c 100644 (file)
@@ -41,14 +41,16 @@ export const receiveTreePickerData = <T>(params: ReceiveTreePickerDataParams<T>)
         }));
         dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
     };
-
-export const loadProject = (id: string, pickerId: string, include?: ResourceKind[]) =>
+export const loadProject = (id: string, pickerId: string, includeCollections = false, includeFiles = false) =>
     async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
+
         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
 
         const filters = pipe(
             (fb: FilterBuilder) => fb.addEqual('ownerUuid', id),
-            fb => include ? fb.addIsA('uuid', include) : fb,
+            fb => includeCollections
+                ? fb.addIsA('uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
+                : fb.addIsA('uuid', [ResourceKind.PROJECT]),
             fb => fb.getFilters(),
         )(new FilterBuilder());
 
@@ -58,7 +60,15 @@ export const loadProject = (id: string, pickerId: string, include?: ResourceKind
             id,
             pickerId,
             data: items,
-            extractNodeData: item => ({ id: item.uuid, value: item }),
+            extractNodeData: item => ({
+                id: item.uuid,
+                value: item,
+                status: item.kind === ResourceKind.PROJECT
+                    ? TreeNodeStatus.INITIAL
+                    : includeFiles
+                        ? TreeNodeStatus.INITIAL
+                        : TreeNodeStatus.LOADED
+            }),
         }));
     };
 
@@ -73,12 +83,50 @@ export const loadCollection = (id: string, pickerId: string) =>
             id,
             pickerId,
             data,
-            extractNodeData: value => {
-                return {
-                    id: value.id,
+            extractNodeData: value => ({
+                id: value.id,
+                status: TreeNodeStatus.LOADED,
+                value,
+            }),
+        }));
+    };
+
+
+export const initUserProject = (pickerId: string) =>
+    async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        const uuid = services.authService.getUuid();
+        if (uuid) {
+            dispatch(receiveTreePickerData({
+                id: '',
+                pickerId,
+                data: [{ uuid, name: 'Projects' }],
+                extractNodeData: value => ({
+                    id: value.uuid,
+                    status: TreeNodeStatus.INITIAL,
                     value,
-                    status: TreeNodeStatus.LOADED,
-                };
-            },
+                }),
+            }));
+        }
+    };
+export const loadUserProject = (pickerId: string, includeCollections = false, includeFiles = false) =>
+    async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        const uuid = services.authService.getUuid();
+        if (uuid) {
+            dispatch(loadProject(uuid, pickerId, includeCollections, includeFiles));
+        }
+    };
+
+
+export const initSharedProject = (pickerId: string) =>
+    async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(receiveTreePickerData({
+            id: '',
+            pickerId,
+            data: [{ uuid: 'Shared with me', name: 'Shared with me' }],
+            extractNodeData: value => ({
+                id: value.uuid,
+                status: TreeNodeStatus.INITIAL,
+                value,
+            }),
         }));
     };
diff --git a/src/views-components/projects-tree-picker/projects-tree-picker.tsx b/src/views-components/projects-tree-picker/projects-tree-picker.tsx
new file mode 100644 (file)
index 0000000..ac6d76e
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dispatch } from "redux";
+import { connect } from "react-redux";
+import { TreeItem, TreeItemStatus } from '~/components/tree/tree';
+import { ProjectResource } from "~/models/project";
+import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
+import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
+import { ProjectIcon, InputIcon, IconType, CollectionIcon } from '~/components/icon/icon';
+import { loadProject, loadCollection } from '~/store/tree-picker/tree-picker-actions';
+import { GroupContentsResource } from '~/services/groups-service/groups-service';
+import { CollectionDirectory, CollectionFile, CollectionFileType } from '~/models/collection-file';
+import { ResourceKind } from '~/models/resource';
+import { TreePickerProps, TreePicker } from "~/views-components/tree-picker/tree-picker";
+
+export interface ProjectsTreePickerRootItem {
+    id: string;
+    name: string;
+}
+
+type ProjectsTreePickerItem = ProjectsTreePickerRootItem | GroupContentsResource | CollectionDirectory | CollectionFile;
+type PickedTreePickerProps = Pick<TreePickerProps<ProjectsTreePickerItem>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
+
+export interface ProjectsTreePickerDataProps {
+    pickerId: string;
+    includeCollections?: boolean;
+    includeFiles?: boolean;
+    rootItemIcon: IconType;
+    loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void;
+}
+
+export interface ProjectsTreePickerActionProps {
+}
+
+export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & ProjectsTreePickerActionProps;
+
+const mapStateToProps = (_: any, { pickerId, rootItemIcon }: ProjectsTreePickerProps) => ({
+    render: renderTreeItem(rootItemIcon),
+    pickerId,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch, props: ProjectsTreePickerProps): PickedTreePickerProps => ({
+    onContextMenu: () => { return; },
+    toggleItemActive: (_, { id }, pickerId) => {
+        dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId }));
+    },
+    toggleItemOpen: (_, item, pickerId) => {
+        const { id, data, status } = item;
+        if (status === TreeItemStatus.INITIAL) {
+            if ('kind' in data) {
+                dispatch<any>(
+                    data.kind === ResourceKind.COLLECTION
+                        ? loadCollection(id, pickerId)
+                        : loadProject(id, pickerId, props.includeCollections, props.includeFiles)
+                );
+            } else if (!('type' in data) && props.loadRootItem) {
+                props.loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, props.includeCollections, props.includeFiles);
+            }
+        } else if (status === TreeItemStatus.LOADED) {
+            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
+        }
+    },
+    toggleItemSelection: (_, { id }, pickerId) => {
+        dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId }));
+    },
+});
+
+export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker);
+
+const getProjectPickerIcon = ({ data }: TreeItem<ProjectsTreePickerItem>, rootIcon: IconType): IconType => {
+    if ('kind' in data) {
+        switch (data.kind) {
+            case ResourceKind.COLLECTION:
+                return CollectionIcon;
+            default:
+                return ProjectIcon;
+        }
+    } else if ('type' in data) {
+        switch (data.type) {
+            case CollectionFileType.FILE:
+                return InputIcon;
+            default:
+                return ProjectIcon;
+        }
+    } else {
+        return rootIcon;
+    }
+};
+
+const renderTreeItem = (rootItemIcon: IconType) => (item: TreeItem<ProjectResource>) =>
+    <ListItemTextIcon
+        icon={getProjectPickerIcon(item, rootItemIcon)}
+        name={item.data.name}
+        isActive={item.active}
+        hasMargin={true} />;
diff --git a/src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx b/src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx
new file mode 100644 (file)
index 0000000..2039db7
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from 'react-redux';
+import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { Dispatch } from 'redux';
+import { loadUserProject } from '~/store/tree-picker/tree-picker-actions';
+import { ShareIcon } from '~/components/icon/icon';
+
+export const UserProjectsTreePicker = connect(() => ({
+    rootItemIcon: ShareIcon,
+}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
+    loadRootItem: (_, pickerId, includeCollections, includeFiles) => {
+        dispatch<any>(loadUserProject(pickerId, includeCollections, includeFiles));
+    },
+}))(ProjectsTreePicker);
\ No newline at end of file
diff --git a/src/views-components/projects-tree-picker/user-projects-tree-picker.tsx b/src/views-components/projects-tree-picker/user-projects-tree-picker.tsx
new file mode 100644 (file)
index 0000000..10a0b20
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from 'react-redux';
+import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { Dispatch } from 'redux';
+import { loadUserProject } from '~/store/tree-picker/tree-picker-actions';
+import { ProjectIcon } from '~/components/icon/icon';
+
+export const UserProjectsTreePicker = connect(() => ({
+    rootItemIcon: ProjectIcon,
+}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
+    loadRootItem: (_, pickerId, includeCollections, includeFiles) => {
+        dispatch<any>(loadUserProject(pickerId, includeCollections, includeFiles));
+    },
+}))(ProjectsTreePicker);
\ No newline at end of file
index 407488b6d6a78df6164bc9096fe2152daa0fde68..998e40045d25aab3500dc86812e23015acd08762 100644 (file)
@@ -45,6 +45,8 @@ import { TreePicker } from '../../views-components/tree-picker/tree-picker';
 import { noop } from 'lodash';
 import { TreeItem } from '~/components/tree/tree';
 import { GroupContentsResource } from '~/services/groups-service/groups-service';
+import { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { UserProjectsTreePicker } from '~/views-components/projects-tree-picker/user-projects-tree-picker';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -92,14 +94,9 @@ export const WorkbenchPanel =
                             <MainContentBar />
                         </Grid>
                         <Grid item xs className={classes.content}>
-                            <TreePicker
-                                render={({ data }: TreeItem<GroupContentsResource>) =>
-                                    <p>{JSON.stringify(data.name)}</p>}
-                                pickerId='testPicker'
-                                onContextMenu={noop}
-                                toggleItemOpen={noop}
-                                toggleItemActive={item => console.log(item)}
-                                toggleItemSelection={noop} />
+                            <UserProjectsTreePicker pickerId='testPicker1'/>
+                            <UserProjectsTreePicker pickerId='testPicker2' includeCollections/>
+                            <UserProjectsTreePicker pickerId='testPicker3' includeCollections includeFiles/>
                             <Switch>
                                 <Route path={Routes.PROJECTS} component={ProjectPanel} />
                                 <Route path={Routes.COLLECTIONS} component={CollectionPanel} />