[ARVADOS-WORKBENCH2] updated: 1.1.4-582-g1c1a441

Git user git at public.curoverse.com
Fri Aug 17 05:19:39 EDT 2018


Summary of changes:
 src/components/move-to-dialog/move-to-dialog.tsx   |   8 +-
 src/components/tree/tree.tsx                       |  10 +-
 src/index.tsx                                      |   2 +
 .../data-explorer/data-explorer-reducer.test.tsx   |   8 +-
 src/store/project/project-reducer.test.ts          |   2 +-
 src/store/tree-picker/tree-picker-actions.ts       |  43 +++++++-
 src/store/tree-picker/tree-picker-reducer.test.ts  |  82 ++++++++-------
 src/store/tree-picker/tree-picker-reducer.ts       |  37 ++++---
 src/store/tree-picker/tree-picker.ts               |   4 +-
 .../create-collection-dialog-with-selected.tsx     |   2 -
 .../dialog-collection-create-selected.tsx          |   6 +-
 .../move-to-dialog/move-to-dialog.tsx              |   2 -
 .../project-tree-picker/project-tree-picker.tsx    | 110 ++++++++++++++++-----
 .../project-tree/project-tree.test.tsx             |  10 +-
 src/views-components/tree-picker/tree-picker.ts    |  37 +++----
 15 files changed, 236 insertions(+), 127 deletions(-)

       via  1c1a44191e11b62a0640551ea73f2281b2ed9a7f (commit)
      from  42c529f08bbeccaeb0d4c07f639c74504569c621 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 1c1a44191e11b62a0640551ea73f2281b2ed9a7f
Author: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>
Date:   Fri Aug 17 11:18:57 2018 +0200

    13902-ui-move-to-popup
    
    Feature #13902
    
    Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>

diff --git a/src/components/move-to-dialog/move-to-dialog.tsx b/src/components/move-to-dialog/move-to-dialog.tsx
index df66c01..f38c542 100644
--- a/src/components/move-to-dialog/move-to-dialog.tsx
+++ b/src/components/move-to-dialog/move-to-dialog.tsx
@@ -7,7 +7,7 @@ import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form";
 import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core";
 
 import { WithDialogProps } from "../../store/dialog/with-dialog";
-import { ProjectTreePickerWithSidePanel } from "../../views-components/project-tree-picker/project-tree-picker";
+import { ProjectTreePicker } from "../../views-components/project-tree-picker/project-tree-picker";
 import { MOVE_TO_VALIDATION } from "../../validators/move-to/move-to-validator";
 
 export const MoveTo = (props: WithDialogProps<string> & InjectedFormProps<{ name: string }>) =>
