[ARVADOS-WORKBENCH2] created: 1.2.0-541-gd1af457

Git user git at public.curoverse.com
Thu Oct 4 16:11:31 EDT 2018


        at  d1af457494186adb375ce2c012cb58685e0556e3 (commit)


commit d1af457494186adb375ce2c012cb58685e0556e3
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date:   Thu Oct 4 22:11:21 2018 +0200

    Move common operations to tree model
    
    Feature #13862
    
    Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>

diff --git a/src/components/file-tree/file-tree.tsx b/src/components/file-tree/file-tree.tsx
index 06fc8b7..165b4bf 100644
--- a/src/components/file-tree/file-tree.tsx
+++ b/src/components/file-tree/file-tree.tsx
@@ -24,7 +24,7 @@ export class FileTree extends React.Component<FileTreeProps> {
             onContextMenu={this.handleContextMenu}
             toggleItemActive={this.handleToggleActive}
             toggleItemOpen={this.handleToggle}
-            onSelectionChange={this.handleSelectionChange} />;
+            toggleItemSelection={this.handleSelectionChange} />;
     }
 
     handleContextMenu = (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => {
diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx
index c892d7d..247661f 100644
--- a/src/components/tree/tree.tsx
+++ b/src/components/tree/tree.tsx
@@ -60,7 +60,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         width: theme.spacing.unit * 3,
         height: theme.spacing.unit * 3,
         margin: `0 ${theme.spacing.unit}px`,
-        color: theme.palette.grey["500"]
+        padding: 0,
+        color: theme.palette.grey["500"],
     }
 });
 
@@ -88,7 +89,7 @@ export interface TreeProps<T> {
     level?: number;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
     showSelection?: boolean;
-    onSelectionChange?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
+    toggleItemSelection?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
     disableRipple?: boolean;
 }
 
@@ -134,7 +135,7 @@ export const Tree = withStyles(styles)(
                                     toggleItemActive={toggleItemActive}
                                     level={level + 1}
                                     onContextMenu={onContextMenu}
-                                    onSelectionChange={this.props.onSelectionChange} />
+                                    toggleItemSelection={this.props.toggleItemSelection} />
                             </Collapse>}
                     </div>)}
             </List>;
@@ -164,10 +165,11 @@ export const Tree = withStyles(styles)(
                 this.props.onContextMenu(event, item)
 
         handleCheckboxChange = (item: TreeItem<T>) => {
-            const { onSelectionChange } = this.props;
-            return onSelectionChange
+            const { toggleItemSelection } = this.props;
+            return toggleItemSelection
                 ? (event: React.MouseEvent<HTMLElement>) => {
-                    onSelectionChange(event, item);
+                    event.stopPropagation();
+                    toggleItemSelection(event, item);
                 }
                 : undefined;
         }
diff --git a/src/models/collection-file.ts b/src/models/collection-file.ts
index d74ada6..37e18cf 100644
--- a/src/models/collection-file.ts
+++ b/src/models/collection-file.ts
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Tree, createTree, setNode } from './tree';
+import { Tree, createTree, setNode, TreeNodeStatus } from './tree';
 
 export type CollectionFilesTree = Tree<CollectionDirectory | CollectionFile>;
 
@@ -60,6 +60,11 @@ export const createCollectionFilesTree = (data: Array<CollectionDirectory | Coll
             children: [],
             id: item.id,
             parent: item.path,
-            value: item
+            value: item,
+            active: false,
+            selected: false,
+            expanded: false,
+            status: TreeNodeStatus.INITIAL
+
         })(tree), createTree<CollectionDirectory | CollectionFile>());
 };
\ No newline at end of file
diff --git a/src/models/tree.test.ts b/src/models/tree.test.ts
index 375a012..b36cfad 100644
--- a/src/models/tree.test.ts
+++ b/src/models/tree.test.ts
@@ -12,77 +12,88 @@ describe('Tree', () => {
     });
 
     it('sets new node', () => {
-        const newTree = Tree.setNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' })(tree);
+        const newTree = Tree.setNode(mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }))(tree);
         expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: [], id: 'Node 1', parent: '', value: 'Value 1' });
     });
 
     it('adds new node reference to parent children', () => {
         const [newTree] = [tree]
-            .map(Tree.setNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }))
-            .map(Tree.setNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' }));
+            .map(Tree.setNode(mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' })))
+            .map(Tree.setNode(mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' })));
 
         expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: ['Node 2'], id: 'Node 1', parent: '', value: 'Value 1' });
     });
 
     it('gets node ancestors', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 3', parent: 'Node 2', value: 'Value 1' }
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 2', value: 'Value 1' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         expect(Tree.getNodeAncestorsIds('Node 3')(newTree)).toEqual(['Node 1', 'Node 2']);
     });
 
     it('gets node descendants', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' },
-            { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         expect(Tree.getNodeDescendantsIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
     });
 
     it('gets root descendants', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' },
-            { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         expect(Tree.getNodeDescendantsIds('')(newTree)).toEqual(['Node 1', 'Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
     });
 
     it('gets node children', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' },
-            { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         expect(Tree.getNodeChildrenIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3']);
     });
 
     it('gets root children', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
-            { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' },
-            { children: [], id: 'Node 3', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         expect(Tree.getNodeChildrenIds('')(newTree)).toEqual(['Node 1', 'Node 3']);
     });
 
     it('maps tree', () => {
         const newTree = [
-            { children: [], id: 'Node 1', parent: '', value: 'Value 1' },
-            { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' },
+            mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }),
+            mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' }),
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
         const mappedTree = Tree.mapTreeValues<string, number>(value => parseInt(value.split(' ')[1], 10))(newTree);
-        expect(Tree.getNode('Node 2')(mappedTree)).toEqual({ children: [], id: 'Node 2', parent: 'Node 1', value: 2 }, );
+        expect(Tree.getNode('Node 2')(mappedTree)).toEqual({ children: [], id: 'Node 2', parent: 'Node 1', value: 2 });
     });
+});
+
+const mockTreeNode = <T>(node: Partial<Tree.TreeNode<T | string>>): Tree.TreeNode<T | string> => ({
+    children: [],
+    id: '',
+    parent: '',
+    value: '',
+    active: false,
+    selected: false,
+    expanded: false,
+    status: Tree.TreeNodeStatus.INITIAL,
 });
\ No newline at end of file
diff --git a/src/models/tree.ts b/src/models/tree.ts
index a5fb49c..b649a40 100644
--- a/src/models/tree.ts
+++ b/src/models/tree.ts
@@ -2,6 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { pipe } from 'lodash/fp';
 export type Tree<T> = Record<string, TreeNode<T>>;
 
 export const TREE_ROOT_ID = '';
@@ -11,6 +12,16 @@ export interface TreeNode<T = any> {
     value: T;
     id: string;
     parent: string;
+    active: boolean;
+    selected: boolean;
+    expanded: boolean;
+    status: TreeNodeStatus;
+}
+
+export enum TreeNodeStatus {
+    INITIAL = 'INITIAL',
+    PENDING = 'PENDING',
+    LOADED = 'LOADED',
 }
 
 export const createTree = <T>(): Tree<T> => ({});
@@ -18,6 +29,7 @@ export const createTree = <T>(): Tree<T> => ({});
 export const getNode = (id: string) => <T>(tree: Tree<T>): TreeNode<T> | undefined => tree[id];
 
 export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
+    console.log(node);
     const [newTree] = [tree]
         .map(tree => getNode(node.id)(tree) === node
             ? tree
@@ -95,6 +107,79 @@ export const getNodeChildrenIds = (id: string) => <T>(tree: Tree<T>): string[] =
 export const mapIdsToNodes = (ids: string[]) => <T>(tree: Tree<T>) =>
     ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode<T> => node !== undefined);
 
+export const activateNode = (id: string) => <T>(tree: Tree<T>) =>
+    mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree);
+
+
+export const expandNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
+    mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree);
+
+export const collapseNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
+    mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree);
+
+export const toggleNodeCollapse = (...ids: string[]) => <T>(tree: Tree<T>) =>
+    mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree);
+
+export const setNodeStatus = (id: string) => (status: TreeNodeStatus) => <T>(tree: Tree<T>) => {
+    const node = getNode(id)(tree);
+    return node
+        ? setNode({ ...node, status })(tree)
+        : tree;
+};
+
+export const toggleNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
+    const node = getNode(id)(tree);
+    return node
+        ? pipe(
+            setNode({ ...node, selected: !node.selected }),
+            toggleAncestorsSelection(id),
+            toggleDescendantsSelection(id))(tree)
+        : tree;
+
+};
+
+export const initTreeNode = <T>(data: Pick<TreeNode<T>, 'id' | 'value'>): TreeNode<T> => ({
+    children: [],
+    active: false,
+    selected: false,
+    expanded: false,
+    status: TreeNodeStatus.INITIAL,
+    parent: '',
+    ...data,
+});
+
+const toggleDescendantsSelection = (id: string) => <T>(tree: Tree<T>) => {
+    const node = getNode(id)(tree);
+    if (node) {
+        return getNodeDescendants(id)(tree)
+            .reduce((newTree, subNode) =>
+                setNode({ ...subNode, selected: node.selected })(newTree),
+                tree);
+    }
+    return tree;
+};
+
+const toggleAncestorsSelection = (id: string) => <T>(tree: Tree<T>) => {
+    const ancestors = getNodeAncestorsIds(id)(tree).reverse();
+    return ancestors.reduce((newTree, parent) => parent ? toggleParentNodeSelection(parent)(newTree) : newTree, tree);
+};
+
+const toggleParentNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
+    const node = getNode(id)(tree);
+    if (node) {
+        const parentNode = getNode(node.id)(tree);
+        if (parentNode) {
+            const selected = parentNode.children
+                .map(id => getNode(id)(tree))
+                .every(node => node !== undefined && node.selected);
+            return setNode({ ...parentNode, selected })(tree);
+        }
+        return setNode(node)(tree);
+    }
+    return tree;
+};
+
+
 const mapNodeValue = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
     ({ ...node, value: mapFn(node.value) });
 