@@ -36,9 +36,7 @@ export const MoveTo = (props: WithDialogProps<string> & InjectedFormProps<{ name
                     type='submit'
                     onClick={props.handleSubmit}
                     disabled={props.pristine || props.invalid || props.submitting}>
-                    {props.submitting
-                        ? <CircularProgress size={20} />
-                        : 'Move'}
+                    {props.submitting ? <CircularProgress size={20} /> : 'Move'}
                 </Button>
             </DialogActions>
         </Dialog>
@@ -46,5 +44,5 @@ export const MoveTo = (props: WithDialogProps<string> & InjectedFormProps<{ name
 
 const Picker = (props: WrappedFieldProps) =>
     <div style={{ width: '400px', height: '144px', display: 'flex', flexDirection: 'column' }}>
-        <ProjectTreePickerWithSidePanel onChange={projectUuid => props.input.onChange(projectUuid)} />
+       <ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} /> 
     </div>;
\ No newline at end of file
diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx
index 669b70c..7f45e2d 100644
--- a/src/components/tree/tree.tsx
+++ b/src/components/tree/tree.tsx
@@ -65,9 +65,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 });
 
 export enum TreeItemStatus {
-    INITIAL,
-    PENDING,
-    LOADED
+    INITIAL = 'initial',
+    PENDING = 'pending',
+    LOADED = 'loaded'
 }
 
 export interface TreeItem<T> {
@@ -110,7 +110,9 @@ export const Tree = withStyles(styles)(
                             <i onClick={() => this.props.toggleItemOpen(it.id, it.status)}
                                 className={toggableIconContainer}>
                                 <ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
-                                    {it.status !== TreeItemStatus.INITIAL && it.items && it.items.length === 0 ? <span /> : <SidePanelRightArrowIcon />}
+                                    {it.status === TreeItemStatus.PENDING 
+                                    || (it.status === TreeItemStatus.LOADED && !it.items)
+                                    || (it.status === TreeItemStatus.LOADED && it.items && it.items.length === 0) ? <span /> : <SidePanelRightArrowIcon />}
                                 </ListItemIcon>
                             </i>
                             {this.props.showSelection &&
diff --git a/src/index.tsx b/src/index.tsx
index dd27223..bf3002e 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -27,6 +27,7 @@ import { collectionFilesActionSet } from './views-components/context-menu/action
 import { collectionFilesItemActionSet } from './views-components/context-menu/action-sets/collection-files-item-action-set';
 import { collectionActionSet } from './views-components/context-menu/action-sets/collection-action-set';
 import { collectionResourceActionSet } from './views-components/context-menu/action-sets/collection-resource-action-set';
+import { initPickerProjectTree } from './store/tree-picker/tree-picker-actions';
 
 addMenuActionSet(ContextMenuKind.ROOT_PROJECT, rootProjectActionSet);
 addMenuActionSet(ContextMenuKind.PROJECT, projectActionSet);
@@ -45,6 +46,7 @@ fetchConfig()
 
         store.dispatch(initAuth());
         store.dispatch(getProjectList(services.authService.getUuid()));
+        store.dispatch(initPickerProjectTree());
 
         const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props}/>;
         const WorkbenchComponent = (props: any) => <Workbench authService={services.authService} {...props}/>;
diff --git a/src/store/data-explorer/data-explorer-reducer.test.tsx b/src/store/data-explorer/data-explorer-reducer.test.tsx
index c54a86a..11296ce 100644
--- a/src/store/data-explorer/data-explorer-reducer.test.tsx
+++ b/src/store/data-explorer/data-explorer-reducer.test.tsx
@@ -13,7 +13,8 @@ describe('data-explorer-reducer', () => {
         const columns: DataColumns<any> = [{
             name: "Column 1",
             render: jest.fn(),
-            selected: true
+            selected: true,
+            configurable: false
         }];
         const state = dataExplorerReducer(undefined,
             dataExplorerActions.SET_COLUMNS({ id: "Data explorer", columns }));
@@ -25,12 +26,14 @@ describe('data-explorer-reducer', () => {
             name: "Column 1",
             render: jest.fn(),
             selected: true,
-            sortDirection: SortDirection.ASC
+            sortDirection: SortDirection.ASC,
+            configurable: false
         }, {
             name: "Column 2",
             render: jest.fn(),
             selected: true,
             sortDirection: SortDirection.NONE,
+            configurable: false
         }];
         const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
             dataExplorerActions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
@@ -43,6 +46,7 @@ describe('data-explorer-reducer', () => {
             name: "Column 1",
             render: jest.fn(),
             selected: true,
+            configurable: false
         }];
 
         const filters: DataTableFilterItem[] = [{
diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts
index 92274b3..efee541 100644
--- a/src/store/project/project-reducer.test.ts
+++ b/src/store/project/project-reducer.test.ts
@@ -21,7 +21,7 @@ describe('project-reducer', () => {
                 id: "1",
                 items: [],
                 data: mockProjectResource({ uuid: "1" }),
-                status: 0
+                status: TreeItemStatus.INITIAL
             }, {
                 active: false,
                 open: false,
diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts
index 772d89d..5b29c4c 100644
--- a/src/store/tree-picker/tree-picker-actions.ts
+++ b/src/store/tree-picker/tree-picker-actions.ts
@@ -3,17 +3,50 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
-import { TreeNode } from "../../models/tree";
+
 import { TreePickerNode } from "./tree-picker";
+import { receiveTreePickerData, TreePickerKind } from "../../views-components/project-tree-picker/project-tree-picker";
+import { mockProjectResource } from "../../models/test-utils";
+import { Dispatch } from "redux";
+import { RootState } from "../store";
+import { ServiceRepository } from "../../services/services";
 
 export const treePickerActions = unionize({
-    LOAD_TREE_PICKER_NODE: ofType<{ id: string }>(),
-    LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array<TreePickerNode> }>(),
-    TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string }>(),
-    TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ id: string }>()
+    LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
+    LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array<TreePickerNode>, pickerId: string }>(),
+    TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string, pickerId: string }>(),
+    TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ id: string, pickerId: string }>()
 }, {
         tag: 'type',
         value: 'payload'
     });
 
+export const initPickerProjectTree = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+    const uuid = services.authService.getUuid();
+
+    dispatch<any>(getPickerTreeProjects(uuid));
+    dispatch<any>(getSharedWithMeProjectsPickerTree(uuid));
+    dispatch<any>(getFavoritesProjectsPickerTree(uuid));
+};
+
+const getPickerTreeProjects = (uuid: string = '') => {
+    return getProjectsPickerTree(uuid, TreePickerKind.PROJECTS);
+};
+
+const getSharedWithMeProjectsPickerTree = (uuid: string = '') => {
+    return getProjectsPickerTree(uuid, TreePickerKind.SHARED_WITH_ME);
+};
+
+const getFavoritesProjectsPickerTree = (uuid: string = '') => {
+    return getProjectsPickerTree(uuid, TreePickerKind.FAVORITES);
+};
+
+const getProjectsPickerTree = (uuid: string, kind: string) => {
+    return receiveTreePickerData(
+        '',
+        [mockProjectResource({ uuid, name: kind })],
+        kind
+    );
+};
+
 export type TreePickerAction = UnionOf<typeof treePickerActions>;
diff --git a/src/store/tree-picker/tree-picker-reducer.test.ts b/src/store/tree-picker/tree-picker-reducer.test.ts
index ac4de0c..0b37aee 100644
--- a/src/store/tree-picker/tree-picker-reducer.test.ts
+++ b/src/store/tree-picker/tree-picker-reducer.test.ts
@@ -12,88 +12,94 @@ import { TreeItemStatus } from "../../components/tree/tree";
 describe('TreePickerReducer', () => {
     it('LOAD_TREE_PICKER_NODE - initial state', () => {
         const tree = createTree<TreePickerNode>();
-        const newTree = treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1' }));
-        expect(newTree).toEqual(tree);
+        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }));
+        expect(newState).toEqual({ 'projects': tree });
     });
 
     it('LOAD_TREE_PICKER_NODE', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1' })));
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })));
+
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             status: TreeItemStatus.PENDING
         });
     });
 
     it('LOAD_TREE_PICKER_NODE_SUCCESS - initial state', () => {
-        const tree = createTree<TreePickerNode>();
         const subNode = createTreePickerNode({ id: '1.1', value: '1.1' });
-        const newTree = treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [subNode] }));
-        expect(getNodeChildren('')(newTree)).toEqual(['1.1']);
+        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [subNode], pickerId: "projects" }));
+        expect(getNodeChildren('')(newState.projects)).toEqual(['1.1']);
     });
 
     it('LOAD_TREE_PICKER_NODE_SUCCESS', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
         const subNode = createTreePickerNode({ id: '1.1', value: '1.1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode] })));
-        expect(getNodeChildren('1')(newTree)).toEqual(['1.1']);
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode], pickerId: "projects" })));
+        expect(getNodeChildren('1')(newState.projects)).toEqual(['1.1']);
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             status: TreeItemStatus.LOADED
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - collapsed', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1' })));
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" })));
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             collapsed: false
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - expanded', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1' })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1' })));
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" })));
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             collapsed: true
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_SELECT - selected', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1' })));
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1', pickerId: "projects" })));
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             selected: true
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_SELECT - not selected', () => {
-        const tree = createTree<TreePickerNode>();
         const node = createTreePickerNode({ id: '1', value: '1' });
-        const [newTree] = [tree]
-            .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1' })))
-            .map(tree => treePickerReducer(tree, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1' })));
-        expect(getNodeValue('1')(newTree)).toEqual({
+        const [newState] = [{
+            projects: createTree<TreePickerNode>()
+        }]
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1', pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '1', pickerId: "projects" })));
+        expect(getNodeValue('1')(newState.projects)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             selected: false
         });
diff --git a/src/store/tree-picker/tree-picker-reducer.ts b/src/store/tree-picker/tree-picker-reducer.ts
index d195a98..e4ca262 100644
--- a/src/store/tree-picker/tree-picker-reducer.ts
+++ b/src/store/tree-picker/tree-picker-reducer.ts
@@ -2,26 +2,35 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { createTree, setNodeValueWith, TreeNode, setNode, mapTree, mapTreeValues } from "../../models/tree";
+import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "../../models/tree";
 import { TreePicker, TreePickerNode } from "./tree-picker";
 import { treePickerActions, TreePickerAction } from "./tree-picker-actions";
 import { TreeItemStatus } from "../../components/tree/tree";
 
-
-export const treePickerReducer = (state: TreePicker = createTree(), action: TreePickerAction) =>
+export const treePickerReducer = (state: TreePicker = {}, action: TreePickerAction) =>
     treePickerActions.match(action, {
-        LOAD_TREE_PICKER_NODE: ({ id }) =>
-            setNodeValueWith(setPending)(id)(state),
-        LOAD_TREE_PICKER_NODE_SUCCESS: ({ id, nodes }) => {
-            const [newState] = [state]
+        LOAD_TREE_PICKER_NODE: ({ id, pickerId }) => {
+            const picker = state[pickerId] || createTree();
+            const updatedPicker = setNodeValueWith(setPending)(id)(picker);
+            return { ...state, [pickerId]: updatedPicker };
+        },
+        LOAD_TREE_PICKER_NODE_SUCCESS: ({ id, nodes, pickerId }) => {
+            const picker = state[pickerId] || createTree();
+            const [updatedPicker] = [picker]
                 .map(receiveNodes(nodes)(id))
                 .map(setNodeValueWith(setLoaded)(id));
-            return newState;
+            return { ...state, [pickerId]: updatedPicker };
+        },
+        TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ id, pickerId }) => {
+            const picker = state[pickerId] || createTree();
+            const updatedPicker = setNodeValueWith(toggleCollapse)(id)(picker);
+            return { ...state, [pickerId]: updatedPicker };
+        },
+        TOGGLE_TREE_PICKER_NODE_SELECT: ({ id, pickerId }) => {
+            const picker = state[pickerId] || createTree();
+            const updatedPicker = mapTreeValues(toggleSelect(id))(picker);
+            return { ...state, [pickerId]: updatedPicker };
         },
-        TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ id }) =>
-            setNodeValueWith(toggleCollapse)(id)(state),
-        TOGGLE_TREE_PICKER_NODE_SELECT: ({ id }) =>
-            mapTreeValues(toggleSelect(id))(state),
         default: () => state
     });
 