diff --git a/src/services/collection-files-service/collection-manifest-mapper.ts b/src/services/collection-files-service/collection-manifest-mapper.ts
index 0c7e91d..6e64f83 100644
--- a/src/services/collection-files-service/collection-manifest-mapper.ts
+++ b/src/services/collection-files-service/collection-manifest-mapper.ts
@@ -4,7 +4,7 @@
 
 import { uniqBy, groupBy } from 'lodash';
 import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "~/models/keep-manifest";
-import { TreeNode, setNode, createTree, getNodeDescendantsIds, getNodeValue } from '~/models/tree';
+import { TreeNode, setNode, createTree, getNodeDescendantsIds, getNodeValue, TreeNodeStatus } from '~/models/tree';
 import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file';
 
 export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => {
@@ -30,7 +30,11 @@ export const mapCollectionFileToTreeNode = (file: CollectionFile): TreeNode<Coll
     children: [],
     id: file.id,
     parent: file.path,
-    value: file
+    value: file,
+    active: false,
+    selected: false,
+    expanded: false,
+    status: TreeNodeStatus.INITIAL,
 });
 
 export const manifestToCollectionFiles = (manifest: KeepManifest): Array<CollectionDirectory | CollectionFile> => ([
diff --git a/src/store/breadcrumbs/breadcrumbs-actions.ts b/src/store/breadcrumbs/breadcrumbs-actions.ts
index a5ded34..8b1eb2b 100644
--- a/src/store/breadcrumbs/breadcrumbs-actions.ts
+++ b/src/store/breadcrumbs/breadcrumbs-actions.ts
@@ -28,7 +28,7 @@ const getSidePanelTreeBreadcrumbs = (uuid: string) => (treePicker: TreePicker):
     const nodes = getSidePanelTreeBranch(uuid)(treePicker);
     return nodes.map(node =>
         typeof node.value === 'string'
-            ? { label: node.value, uuid: node.nodeId }
+            ? { label: node.value, uuid: node.id }
             : { label: node.value.name, uuid: node.value.uuid });
 };
 
diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts
index 90dedaa..3964ee4 100644
--- a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts
+++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts
@@ -5,7 +5,7 @@
 import { collectionPanelFilesReducer } from "./collection-panel-files-reducer";
 import { collectionPanelFilesAction } from "./collection-panel-files-actions";
 import { CollectionFile, CollectionDirectory, createCollectionFile, createCollectionDirectory } from "~/models/collection-file";
-import { createTree, setNode, getNodeValue, mapTreeValues } from "~/models/tree";
+import { createTree, setNode, getNodeValue, mapTreeValues, TreeNodeStatus } from "~/models/tree";
 import { CollectionPanelFile, CollectionPanelDirectory } from "./collection-panel-files-state";
 
 describe('CollectionPanelFilesReducer', () => {
@@ -26,7 +26,11 @@ describe('CollectionPanelFilesReducer', () => {
         children: [],
         id: file.id,
         parent: file.path,
-        value: file
+        value: file,
+        active: false,
+        selected: false,
+        expanded: false,
+        status: TreeNodeStatus.INITIAL,
     })(tree), createTree<CollectionFile | CollectionDirectory>());
 
     const collectionPanelFilesTree = collectionPanelFilesReducer(
diff --git a/src/store/file-tree-picker/file-tree-picker-actions.ts b/src/store/file-tree-picker/file-tree-picker-actions.ts
deleted file mode 100644
index 18d0f5f..0000000
--- a/src/store/file-tree-picker/file-tree-picker-actions.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { unionize, ofType, UnionOf } from "~/common/unionize";
-
-import { TreePickerNode } from "./file-tree-picker";
-
-export const fileTreePickerActions = unionize({
-    LOAD_TREE_PICKER_NODE: ofType<{ nodeId: string, pickerId: string }>(),
-    LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ nodeId: string, nodes: Array<TreePickerNode>, pickerId: string }>(),
-    TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ nodeId: string, pickerId: string }>(),
-    TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ nodeId: string, pickerId: string }>(),
-    EXPAND_TREE_PICKER_NODES: ofType<{ nodeIds: string[], pickerId: string }>(),
-    RESET_TREE_PICKER: ofType<{ pickerId: string }>()
-});
-
-export type FileTreePickerAction = UnionOf<typeof fileTreePickerActions>;
diff --git a/src/store/file-tree-picker/file-tree-picker-reducer.ts b/src/store/file-tree-picker/file-tree-picker-reducer.ts
deleted file mode 100644
index 59e33a9..0000000
--- a/src/store/file-tree-picker/file-tree-picker-reducer.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "~/models/tree";
-import { TreePicker, TreePickerNode } from "./file-tree-picker";
-import { fileTreePickerActions, FileTreePickerAction } from "./file-tree-picker-actions";
-import { TreeItemStatus } from "~/components/tree/tree";
-import { compose } from "redux";
-import { getNode } from '~/models/tree';
-
-export const fileTreePickerReducer = (state: TreePicker = {}, action: FileTreePickerAction) =>
-    fileTreePickerActions.match(action, {
-        LOAD_TREE_PICKER_NODE: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, setNodeValueWith(setPending)(nodeId)),
-        LOAD_TREE_PICKER_NODE_SUCCESS: ({ nodeId, nodes, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(nodeId), setNodeValueWith(setLoaded)(nodeId))),
-        TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, setNodeValueWith(toggleCollapse)(nodeId)),
-        TOGGLE_TREE_PICKER_NODE_SELECT: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, mapTreeValues(toggleSelect(nodeId))),
-        RESET_TREE_PICKER: ({ pickerId }) =>
-            updateOrCreatePicker(state, pickerId, createTree),
-        EXPAND_TREE_PICKER_NODES: ({ pickerId, nodeIds }) =>
-            updateOrCreatePicker(state, pickerId, mapTreeValues(expand(nodeIds))),
-        default: () => state
-    });
-
-const updateOrCreatePicker = (state: TreePicker, pickerId: string, func: (value: Tree<TreePickerNode>) => Tree<TreePickerNode>) => {
-    const picker = state[pickerId] || createTree();
-    const updatedPicker = func(picker);
-    return { ...state, [pickerId]: updatedPicker };
-};
-
-const expand = (ids: string[]) => (node: TreePickerNode): TreePickerNode =>
-    ids.some(id => id === node.nodeId)
-        ? { ...node, collapsed: false }
-        : node;
-
-const setPending = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, status: TreeItemStatus.PENDING });
-
-const setLoaded = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, status: TreeItemStatus.LOADED });
-
-const toggleCollapse = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, collapsed: !value.collapsed });
-
-const toggleSelect = (nodeId: string) => (value: TreePickerNode): TreePickerNode =>
-    value.nodeId === nodeId
-        ? ({ ...value, selected: !value.selected })
-        : ({ ...value, selected: false });
-
-const receiveNodes = (nodes: Array<TreePickerNode>) => (parent: string) => (state: Tree<TreePickerNode>) => {
-    const parentNode = getNode(parent)(state);
-    let newState = state;
-    if (parentNode) {
-        newState = setNode({ ...parentNode, children: [] })(state);
-    }
-    return nodes.reduce((tree, node) => {
-        const oldNode = getNode(node.nodeId)(state) || { value: {} };
-        const newNode = createTreeNode(parent)(node);
-        const value = { ...oldNode.value, ...newNode.value };
-        return setNode({ ...newNode, value })(tree);
-    }, newState);
-};
-
-const createTreeNode = (parent: string) => (node: TreePickerNode): TreeNode<TreePickerNode> => ({
-    children: [],
-    id: node.nodeId,
-    parent,
-    value: node
-});
diff --git a/src/store/file-tree-picker/file-tree-picker.ts b/src/store/file-tree-picker/file-tree-picker.ts
deleted file mode 100644
index 259a4b8..0000000
--- a/src/store/file-tree-picker/file-tree-picker.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { Tree } from "~/models/tree";
-import { TreeItemStatus } from "~/components/tree/tree";
-
-export type TreePicker = { [key: string]: Tree<TreePickerNode> };
-
-export interface TreePickerNode<Value = any> {
-    nodeId: string;
-    value: Value;
-    selected: boolean;
-    collapsed: boolean;
-    status: TreeItemStatus;
-}
-
-export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({
-    ...data,
-    selected: false,
-    collapsed: true,
-    status: TreeItemStatus.INITIAL
-});
-
-export const getTreePicker = <Value = {}>(id: string) => (state: TreePicker): Tree<TreePickerNode<Value>> | undefined => state[id];
\ No newline at end of file
diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts
index 22a83dd..ec593b9 100644
--- a/src/store/side-panel-tree/side-panel-tree-actions.ts
+++ b/src/store/side-panel-tree/side-panel-tree-actions.ts
@@ -4,14 +4,12 @@
 
 import { Dispatch } from 'redux';
 import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