@@ -39,8 +48,8 @@ const toggleSelect = (id: string) => (value: TreePickerNode): TreePickerNode =>
         ? ({ ...value, selected: !value.selected })
         : ({ ...value, selected: false });
 
-const receiveNodes = (nodes: Array<TreePickerNode>) => (parent: string) => (state: TreePicker) =>
-    nodes.reduce((tree, node) =>
+const receiveNodes = (nodes: Array<TreePickerNode>) => (parent: string) => (state: Tree<TreePickerNode>) =>
+    nodes.reduce((tree, node) => 
         setNode(
             createTreeNode(parent)(node)
         )(tree), state);
diff --git a/src/store/tree-picker/tree-picker.ts b/src/store/tree-picker/tree-picker.ts
index ee45bec..5cb2d97 100644
--- a/src/store/tree-picker/tree-picker.ts
+++ b/src/store/tree-picker/tree-picker.ts
@@ -5,7 +5,7 @@
 import { Tree } from "../../models/tree";
 import { TreeItemStatus } from "../../components/tree/tree";
 
-export type TreePicker = Tree<TreePickerNode>;
+export type TreePicker = { [key: string]: Tree<TreePickerNode> };
 
 export interface TreePickerNode {
     id: string;
@@ -15,7 +15,7 @@ export interface TreePickerNode {
     status: TreeItemStatus;
 }
 
-export const createTreePickerNode = (data: {id: string, value: any}) => ({
+export const createTreePickerNode = (data: { id: string, value: any }) => ({
     ...data,
     selected: false,
     collapsed: true,
diff --git a/src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx b/src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx
index 8a2efca..ece0639 100644
--- a/src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx
+++ b/src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx
@@ -7,14 +7,12 @@ import { reduxForm, reset, startSubmit, stopSubmit } from "redux-form";
 import { withDialog } from "../../store/dialog/with-dialog";
 import { dialogActions } from "../../store/dialog/dialog-actions";
 import { DialogCollectionCreateWithSelected } from "../dialog-create/dialog-collection-create-selected";
-import { loadProjectTreePickerProjects } from "../project-tree-picker/project-tree-picker";
 
 export const DIALOG_COLLECTION_CREATE_WITH_SELECTED = 'dialogCollectionCreateWithSelected';
 
 export const createCollectionWithSelected = () =>
     (dispatch: Dispatch) => {
         dispatch(reset(DIALOG_COLLECTION_CREATE_WITH_SELECTED));
-        dispatch<any>(loadProjectTreePickerProjects(''));
         dispatch(dialogActions.OPEN_DIALOG({ id: DIALOG_COLLECTION_CREATE_WITH_SELECTED, data: {} }));
     };
 
diff --git a/src/views-components/dialog-create/dialog-collection-create-selected.tsx b/src/views-components/dialog-create/dialog-collection-create-selected.tsx
index 8fb6033..edaa527 100644
--- a/src/views-components/dialog-create/dialog-collection-create-selected.tsx
+++ b/src/views-components/dialog-create/dialog-collection-create-selected.tsx
@@ -4,7 +4,7 @@
 
 import * as React from "react";
 import { InjectedFormProps, Field, WrappedFieldProps } from "redux-form";
-import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress, FormHelperText } from "@material-ui/core";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core";
 import { WithDialogProps } from "../../store/dialog/with-dialog";
 import { TextField } from "../../components/text-field/text-field";
 import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "../../validators/create-collection/create-collection-validator";
@@ -48,9 +48,7 @@ export const DialogCollectionCreateWithSelected = (props: WithDialogProps<string
                     type='submit'
                     onClick={props.handleSubmit}
                     disabled={props.pristine || props.invalid || props.submitting}>
-                    {props.submitting
-                        ? <CircularProgress size={20} />
-                        : 'Create a collection'}
+                    {props.submitting ? <CircularProgress size={20} /> : 'Create a collection'}
                 </Button>
             </DialogActions>
         </Dialog>
diff --git a/src/views-components/move-to-dialog/move-to-dialog.tsx b/src/views-components/move-to-dialog/move-to-dialog.tsx
index 74d5e50..21aa515 100644
--- a/src/views-components/move-to-dialog/move-to-dialog.tsx
+++ b/src/views-components/move-to-dialog/move-to-dialog.tsx
@@ -6,14 +6,12 @@ import { Dispatch } from "redux";
 import { withDialog } from "../../store/dialog/with-dialog";
 import { dialogActions } from "../../store/dialog/dialog-actions";
 import { MoveTo } from "../../components/move-to-dialog/move-to-dialog";
-import { loadProjectTreePickerProjects } from "../project-tree-picker/project-tree-picker";
 import { reduxForm, startSubmit, stopSubmit } from "redux-form";
 
 export const MOVE_TO_DIALOG = 'moveToDialog';
 
 export const openMoveToDialog = () =>
     (dispatch: Dispatch) => {
-        dispatch<any>(loadProjectTreePickerProjects(''));
         dispatch(dialogActions.OPEN_DIALOG({ id: MOVE_TO_DIALOG, data: {}}));
     };
 
diff --git a/src/views-components/project-tree-picker/project-tree-picker.tsx b/src/views-components/project-tree-picker/project-tree-picker.tsx
index 49fb4d4..0338534 100644
--- a/src/views-components/project-tree-picker/project-tree-picker.tsx
+++ b/src/views-components/project-tree-picker/project-tree-picker.tsx
@@ -6,53 +6,74 @@ import * as React from "react";
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
 import { Typography } from "@material-ui/core";
-import { TreePicker } from "../tree-picker/tree-picker";
-import { TreeProps, TreeItem, TreeItemStatus } from "../../components/tree/tree";
+import { TreePicker, TreePickerProps } from "../tree-picker/tree-picker";
+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 } from "../../components/icon/icon";
+import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon } from "../../components/icon/icon";
 import { createTreePickerNode } from "../../store/tree-picker/tree-picker";
 import { RootState } from "../../store/store";
 import { ServiceRepository } from "../../services/services";
 import { FilterBuilder } from "../../common/api/filter-builder";
 
-type ProjectTreePickerProps = Pick<TreeProps<ProjectResource>, 'toggleItemActive' | 'toggleItemOpen'>;
+type ProjectTreePickerProps = Pick<TreePickerProps, 'toggleItemActive' | 'toggleItemOpen'>;
 
 const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): ProjectTreePickerProps => ({
-    toggleItemActive: id => {
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id }));
+    toggleItemActive: (id, status, pickerId) => {
+        getNotSelectedTreePickerKind(pickerId)
+            .forEach(pickerId => dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '', pickerId })));
+        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id, pickerId }));
+
         props.onChange(id);
     },
-    toggleItemOpen: (id, status) => {
-        status === TreeItemStatus.INITIAL
-            ? dispatch<any>(loadProjectTreePickerProjects(id))
-            : dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id }));
+    toggleItemOpen: (id, status, pickerId) => {
+        dispatch<any>(toggleItemOpen(id, status, pickerId));
     }
 });
 
+const toggleItemOpen = (id: string, status: TreeItemStatus, pickerId: string) =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        if (status === TreeItemStatus.INITIAL) {
+            if (pickerId === TreePickerKind.PROJECTS) {
+                dispatch<any>(loadProjectTreePickerProjects(id));
+            } else if (pickerId === TreePickerKind.FAVORITES) {
+                dispatch<any>(loadFavoriteTreePickerProjects(id === services.authService.getUuid() ? '' : id));
+            } else {
+                // TODO: load sharedWithMe
+            }
+        } else {
+            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
+        }
+    };
+
+const getNotSelectedTreePickerKind = (pickerId: string) => {
+    return [TreePickerKind.PROJECTS, TreePickerKind.FAVORITES, TreePickerKind.SHARED_WITH_ME].filter(id => id !== pickerId);
+};
+
+export enum TreePickerKind {
+    PROJECTS = 'Projects',
+    SHARED_WITH_ME = 'Shared with me',
+    FAVORITES = 'Favorites'
+}
+
 export const ProjectTreePicker = connect(undefined, mapDispatchToProps)((props: ProjectTreePickerProps) =>
     <div style={{ display: 'flex', flexDirection: 'column' }}>
         <Typography variant='caption' style={{ flexShrink: 0 }}>
             Select a project
         </Typography>
         <div style={{ flexGrow: 1, overflow: 'auto' }}>
-            <TreePicker {...props} render={renderTreeItem} />
+            <TreePicker {...props} render={renderTreeItem} pickerId={TreePickerKind.PROJECTS} />
+            <TreePicker {...props} render={renderTreeItem} pickerId={TreePickerKind.SHARED_WITH_ME} />
+            <TreePicker {...props} render={renderTreeItem} pickerId={TreePickerKind.FAVORITES} />
         </div>
     </div>);
 