-import { createTreePickerNode, TreePickerNode } from '~/store/tree-picker/tree-picker';
 import { RootState } from '../store';
 import { ServiceRepository } from '~/services/services';
 import { FilterBuilder } from '~/services/api/filter-builder';
 import { resourcesActions } from '../resources/resources-actions';
 import { getTreePicker, TreePicker } from '../tree-picker/tree-picker';
-import { TreeItemStatus } from "~/components/tree/tree";
-import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
+import { getNodeAncestors, getNodeAncestorsIds, getNode, TreeNode, initTreeNode, TreeNodeStatus } from '~/models/tree';
 import { ProjectResource } from '~/models/project';
 import { OrderBuilder } from '../../services/api/order-builder';
 
@@ -29,11 +27,11 @@ export const SIDE_PANEL_TREE = 'sidePanelTree';
 export const getSidePanelTree = (treePicker: TreePicker) =>
     getTreePicker<ProjectResource | string>(SIDE_PANEL_TREE)(treePicker);
 
-export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array<TreePickerNode<ProjectResource | string>> => {
+export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array<TreeNode<ProjectResource | string>> => {
     const tree = getSidePanelTree(treePicker);
     if (tree) {
-        const ancestors = getNodeAncestors(uuid)(tree).map(node => node.value);
-        const node = getNodeValue(uuid)(tree);
+        const ancestors = getNodeAncestors(uuid)(tree);
+        const node = getNode(uuid)(tree);
         if (node) {
             return [...ancestors, node];
         }
@@ -54,16 +52,16 @@ export const isSidePanelTreeCategory = (id: string) => SIDE_PANEL_CATEGORIES.som
 export const initSidePanelTree = () =>
     (dispatch: Dispatch, getState: () => RootState, { authService }: ServiceRepository) => {
         const rootProjectUuid = authService.getUuid() || '';
-        const nodes = SIDE_PANEL_CATEGORIES.map(nodeId => createTreePickerNode({ nodeId, value: nodeId }));
-        const projectsNode = createTreePickerNode({ nodeId: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS });
+        const nodes = SIDE_PANEL_CATEGORIES.map(id => initTreeNode({ id, value: id }));
+        const projectsNode = initTreeNode({ id: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS });
         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
-            nodeId: '',
+            id: '',
             pickerId: SIDE_PANEL_TREE,
             nodes: [projectsNode, ...nodes]
         }));
         SIDE_PANEL_CATEGORIES.forEach(category => {
             dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
-                nodeId: category,
+                id: category,
                 pickerId: SIDE_PANEL_TREE,
                 nodes: []
             }));
@@ -75,7 +73,7 @@ export const loadSidePanelTreeProjects = (projectUuid: string) =>
         const treePicker = getTreePicker(SIDE_PANEL_TREE)(getState().treePicker);
         const node = treePicker ? getNode(projectUuid)(treePicker) : undefined;
         if (node || projectUuid === '') {
-            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: projectUuid, pickerId: SIDE_PANEL_TREE }));
+            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: projectUuid, pickerId: SIDE_PANEL_TREE }));
             const params = {
                 filters: new FilterBuilder()
                     .addEqual('ownerUuid', projectUuid)
@@ -86,81 +84,81 @@ export const loadSidePanelTreeProjects = (projectUuid: string) =>
             };
             const { items } = await services.projectService.list(params);
             dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
-                nodeId: projectUuid,
+                id: projectUuid,
                 pickerId: SIDE_PANEL_TREE,
-                nodes: items.map(item => createTreePickerNode({ nodeId: item.uuid, value: item })),
+                nodes: items.map(item => initTreeNode({ id: item.uuid, value: item })),
             }));
             dispatch(resourcesActions.SET_RESOURCES(items));
         }
     };
 