-export const ProjectTreePickerWithSidePanel = connect(undefined, mapDispatchToProps)((props: ProjectTreePickerProps) =>
-    <div style={{ display: 'flex', flexDirection: 'column' }}>
-        <div style={{ flexGrow: 1, overflow: 'auto' }}>
-            <TreePicker {...props} render={renderTreeItem} />
-        </div>
-    </div>
-);
 
 // TODO: move action creator to store directory
 export const loadProjectTreePickerProjects = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id }));
+        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerKind.PROJECTS }));
 
         const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id;
 
@@ -62,22 +83,61 @@ export const loadProjectTreePickerProjects = (id: string) =>
 
         const { items } = await services.projectService.list({ filters });
 
-        dispatch<any>(receiveProjectTreePickerData(id, items));
+        dispatch<any>(receiveTreePickerData(id, items, TreePickerKind.PROJECTS));
+    };
+
+export const loadFavoriteTreePickerProjects = (id: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const parentId = services.authService.getUuid() || '';
+
+        if (id === '') {
+            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: parentId, pickerId: TreePickerKind.FAVORITES }));
+            const { items } = await services.favoriteService.list(parentId);
+
+            dispatch<any>(receiveTreePickerData(parentId, items as ProjectResource[], TreePickerKind.FAVORITES));
+        } else {
+            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerKind.FAVORITES }));
+            const filters = FilterBuilder
+                .create()
+                .addEqual('ownerUuid', id);
+
+            const { items } = await services.projectService.list({ filters });
+
+            dispatch<any>(receiveTreePickerData(id, items, TreePickerKind.FAVORITES));
+        }
+
     };
 