-export const activateSidePanelTreeItem = (nodeId: string) =>
+export const activateSidePanelTreeItem = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const node = getSidePanelTreeNode(nodeId)(getState().treePicker);
-        if (node && !node.selected) {
-            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId: SIDE_PANEL_TREE }));
+        const node = getSidePanelTreeNode(id)(getState().treePicker);
+        if (node && !node.active) {
+            dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SIDE_PANEL_TREE }));
         }
-        if (!isSidePanelTreeCategory(nodeId)) {
-            await dispatch<any>(activateSidePanelTreeProject(nodeId));
+        if (!isSidePanelTreeCategory(id)) {
+            await dispatch<any>(activateSidePanelTreeProject(id));
         }
     };
 
-export const activateSidePanelTreeProject = (nodeId: string) =>
+export const activateSidePanelTreeProject = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const { treePicker } = getState();
-        const node = getSidePanelTreeNode(nodeId)(treePicker);
-        if (node && node.status !== TreeItemStatus.LOADED) {
-            await dispatch<any>(loadSidePanelTreeProjects(nodeId));
+        const node = getSidePanelTreeNode(id)(treePicker);
+        if (node && node.status !== TreeNodeStatus.LOADED) {
+            await dispatch<any>(loadSidePanelTreeProjects(id));
         } else if (node === undefined) {
-            await dispatch<any>(activateSidePanelTreeBranch(nodeId));
+            await dispatch<any>(activateSidePanelTreeBranch(id));
         }
         dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({
-            nodeIds: getSidePanelTreeNodeAncestorsIds(nodeId)(treePicker),
+            ids: getSidePanelTreeNodeAncestorsIds(id)(treePicker),
             pickerId: SIDE_PANEL_TREE
         }));
-        dispatch<any>(expandSidePanelTreeItem(nodeId));
+        dispatch<any>(expandSidePanelTreeItem(id));
     };
 
-export const activateSidePanelTreeBranch = (nodeId: string) =>
+export const activateSidePanelTreeBranch = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const ancestors = await services.ancestorsService.ancestors(nodeId, services.authService.getUuid() || '');
+        const ancestors = await services.ancestorsService.ancestors(id, services.authService.getUuid() || '');
         for (const ancestor of ancestors) {
             await dispatch<any>(loadSidePanelTreeProjects(ancestor.uuid));
         }
         dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({
-            nodeIds: ancestors.map(ancestor => ancestor.uuid),
+            ids: ancestors.map(ancestor => ancestor.uuid),
             pickerId: SIDE_PANEL_TREE
         }));
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId: SIDE_PANEL_TREE }));
+        dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SIDE_PANEL_TREE }));
     };
 
-export const toggleSidePanelTreeItemCollapse = (nodeId: string) =>
+export const toggleSidePanelTreeItemCollapse = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState) => {
-        const node = getSidePanelTreeNode(nodeId)(getState().treePicker);
-        if (node && node.status === TreeItemStatus.INITIAL) {
-            await dispatch<any>(loadSidePanelTreeProjects(node.nodeId));
+        const node = getSidePanelTreeNode(id)(getState().treePicker);
+        if (node && node.status === TreeNodeStatus.INITIAL) {
+            await dispatch<any>(loadSidePanelTreeProjects(node.id));
         }
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId: SIDE_PANEL_TREE }));
+        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SIDE_PANEL_TREE }));
     };
 
-export const expandSidePanelTreeItem = (nodeId: string) =>
+export const expandSidePanelTreeItem = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState) => {
-        const node = getSidePanelTreeNode(nodeId)(getState().treePicker);
-        if (node && node.collapsed) {
-            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId: SIDE_PANEL_TREE }));
+        const node = getSidePanelTreeNode(id)(getState().treePicker);
+        if (node && !node.expanded) {
+            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SIDE_PANEL_TREE }));
         }
     };
 
-export const getSidePanelTreeNode = (nodeId: string) => (treePicker: TreePicker) => {
+export const getSidePanelTreeNode = (id: string) => (treePicker: TreePicker) => {
     const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker);
     return sidePanelTree
-        ? getNodeValue(nodeId)(sidePanelTree)
+        ? getNode(id)(sidePanelTree)
         : undefined;
 };
 
-export const getSidePanelTreeNodeAncestorsIds = (nodeId: string) => (treePicker: TreePicker) => {
+export const getSidePanelTreeNodeAncestorsIds = (id: string) => (treePicker: TreePicker) => {
     const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker);
     return sidePanelTree
-        ? getNodeAncestorsIds(nodeId)(sidePanelTree)
+        ? getNodeAncestorsIds(id)(sidePanelTree)
         : [];
 };
diff --git a/src/store/store.ts b/src/store/store.ts
index 4c08a39..21e5029 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -38,7 +38,6 @@ import { progressIndicatorReducer } from './progress-indicator/progress-indicato
 import { runProcessPanelReducer } from '~/store/run-process-panel/run-process-panel-reducer';
 import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
 import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
-import { fileTreePickerReducer } from './file-tree-picker/file-tree-picker-reducer';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -100,6 +99,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     fileUploader: fileUploaderReducer,
     processPanel: processPanelReducer,
     progressIndicator: progressIndicatorReducer,
-    fileTreePicker: fileTreePickerReducer,
     runProcessPanel: runProcessPanelReducer
 });
diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts
index 5b04389..0385766 100644
--- a/src/store/tree-picker/tree-picker-actions.ts
+++ b/src/store/tree-picker/tree-picker-actions.ts
@@ -3,15 +3,16 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { unionize, ofType, UnionOf } from "~/common/unionize";
+import { TreeNode } from '~/models/tree';
 
-import { TreePickerNode } from "./tree-picker";
 
 export const treePickerActions = unionize({
-    LOAD_TREE_PICKER_NODE: ofType<{ nodeId: string, pickerId: string }>(),
-    LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ nodeId: string, nodes: Array<TreePickerNode>, pickerId: string }>(),
-    TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ nodeId: string, pickerId: string }>(),
-    TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ nodeId: string, pickerId: string }>(),
-    EXPAND_TREE_PICKER_NODES: ofType<{ nodeIds: string[], pickerId: string }>(),
+    LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
+    LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array<TreeNode<any>>, pickerId: string }>(),
+    TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string, pickerId: string }>(),
+    ACTIVATE_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
+    TOGGLE_TREE_PICKER_NODE_SELECTION: ofType<{ id: string, pickerId: string }>(),
+    EXPAND_TREE_PICKER_NODES: ofType<{ ids: string[], pickerId: string }>(),
     RESET_TREE_PICKER: ofType<{ pickerId: string }>()
 });
 
diff --git a/src/store/tree-picker/tree-picker-reducer.test.ts b/src/store/tree-picker/tree-picker-reducer.test.ts
index e09d12d..56a3ba5 100644
--- a/src/store/tree-picker/tree-picker-reducer.test.ts
+++ b/src/store/tree-picker/tree-picker-reducer.test.ts
@@ -3,103 +3,103 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { createTree, getNodeValue, getNodeChildrenIds } from "~/models/tree";
-import { TreePickerNode, createTreePickerNode } from "./tree-picker";
 import { treePickerReducer } from "./tree-picker-reducer";
 import { treePickerActions } from "./tree-picker-actions";
 import { TreeItemStatus } from "~/components/tree/tree";
+import { initTreeNode } from '~/models/tree';
 
 describe('TreePickerReducer', () => {
     it('LOAD_TREE_PICKER_NODE - initial state', () => {
-        const tree = createTree<TreePickerNode>();
-        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: '1', pickerId: "projects" }));
+        const tree = createTree<{}>();
+        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }));
         expect(newState).toEqual({ 'projects': tree });
     });
 
     it('LOAD_TREE_PICKER_NODE', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
+        const node = initTreeNode({ id: '1', value: '1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: '1', pickerId: "projects" })));
+            .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({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ id: '1', value: '1' }),
             status: TreeItemStatus.PENDING
         });
     });
 
     it('LOAD_TREE_PICKER_NODE_SUCCESS - initial state', () => {
-        const subNode = createTreePickerNode({ nodeId: '1.1', value: '1.1' });
-        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [subNode], pickerId: "projects" }));
+        const subNode = initTreeNode({ id: '1.1', value: '1.1' });
+        const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [subNode], pickerId: "projects" }));
         expect(getNodeChildrenIds('')(newState.projects)).toEqual(['1.1']);
     });
 
     it('LOAD_TREE_PICKER_NODE_SUCCESS', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
-        const subNode = createTreePickerNode({ nodeId: '1.1', value: '1.1' });
+        const node = initTreeNode({ id: '1', value: '1' });
+        const subNode = initTreeNode({ id: '1.1', value: '1.1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '1', nodes: [subNode], pickerId: "projects" })));
+            .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(getNodeChildrenIds('1')(newState.projects)).toEqual(['1.1']);
         expect(getNodeValue('1')(newState.projects)).toEqual({
-            ...createTreePickerNode({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ id: '1', value: '1' }),
             status: TreeItemStatus.LOADED
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - collapsed', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
+        const node = initTreeNode({ id: '1', value: '1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" })));
+            .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({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ id: '1', value: '1' }),
             collapsed: false
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - expanded', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
+        const node = initTreeNode({ id: '1', value: '1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" })));
+            .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({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ id: '1', value: '1' }),
             collapsed: true
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_SELECT - selected', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
+        const node = initTreeNode({ id: '1', value: '1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" })));
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })));
         expect(getNodeValue('1')(newState.projects)).toEqual({
-            ...createTreePickerNode({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ id: '1', value: '1' }),
             selected: true
         });
     });
 
     it('TOGGLE_TREE_PICKER_NODE_SELECT - not selected', () => {
-        const node = createTreePickerNode({ nodeId: '1', value: '1' });
+        const node = initTreeNode({ id: '1', value: '1' });
         const [newState] = [{
-            projects: createTree<TreePickerNode>()
+            projects: createTree<{}>()
         }]
-            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" })))
-            .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" })));
+            .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })))
+            .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })));
         expect(getNodeValue('1')(newState.projects)).toEqual({
-            ...createTreePickerNode({ nodeId: '1', value: '1' }),
+            ...initTreeNode({ 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 b0d9bc9..054cb44 100644
--- a/src/store/tree-picker/tree-picker-reducer.ts
+++ b/src/store/tree-picker/tree-picker-reducer.ts
@@ -2,72 +2,45 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "~/models/tree";
-import { TreePicker, TreePickerNode } from "./tree-picker";
+import { createTree, TreeNode, setNode, Tree, TreeNodeStatus, setNodeStatus, expandNode } from '~/models/tree';
+import { TreePicker } from "./tree-picker";
 import { treePickerActions, TreePickerAction } from "./tree-picker-actions";
-import { TreeItemStatus } from "~/components/tree/tree";
 import { compose } from "redux";
-import { getNode } from '../../models/tree';
+import { getNode, toggleNodeCollapse, toggleNodeSelection } from '~/models/tree';
+import { activateNode } from '../../models/tree';
 
 export const treePickerReducer = (state: TreePicker = {}, action: TreePickerAction) =>
     treePickerActions.match(action, {
-        LOAD_TREE_PICKER_NODE: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, setNodeValueWith(setPending)(nodeId)),
-        LOAD_TREE_PICKER_NODE_SUCCESS: ({ nodeId, nodes, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(nodeId), setNodeValueWith(setLoaded)(nodeId))),
-        TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, setNodeValueWith(toggleCollapse)(nodeId)),
-        TOGGLE_TREE_PICKER_NODE_SELECT: ({ nodeId, pickerId }) =>
-            updateOrCreatePicker(state, pickerId, mapTreeValues(toggleSelect(nodeId))),
+        LOAD_TREE_PICKER_NODE: ({ id, pickerId }) =>
+            updateOrCreatePicker(state, pickerId, setNodeStatus(id)(TreeNodeStatus.PENDING)),
+        LOAD_TREE_PICKER_NODE_SUCCESS: ({ id, nodes, pickerId }) =>
+            updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(id), setNodeStatus(id)(TreeNodeStatus.LOADED))),
+        TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ id, pickerId }) =>
+            updateOrCreatePicker(state, pickerId, toggleNodeCollapse(id)),
+        ACTIVATE_TREE_PICKER_NODE: ({ id, pickerId }) =>
+            updateOrCreatePicker(state, pickerId, activateNode(id)),
+        TOGGLE_TREE_PICKER_NODE_SELECTION: ({ id, pickerId }) =>
+            updateOrCreatePicker(state, pickerId, toggleNodeSelection(id)),
         RESET_TREE_PICKER: ({ pickerId }) =>
             updateOrCreatePicker(state, pickerId, createTree),
-        EXPAND_TREE_PICKER_NODES: ({ pickerId, nodeIds }) =>
-            updateOrCreatePicker(state, pickerId, mapTreeValues(expand(nodeIds))),
+        EXPAND_TREE_PICKER_NODES: ({ pickerId, ids }) =>
+            updateOrCreatePicker(state, pickerId, expandNode(...ids)),
         default: () => state
     });
 
-const updateOrCreatePicker = (state: TreePicker, pickerId: string, func: (value: Tree<TreePickerNode>) => Tree<TreePickerNode>) => {
+const updateOrCreatePicker = <V>(state: TreePicker, pickerId: string, func: (value: Tree<V>) => Tree<V>) => {
     const picker = state[pickerId] || createTree();
     const updatedPicker = func(picker);
     return { ...state, [pickerId]: updatedPicker };
 };
 
-const expand = (ids: string[]) => (node: TreePickerNode): TreePickerNode =>
-    ids.some(id => id === node.nodeId)
-        ? { ...node, collapsed: false }
-        : node;
-
-const setPending = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, status: TreeItemStatus.PENDING });
-
-const setLoaded = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, status: TreeItemStatus.LOADED });
-
-const toggleCollapse = (value: TreePickerNode): TreePickerNode =>
-    ({ ...value, collapsed: !value.collapsed });
-
-const toggleSelect = (nodeId: string) => (value: TreePickerNode): TreePickerNode =>
-    value.nodeId === nodeId
-        ? ({ ...value, selected: !value.selected })
-        : ({ ...value, selected: false });
-
-const receiveNodes = (nodes: Array<TreePickerNode>) => (parent: string) => (state: Tree<TreePickerNode>) => {
+const receiveNodes = <V>(nodes: Array<TreeNode<V>>) => (parent: string) => (state: Tree<V>) => {
     const parentNode = getNode(parent)(state);
     let newState = state;
     if (parentNode) {
         newState = setNode({ ...parentNode, children: [] })(state);
     }
     return nodes.reduce((tree, node) => {
-        const oldNode = getNode(node.nodeId)(state) || { value: {} };
-        const newNode = createTreeNode(parent)(node);
-        const value = { ...oldNode.value, ...newNode.value };
-        return setNode({ ...newNode, value })(tree);
+        return setNode({ ...node, parent })(tree);
     }, newState);
 };
-
-const createTreeNode = (parent: string) => (node: TreePickerNode): TreeNode<TreePickerNode> => ({
-    children: [],
-    id: node.nodeId,
-    parent,
-    value: node
-});
diff --git a/src/store/tree-picker/tree-picker.ts b/src/store/tree-picker/tree-picker.ts
index 259a4b8..a4127fa 100644
--- a/src/store/tree-picker/tree-picker.ts
+++ b/src/store/tree-picker/tree-picker.ts
@@ -3,17 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Tree } from "~/models/tree";
-import { TreeItemStatus } from "~/components/tree/tree";
+import { TreeItemStatus } from '~/components/tree/tree';
+export type TreePicker = { [key: string]: Tree<any> };
 