+const getProjectPickerIcon = (item: TreeItem<ProjectResource>) => {
+    switch (item.data.name) {
+        case TreePickerKind.FAVORITES:
+            return FavoriteIcon;
+        case TreePickerKind.PROJECTS:
+            return ProjectsIcon;
+        case TreePickerKind.SHARED_WITH_ME:
+            return ShareMeIcon;
+        default:
+            return ProjectIcon;
+    }
+};
+
 const renderTreeItem = (item: TreeItem<ProjectResource>) =>
     <ListItemTextIcon
-        icon={ProjectIcon}
+        icon={getProjectPickerIcon(item)}
         name={item.data.name}
         isActive={item.active}
         hasMargin={true} />;
 
+
 // TODO: move action creator to store directory
-const receiveProjectTreePickerData = (id: string, projects: ProjectResource[]) =>
+export const receiveTreePickerData = (id: string, projects: ProjectResource[], pickerId: string) =>
     (dispatch: Dispatch) => {
         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
             id,
-            nodes: projects.map(project => createTreePickerNode({ id: project.uuid, value: project }))
+            nodes: projects.map(project => createTreePickerNode({ id: project.uuid, value: project })),
+            pickerId,
         }));
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id }));
-    };
\ No newline at end of file
+        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
+    };
+
+
diff --git a/src/views-components/project-tree/project-tree.test.tsx b/src/views-components/project-tree/project-tree.test.tsx
index 98b4a67..18efdaf 100644
--- a/src/views-components/project-tree/project-tree.test.tsx
+++ b/src/views-components/project-tree/project-tree.test.tsx
@@ -11,7 +11,7 @@ import { Collapse } from '@material-ui/core';
 import CircularProgress from '@material-ui/core/CircularProgress';
 
 import { ProjectTree } from './project-tree';