-export type TreePicker = { [key: string]: Tree<TreePickerNode> };
-
-export interface TreePickerNode<Value = any> {
-    nodeId: string;
-    value: Value;
-    selected: boolean;
-    collapsed: boolean;
-    status: TreeItemStatus;
-}
+export const getTreePicker = <Value = {}>(id: string) => (state: TreePicker): Tree<Value> | undefined => state[id];
 
 export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({
     ...data,
@@ -21,5 +14,3 @@ export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({
     collapsed: true,
     status: TreeItemStatus.INITIAL
 });
-
-export const getTreePicker = <Value = {}>(id: string) => (state: TreePicker): Tree<TreePickerNode<Value>> | undefined => state[id];
\ No newline at end of file
diff --git a/src/views-components/dialog-file-selection/dialog-file-selection.tsx b/src/views-components/dialog-file-selection/dialog-file-selection.tsx
deleted file mode 100644
index e7185c0..0000000
--- a/src/views-components/dialog-file-selection/dialog-file-selection.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { InjectedFormProps, Field } from 'redux-form';
-import { WithDialogProps } from '~/store/dialog/with-dialog';
-import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions';
-import { FormDialog } from '~/components/form-dialog/form-dialog';
-import { require } from '~/validators/require';
-import { FileTreePickerField } from '~/views-components/file-tree-picker/file-tree-picker';
-
-type FileSelectionProps = WithDialogProps<{}> & InjectedFormProps<CollectionCreateFormDialogData>;
-
-export const DialogFileSelection = (props: FileSelectionProps) =>
-    <FormDialog
-        dialogTitle='Choose a file'
-        formFields={FileSelectionFields}
-        submitLabel='Ok'
-        {...props}
-    />;
-
-const FileSelectionFields = () =>
-    <Field
-        name='tree'
-        validate={FILES_FIELD_VALIDATION}
-        component={FileTreePickerField} />;
-
-const FILES_FIELD_VALIDATION = [require];
\ No newline at end of file
diff --git a/src/views-components/dialog-forms/file-selection-dialog.ts b/src/views-components/dialog-forms/file-selection-dialog.ts
deleted file mode 100644
index 4a3f2af..0000000
--- a/src/views-components/dialog-forms/file-selection-dialog.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { compose } from "redux";
-import { reduxForm } from 'redux-form';
-import { withDialog } from "~/store/dialog/with-dialog";
-import { FILE_SELECTION } from '~/store/file-selection/file-selection-actions';
-import { DialogFileSelection } from '~/views-components/dialog-file-selection/dialog-file-selection';
-import { dialogActions } from '~/store/dialog/dialog-actions';
-
-export const FileSelectionDialog = compose(
-    withDialog(FILE_SELECTION),
-    reduxForm({
-        form: FILE_SELECTION,
-        onSubmit: (data, dispatch) => {
-            console.log(data);
-            dispatch(dialogActions.CLOSE_DIALOG({ id: FILE_SELECTION }));
-            return data;
-        }
-    })
-)(DialogFileSelection);
\ No newline at end of file
diff --git a/src/views-components/file-tree-picker/file-tree-picker.tsx b/src/views-components/file-tree-picker/file-tree-picker.tsx
deleted file mode 100644
index 129cb75..0000000
--- a/src/views-components/file-tree-picker/file-tree-picker.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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 { Typography } from "@material-ui/core";
-import { MainFileTreePicker, MainFileTreePickerProps } from "./main-file-tree-picker";
-import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
-import { ProjectResource } from "~/models/project";
-import { fileTreePickerActions } from "~/store/file-tree-picker/file-tree-picker-actions";
-import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
-import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, CollectionIcon } 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 "~/services/api/filter-builder";
-import { WrappedFieldProps } from 'redux-form';
-import { ResourceKind, extractUuidKind } from '~/models/resource';
-import { GroupContentsResource } from '~/services/groups-service/groups-service';
-import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
-
-type FileTreePickerProps = Pick<MainFileTreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
-
-const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): FileTreePickerProps => ({
-    onContextMenu: () => { return; },
-    toggleItemActive: (nodeId, status, pickerId) => {
-        getNotSelectedTreePickerKind(pickerId)
-            .forEach(pickerId => dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '', pickerId })));
-        dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId }));
-
-        props.onChange(nodeId);
-    },
-    toggleItemOpen: (nodeId, status, pickerId) => {
-        dispatch<any>(toggleItemOpen(nodeId, status, pickerId));
-    }
-});
-
-const toggleItemOpen = (nodeId: string, status: TreeItemStatus, pickerId: string) =>
-    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        if (status === TreeItemStatus.INITIAL) {
-            if (pickerId === TreePickerId.PROJECTS) {
-                dispatch<any>(loadProjectTreePicker(nodeId));
-            } else if (pickerId === TreePickerId.FAVORITES) {
-                dispatch<any>(loadFavoriteTreePicker(nodeId === services.authService.getUuid() ? '' : nodeId));
-            } else {
-                // TODO: load sharedWithMe
-            }
-        } else {
-            dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId }));
-        }
-    };
-
-const getNotSelectedTreePickerKind = (pickerId: string) => {
-    return [TreePickerId.PROJECTS, TreePickerId.FAVORITES, TreePickerId.SHARED_WITH_ME].filter(nodeId => nodeId !== pickerId);
-};
-
-enum TreePickerId {
-    PROJECTS = 'Projects',
-    SHARED_WITH_ME = 'Shared with me',
-    FAVORITES = 'Favorites'
-}
-
-export const FileTreePicker = connect(undefined, mapDispatchToProps)((props: FileTreePickerProps) =>
-    <div style={{ display: 'flex', flexDirection: 'column' }}>
-        <div style={{ flexGrow: 1, overflow: 'auto' }}>
-            <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.PROJECTS} />
-            <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.SHARED_WITH_ME} />
-            <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.FAVORITES} />
-        </div>
-    </div>);
-
-export const loadProjectTreePicker = (nodeId: string) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.PROJECTS }));
-
-        const ownerUuid = nodeId.length === 0 ? services.authService.getUuid() || '' : nodeId;
-
-        const filters = new FilterBuilder()
-            .addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION])
-            .addEqual('ownerUuid', ownerUuid)
-            .getFilters();
-
-            // TODO: loadfiles from collections
-        const { items } = (extractUuidKind(nodeId) === ResourceKind.COLLECTION)
-            ? dispatch<any>(loadCollectionFiles(nodeId))
-            : await services.groupsService.contents(ownerUuid, { filters });
-
-        await dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS));
-    };
-
-export const loadFavoriteTreePicker = (nodeId: string) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const parentId = services.authService.getUuid() || '';
-
-        if (nodeId === '') {
-            dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: parentId, pickerId: TreePickerId.FAVORITES }));
-            const { items } = await services.favoriteService.list(parentId);
-
-            dispatch<any>(receiveTreePickerData(parentId, items as ProjectResource[], TreePickerId.FAVORITES));
-        } else {
-            dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.FAVORITES }));
-            const filters = new FilterBuilder()
-                .addEqual('ownerUuid', nodeId)
-                .getFilters();
-
-                const { items } = (extractUuidKind(nodeId) === ResourceKind.COLLECTION)
-                ? dispatch<any>(loadCollectionFiles(nodeId))
-                : await services.groupsService.contents(parentId, { filters });
-
-
-            dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES));
-        }
-    };
-
-const getProjectPickerIcon = (item: TreeItem<ProjectResource>) => {
-    switch (item.data.name) {
-        case TreePickerId.FAVORITES:
-            return FavoriteIcon;
-        case TreePickerId.PROJECTS:
-            return ProjectsIcon;
-        case TreePickerId.SHARED_WITH_ME:
-            return ShareMeIcon;
-        default:
-            return getResourceIcon(item);
-    }
-};
-
-const getResourceIcon = (item: TreeItem<GroupContentsResource>) => {
-    switch (item.data.kind) {
-        case ResourceKind.COLLECTION:
-            return CollectionIcon;
-        case ResourceKind.PROJECT:
-            return ProjectIcon;
-        default:
-            return ProjectIcon;
-    }
-};
-
-const renderTreeItem = (item: TreeItem<ProjectResource>) =>
-    <ListItemTextIcon
-        icon={getProjectPickerIcon(item)}
-        name={item.data.name}
-        isActive={item.active}
-        hasMargin={true} />;
-
-
-export const receiveTreePickerData = (nodeId: string, items: GroupContentsResource[] = [], pickerId: string) =>
-    (dispatch: Dispatch) => {
-        dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
-            nodeId,
-            nodes: items.map(item => createTreePickerNode({ nodeId: item.uuid, value: item })),
-            pickerId,
-        }));
-
-        dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId }));
-    };
-
-export const FileTreePickerField = (props: WrappedFieldProps) =>
-    <div style={{ height: '200px', display: 'flex', flexDirection: 'column' }}>
-        <FileTreePicker onChange={handleChange(props)} />
-        {props.meta.dirty && props.meta.error &&
-            <Typography variant='caption' color='error'>
-                {props.meta.error}
-            </Typography>}
-    </div>;
-
-const handleChange = (props: WrappedFieldProps) => (value: string) =>
-    props.input.value === value
-        ? props.input.onChange('')
-        : props.input.onChange(value);
-
diff --git a/src/views-components/file-tree-picker/main-file-tree-picker.ts b/src/views-components/file-tree-picker/main-file-tree-picker.ts
deleted file mode 100644
index dc52ea6..0000000
--- a/src/views-components/file-tree-picker/main-file-tree-picker.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { connect } from "react-redux";
-import { Tree, TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree";
-import { RootState } from "~/store/store";
-import { createTreePickerNode, TreePickerNode } from "~/store/file-tree-picker/file-tree-picker";
-import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree } from "~/models/tree";
-import { Dispatch } from "redux";
-
-export interface MainFileTreePickerProps {
-    pickerId: string;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>, nodeId: string, pickerId: string) => void;
-    toggleItemOpen: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
-    toggleItemActive: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
-}
-
-const memoizedMapStateToProps = () => {
-    let prevTree: Ttree<TreePickerNode>;
-    let mappedProps: Pick<TreeProps<any>, 'items'>;
-    return (state: RootState, props: MainFileTreePickerProps): Pick<TreeProps<any>, 'items'> => {
-        const tree = state.treePicker[props.pickerId] || createTree();
-        if(tree !== prevTree){
-            prevTree = tree;
-            mappedProps = {
-                items: getNodeChildrenIds('')(tree)
-                    .map(treePickerToTreeItems(tree))
-            };
-        }
-        return mappedProps;
-    };
-};
-
-const mapDispatchToProps = (dispatch: Dispatch, props: MainFileTreePickerProps): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive'> => ({
-    onContextMenu: (event, item) => props.onContextMenu(event, item.id, props.pickerId),
-    toggleItemActive: (id, status) => props.toggleItemActive(id, status, props.pickerId),
-    toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId)
-});
-
-export const MainFileTreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
-
-const treePickerToTreeItems = (tree: Ttree<TreePickerNode>) =>
-    (id: string): TreeItem<any> => {
-        const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ nodeId: '', value: 'InvalidNode' });
-        const items = getNodeChildrenIds(node.nodeId)(tree)
-            .map(treePickerToTreeItems(tree));
-        return {
-            active: node.selected,
-            data: node.value,
-            id: node.nodeId,
-            items: items.length > 0 ? items : undefined,
-            open: !node.collapsed,
-            status: node.status
-        };
-    };
-
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 9139ee7..16ab122 100644
--- a/src/views-components/project-tree-picker/project-tree-picker.tsx
+++ b/src/views-components/project-tree-picker/project-tree-picker.tsx
@@ -12,40 +12,43 @@ 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, 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 "~/services/api/filter-builder";
 import { WrappedFieldProps } from 'redux-form';
+import { initTreeNode } from '~/models/tree';
 
-type ProjectTreePickerProps = Pick<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
+type ProjectTreePickerProps = Pick<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
 
 const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): ProjectTreePickerProps => ({
     onContextMenu: () => { return; },
-    toggleItemActive: (nodeId, status, pickerId) => {
+    toggleItemActive: (id, status, pickerId) => {
         getNotSelectedTreePickerKind(pickerId)
-            .forEach(pickerId => dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '', pickerId })));
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId }));
+            .forEach(pickerId => dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '', pickerId })));
+        dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId }));
 
-        props.onChange(nodeId);
+        props.onChange(id);
+    },
+    toggleItemOpen: (id, status, pickerId) => {
+        dispatch<any>(toggleItemOpen(id, status, pickerId));
+    },
+    toggleItemSelection: (id, pickerId) => {
+        dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId }));
     },
-    toggleItemOpen: (nodeId, status, pickerId) => {
-        dispatch<any>(toggleItemOpen(nodeId, status, pickerId));
-    }
 });
 
-const toggleItemOpen = (nodeId: string, status: TreeItemStatus, pickerId: string) =>
+const toggleItemOpen = (id: string, status: TreeItemStatus, pickerId: string) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         if (status === TreeItemStatus.INITIAL) {
             if (pickerId === TreePickerId.PROJECTS) {
-                dispatch<any>(loadProjectTreePickerProjects(nodeId));
+                dispatch<any>(loadProjectTreePickerProjects(id));
             } else if (pickerId === TreePickerId.FAVORITES) {
-                dispatch<any>(loadFavoriteTreePickerProjects(nodeId === services.authService.getUuid() ? '' : nodeId));
+                dispatch<any>(loadFavoriteTreePickerProjects(id === services.authService.getUuid() ? '' : id));
             } else {
                 // TODO: load sharedWithMe
             }
         } else {
-            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId }));
+            dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
         }
     };
 
@@ -73,11 +76,11 @@ export const ProjectTreePicker = connect(undefined, mapDispatchToProps)((props:
 
 
 // TODO: move action creator to store directory
-export const loadProjectTreePickerProjects = (nodeId: string) =>
+export const loadProjectTreePickerProjects = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.PROJECTS }));
+        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerId.PROJECTS }));
 