-import { TreeItem } from '../../components/tree/tree';
+import { TreeItem, TreeItemStatus } from '../../components/tree/tree';
 import { ProjectResource } from '../../models/project';
 import { mockProjectResource } from '../../models/test-utils';
 
@@ -25,7 +25,7 @@ describe("ProjectTree component", () => {
             id: "3",
             open: true,
             active: true,
-            status: 1
+            status: TreeItemStatus.PENDING
         };
         const wrapper = mount(<ProjectTree
             projects={[project]}
@@ -43,14 +43,14 @@ describe("ProjectTree component", () => {
                 id: "3",
                 open: true,
                 active: true,
-                status: 2,
+                status: TreeItemStatus.LOADED,
                 items: [
                     {
                         data: mockProjectResource(),
                         id: "3",
                         open: true,
                         active: true,
-                        status: 1
+                        status: TreeItemStatus.PENDING
                     }
                 ]
             }
@@ -70,7 +70,7 @@ describe("ProjectTree component", () => {
             id: "3",
             open: false,
             active: true,
-            status: 1
+            status: TreeItemStatus.PENDING
         };
         const wrapper = mount(<ProjectTree
             projects={[project]}
diff --git a/src/views-components/tree-picker/tree-picker.ts b/src/views-components/tree-picker/tree-picker.ts
index 3e0fc6e..a0eae9f 100644
--- a/src/views-components/tree-picker/tree-picker.ts
+++ b/src/views-components/tree-picker/tree-picker.ts
@@ -3,34 +3,35 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { connect } from "react-redux";
-import { Tree, TreeProps, TreeItem } from "../../components/tree/tree";
+import { Tree, TreeProps, TreeItem, TreeItemStatus } from "../../components/tree/tree";
 import { RootState } from "../../store/store";
-import { TreePicker as TTreePicker, TreePickerNode, createTreePickerNode } from "../../store/tree-picker/tree-picker";
-import { getNodeValue, getNodeChildren } from "../../models/tree";
+import { createTreePickerNode, TreePickerNode } from "../../store/tree-picker/tree-picker";
+import { getNodeValue, getNodeChildren, Tree as Ttree, createTree } from "../../models/tree";
+import { Dispatch } from "node_modules/redux";
 
-const memoizedMapStateToProps = () => {
-    let prevState: TTreePicker;
-    let prevTree: Array<TreeItem<any>>;
+export interface TreePickerProps {
+    pickerId: string;
+    toggleItemOpen: (id: string, status: TreeItemStatus, pickerId: string) => void;
+    toggleItemActive: (id: string, status: TreeItemStatus, pickerId: string) => void;
+}
 
-    return (state: RootState): Pick<TreeProps<any>, 'items'> => {
-        if (prevState !== state.treePicker) {
-            prevState = state.treePicker;
-            prevTree = getNodeChildren('')(state.treePicker)
-                .map(treePickerToTreeItems(state.treePicker));
-        }
-        return {
-            items: prevTree
-        };
+const mapStateToProps = (state: RootState, props: TreePickerProps): Pick<TreeProps<any>, 'items'> => {
+    const tree = state.treePicker[props.pickerId] || createTree();
+    return {
+        items: getNodeChildren('')(tree)
+            .map(treePickerToTreeItems(tree))
     };
 };
 
-const mapDispatchToProps = (): Pick<TreeProps<any>, 'onContextMenu'> => ({
+const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive'> => ({
     onContextMenu: () => { return; },
+    toggleItemActive: (id, status) => props.toggleItemActive(id, status, props.pickerId),
+    toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId)
 });
 
-export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
+export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(Tree);
 
-const treePickerToTreeItems = (tree: TTreePicker) =>
+const treePickerToTreeItems = (tree: Ttree<TreePickerNode>) =>
     (id: string): TreeItem<any> => {
         const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ id: '', value: 'InvalidNode' });
         const items = getNodeChildren(node.id)(tree)

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list