-        const ownerUuid = nodeId.length === 0 ? services.authService.getUuid() || '' : nodeId;
+        const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id;
 
         const filters = new FilterBuilder()
             .addEqual('ownerUuid', ownerUuid)
@@ -85,27 +88,27 @@ export const loadProjectTreePickerProjects = (nodeId: string) =>
 
         const { items } = await services.projectService.list({ filters });
 
-        dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS));
+        dispatch<any>(receiveTreePickerData(id, items, TreePickerId.PROJECTS));
     };
 
-export const loadFavoriteTreePickerProjects = (nodeId: string) =>
+export const loadFavoriteTreePickerProjects = (id: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const parentId = services.authService.getUuid() || '';
 
-        if (nodeId === '') {
-            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: parentId, pickerId: TreePickerId.FAVORITES }));
+        if (id === '') {
+            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: parentId, pickerId: TreePickerId.FAVORITES }));
             const { items } = await services.favoriteService.list(parentId);
 
             dispatch<any>(receiveTreePickerData(parentId, items as ProjectResource[], TreePickerId.FAVORITES));
         } else {
-            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.FAVORITES }));
+            dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerId.FAVORITES }));
             const filters = new FilterBuilder()
-                .addEqual('ownerUuid', nodeId)
+                .addEqual('ownerUuid', id)
                 .getFilters();
 
             const { items } = await services.projectService.list({ filters });
 
-            dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES));
+            dispatch<any>(receiveTreePickerData(id, items, TreePickerId.FAVORITES));
         }
 
     };
@@ -132,15 +135,15 @@ const renderTreeItem = (item: TreeItem<ProjectResource>) =>
 
 
 // TODO: move action creator to store directory
-export const receiveTreePickerData = (nodeId: string, projects: ProjectResource[], pickerId: string) =>
+export const receiveTreePickerData = (id: string, projects: ProjectResource[], pickerId: string) =>
     (dispatch: Dispatch) => {
         dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
-            nodeId,
-            nodes: projects.map(project => createTreePickerNode({ nodeId: project.uuid, value: project })),
+            id,
+            nodes: projects.map(project => initTreeNode({ id: project.uuid, value: project })),
             pickerId,
         }));
 
-        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId }));
+        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
     };
 
 export const ProjectTreePickerField = (props: WrappedFieldProps) =>
diff --git a/src/views-components/side-panel-tree/side-panel-tree.tsx b/src/views-components/side-panel-tree/side-panel-tree.tsx
index 4d4760f..96224cf 100644
--- a/src/views-components/side-panel-tree/side-panel-tree.tsx
+++ b/src/views-components/side-panel-tree/side-panel-tree.tsx
@@ -13,13 +13,13 @@ import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, TrashIcon } from
 import { RecentIcon, WorkflowIcon } from '~/components/icon/icon';
 import { activateSidePanelTreeItem, toggleSidePanelTreeItemCollapse, SIDE_PANEL_TREE, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
 import { openSidePanelContextMenu } from '~/store/context-menu/context-menu-actions';
-
+import { noop } from 'lodash';
 export interface SidePanelTreeProps {
     onItemActivation: (id: string) => void;
     sidePanelProgress?: boolean;
 }
 
-type SidePanelTreeActionProps = Pick<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
+type SidePanelTreeActionProps = Pick<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
 
 const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): SidePanelTreeActionProps => ({
     onContextMenu: (event, id) => {
@@ -31,7 +31,8 @@ const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): Side
     },
     toggleItemOpen: (nodeId) => {
         dispatch<any>(toggleSidePanelTreeItemCollapse(nodeId));
-    }
+    },
+    toggleItemSelection: noop,
 });
 
 export const SidePanelTree = connect(undefined, mapDispatchToProps)(
diff --git a/src/views-components/tree-picker/tree-picker.ts b/src/views-components/tree-picker/tree-picker.ts
index 8b7630a..ff11913 100644
--- a/src/views-components/tree-picker/tree-picker.ts
+++ b/src/views-components/tree-picker/tree-picker.ts
@@ -5,23 +5,24 @@
 import { connect } from "react-redux";
 import { Tree, TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree";
 import { RootState } from "~/store/store";
-import { createTreePickerNode, TreePickerNode } from "~/store/tree-picker/tree-picker";
-import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree } from "~/models/tree";
+import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree, getNode, TreeNodeStatus } from '~/models/tree';
 import { Dispatch } from "redux";
+import { initTreeNode } from '../../models/tree';
 
 export interface TreePickerProps {
     pickerId: string;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, nodeId: string, pickerId: string) => void;
     toggleItemOpen: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
     toggleItemActive: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
+    toggleItemSelection: (nodeId: string, pickerId: string) => void;
 }
 
 const memoizedMapStateToProps = () => {
-    let prevTree: Ttree<TreePickerNode>;
+    let prevTree: Ttree<any>;
     let mappedProps: Pick<TreeProps<any>, 'items'>;
     return (state: RootState, props: TreePickerProps): Pick<TreeProps<any>, 'items'> => {
         const tree = state.treePicker[props.pickerId] || createTree();
-        if(tree !== prevTree){
+        if (tree !== prevTree) {
             prevTree = tree;
             mappedProps = {
                 items: getNodeChildrenIds('')(tree)
@@ -32,26 +33,39 @@ const memoizedMapStateToProps = () => {
     };
 };
 
-const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive'> => ({
+const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
     onContextMenu: (event, item) => props.onContextMenu(event, item.id, props.pickerId),
     toggleItemActive: (id, status) => props.toggleItemActive(id, status, props.pickerId),
-    toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId)
+    toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId),
+    toggleItemSelection: (_, item) => props.toggleItemSelection(item.id, props.pickerId),
 });
 
 export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
 
-const treePickerToTreeItems = (tree: Ttree<TreePickerNode>) =>
+const treePickerToTreeItems = (tree: Ttree<any>) =>
     (id: string): TreeItem<any> => {
-        const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ nodeId: '', value: 'InvalidNode' });
-        const items = getNodeChildrenIds(node.nodeId)(tree)
+        const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
+        const items = getNodeChildrenIds(node.id)(tree)
             .map(treePickerToTreeItems(tree));
         return {
-            active: node.selected,
+            active: node.active,
             data: node.value,
-            id: node.nodeId,
+            id: node.id,
             items: items.length > 0 ? items : undefined,
-            open: !node.collapsed,
-            status: node.status
+            open: node.expanded,
+            selected: node.selected,
+            status: treeNodeStatusToTreeItem(node.status),
         };
     };
 
+export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => {
+    switch (status) {
+        case TreeNodeStatus.INITIAL:
+            return TreeItemStatus.INITIAL;
+        case TreeNodeStatus.PENDING:
+            return TreeItemStatus.PENDING;
+        case TreeNodeStatus.LOADED:
+            return TreeItemStatus.LOADED;
+    }
+};
+
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 553afa4..49ce472 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -41,7 +41,6 @@ import { SharedWithMePanel } from '~/views/shared-with-me-panel/shared-with-me-p
 import { RunProcessPanel } from '~/views/run-process-panel/run-process-panel';
 import SplitterLayout from 'react-splitter-layout';
 import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
-import { FileSelectionDialog } from '~/views-components/dialog-forms/file-selection-dialog';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -114,8 +113,6 @@ export const WorkbenchPanel =
             <CreateProjectDialog />
             <CurrentTokenDialog />
             <FileRemoveDialog />
-            <FileRemoveDialog />
-            <FileSelectionDialog />
             <FilesUploadCollectionDialog />
             <MoveCollectionDialog />
             <MoveProcessDialog />

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list