merge master
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 17 Aug 2018 09:57:05 +0000 (11:57 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 17 Aug 2018 09:57:05 +0000 (11:57 +0200)
Feature #13902

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

15 files changed:
1  2 
src/components/tree/tree.tsx
src/index.tsx
src/store/data-explorer/data-explorer-reducer.test.tsx
src/store/project/project-reducer.test.ts
src/store/tree-picker/tree-picker-reducer.test.ts
src/store/tree-picker/tree-picker-reducer.ts
src/store/tree-picker/tree-picker.ts
src/views-components/context-menu/action-sets/collection-action-set.ts
src/views-components/context-menu/action-sets/collection-resource-action-set.ts
src/views-components/context-menu/action-sets/project-action-set.ts
src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx
src/views-components/dialog-create/dialog-collection-create-selected.tsx
src/views-components/project-tree-picker/project-tree-picker.tsx
src/views-components/tree-picker/tree-picker.ts
src/views/workbench/workbench.tsx

index 7f45e2d7f4a66aa0fc6016cce4deb73d8bb79fe5,3e8cf904cfaae2274d1273bb81f1f77048b34c2a..06d7cbbfc3110c2cf998dab1e72c8bacb944ace1
@@@ -9,7 -9,7 +9,7 @@@ import { ReactElement } from "react"
  import CircularProgress from '@material-ui/core/CircularProgress';
  import * as classnames from "classnames";
  
- import { ArvadosTheme } from '../../common/custom-theme';
+ import { ArvadosTheme } from '~/common/custom-theme';
  import { SidePanelRightArrowIcon } from '../icon/icon';
  
  type CssRules = 'list'
@@@ -65,9 -65,9 +65,9 @@@ const styles: StyleRulesCallback<CssRul
  });
  
  export enum TreeItemStatus {
 -    INITIAL,
 -    PENDING,
 -    LOADED
 +    INITIAL = 'initial',
 +    PENDING = 'pending',
 +    LOADED = 'loaded'
  }
  
  export interface TreeItem<T> {
@@@ -110,9 -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 --combined src/index.tsx
index bf3002e9c724713257014d36cfc5aa8eeb9e93c3,cfdbb46cf55882743fd714e7520a9c64884a8d22..461904c43bdad9eb02a71fe95eaebbff1e931de9
@@@ -27,7 -27,6 +27,7 @@@ import { collectionFilesActionSet } fro
  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);
@@@ -41,13 -40,12 +41,13 @@@ addMenuActionSet(ContextMenuKind.COLLEC
  fetchConfig()
      .then(config => {
          const history = createBrowserHistory();
-         const services = createServices(config.API_HOST);
+         const services = createServices(config);
          const store = configureStore(history, services);
  
          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}/>;
  
index 11296ce5606482495ec44a7ac4064eb389742fba,6b1c90798962032ddc273005b1f1b0c7d41de123..56ea3e84b21defc8c8f71e319940be2d8ab9d8a6
@@@ -14,7 -14,7 +14,9 @@@ describe('data-explorer-reducer', () =
              name: "Column 1",
              render: jest.fn(),
              selected: true,
-             configurable: false
++            sortDirection: SortDirection.ASC,
++            filters: [],
+             configurable: true
          }];
          const state = dataExplorerReducer(undefined,
              dataExplorerActions.SET_COLUMNS({ id: "Data explorer", columns }));
              name: "Column 1",
              render: jest.fn(),
              selected: true,
 -            configurable: true,
 -            sortDirection: SortDirection.ASC
 +            sortDirection: SortDirection.ASC,
-             configurable: false
++            filters: [],
++            configurable: true
          }, {
              name: "Column 2",
              render: jest.fn(),
              selected: true,
+             configurable: true,
              sortDirection: SortDirection.NONE,
-             configurable: false
++            filters: [],
          }];
          const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
              dataExplorerActions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
@@@ -46,7 -46,7 +50,9 @@@
              name: "Column 1",
              render: jest.fn(),
              selected: true,
-             configurable: false
 -            configurable: true
++            configurable: true,
++            sortDirection: SortDirection.NONE,
++            filters: [],
          }];
  
          const filters: DataTableFilterItem[] = [{
@@@ -59,7 -59,7 +65,7 @@@
      });
  
      it('should set items', () => {
-         const state = dataExplorerReducer({ "Data explorer": undefined },
+         const state = dataExplorerReducer({},
              dataExplorerActions.SET_ITEMS({
                  id: "Data explorer",
                  items: ["Item 1", "Item 2"],
      });
  
      it('should set page', () => {
-         const state = dataExplorerReducer({ "Data explorer": undefined },
+         const state = dataExplorerReducer({},
              dataExplorerActions.SET_PAGE({ id: "Data explorer", page: 2 }));
          expect(state["Data explorer"].page).toEqual(2);
      });
  
      it('should set rows per page', () => {
-         const state = dataExplorerReducer({ "Data explorer": undefined },
+         const state = dataExplorerReducer({},
              dataExplorerActions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
          expect(state["Data explorer"].rowsPerPage).toEqual(5);
      });
index efee5415f96991e7a605e2a89fe928bf49558922,bb60e396946a588f8d93a1c1f7e6803461ba82eb..8cd3121eecb871c8d9fa538bcfdb6983cf1322f6
@@@ -4,8 -4,8 +4,8 @@@
  
  import { projectsReducer, getTreePath } from "./project-reducer";
  import { projectActions } from "./project-action";
- import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
- import { mockProjectResource } from "../../models/test-utils";
+ import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
+ import { mockProjectResource } from "~/models/test-utils";
  
  describe('project-reducer', () => {
  
@@@ -21,7 -21,7 +21,7 @@@
                  id: "1",
                  items: [],
                  data: mockProjectResource({ uuid: "1" }),
 -                status: 0
 +                status: TreeItemStatus.INITIAL
              }, {
                  active: false,
                  open: false,
              creator: {
                  opened: false,
                  ownerUuid: "",
+             },
+             updater: {
+                 opened: false,
+                 uuid: ''
              }
          });
      });
@@@ -50,6 -54,7 +54,7 @@@
              }],
              currentItemId: "1",
              creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
          const project = {
              items: [{
@@@ -61,6 -66,7 +66,7 @@@
              }],
              currentItemId: "",
              creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
  
          const state = projectsReducer(initialState, projectActions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id));
@@@ -77,7 -83,8 +83,8 @@@
                  status: TreeItemStatus.PENDING
              }],
              currentItemId: "1",
-             creator: { opened: false, ownerUuid: "" }
+             creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
          const project = {
              items: [{
@@@ -89,6 -96,7 +96,7 @@@
              }],
              currentItemId: "1",
              creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
  
          const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
                  status: TreeItemStatus.PENDING,
              }],
              currentItemId: "1",
-             creator: { opened: false, ownerUuid: "" }
+             creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
          const project = {
              items: [{
              }],
              currentItemId: "1",
              creator: { opened: false, ownerUuid: "" },
+             updater: { opened: false, uuid: '' }
          };
  
          const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
index 0b37aeea38d8a54fedcccb40853b508eafe9e39f,3248cb2efba7f06af804c0b4f6a169de02170a67..2432b8429ec375137cccdcc12c78f1093c86761a
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { createTree, getNodeValue, getNodeChildren } from "../../models/tree";
+ import { createTree, getNodeValue, getNodeChildren } 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 { 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
          });
index e4ca2629d2ecc6caa2ba333cbf7ce937c6571db1,8d61714cc9f744929587dd7f96f613ba18120f67..cf67aa7600a011a7f365d1c35d4f17fdca963dd1
@@@ -2,35 -2,25 +2,35 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "../../models/tree";
 -import { createTree, setNodeValueWith, TreeNode, setNode, 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";
+ 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
      });
  
@@@ -48,8 -38,8 +48,8 @@@ const toggleSelect = (id: string) => (v
          ? ({ ...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);
@@@ -59,4 -49,4 +59,4 @@@ const createTreeNode = (parent: string
      id: node.id,
      parent,
      value: node
- });
+ });
index 5cb2d97f1df1c8257199e3c006ac529aa9d46eb3,e19ce3a7a48c0d162c1fb462c00ddb26d8b30c57..5029cbe628c93a611fea3459ded13f36b26c182b
@@@ -2,10 -2,10 +2,10 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { Tree } from "../../models/tree";
- import { TreeItemStatus } from "../../components/tree/tree";
+ 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,9 -15,9 +15,9 @@@
      status: TreeItemStatus;
  }
  
 -export const createTreePickerNode = (data: {id: string, value: any}) => ({
 +export const createTreePickerNode = (data: { id: string, value: any }) => ({
      ...data,
      selected: false,
      collapsed: true,
      status: TreeItemStatus.INITIAL
- });
+ });
index 75ffdb3b82d0892bc3164354411feacc11d7a21b,4561f9d308879b981c8a9a72dc32c29dc701ddcc..e6dd686af67022757de31660c5b93c8761387f67
@@@ -4,11 -4,10 +4,11 @@@
  
  import { ContextMenuActionSet } from "../context-menu-action-set";
  import { ToggleFavoriteAction } from "../actions/favorite-action";
- import { toggleFavorite } from "../../../store/favorites/favorites-actions";
- import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "../../../components/icon/icon";
- import { openUpdater } from "../../../store/collections/updater/collection-updater-action";
- import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+ import { toggleFavorite } from "~/store/favorites/favorites-actions";
+ import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "~/components/icon/icon";
+ import { openUpdater } from "~/store/collections/updater/collection-updater-action";
+ import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 +import { openMoveToDialog } from "../../move-to-dialog/move-to-dialog";
  
  export const collectionActionSet: ContextMenuActionSet = [[
      {
@@@ -28,8 -27,8 +28,8 @@@
      {
          icon: MoveToIcon,
          name: "Move to",
 -        execute: (dispatch, resource) => {
 -            // add code
 +        execute: (dispatch) => {
 +            dispatch<any>(openMoveToDialog());
          }
      },
      {
index f0e7efeb1cd5cfaf70738520af222f2576851c54,7d8364bd70ea22b4c29bb69c5c11b95699f6765b..fd2f8d752579cd12ca33c09bc9462290e89f3d25
@@@ -4,11 -4,10 +4,11 @@@
  
  import { ContextMenuActionSet } from "../context-menu-action-set";
  import { ToggleFavoriteAction } from "../actions/favorite-action";
- import { toggleFavorite } from "../../../store/favorites/favorites-actions";
- import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "../../../components/icon/icon";
- import { openUpdater } from "../../../store/collections/updater/collection-updater-action";
- import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+ import { toggleFavorite } from "~/store/favorites/favorites-actions";
+ import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon";
+ import { openUpdater } from "~/store/collections/updater/collection-updater-action";
+ import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 +import { openMoveToDialog } from "../../move-to-dialog/move-to-dialog";
  
  export const collectionResourceActionSet: ContextMenuActionSet = [[
      {
@@@ -28,8 -27,8 +28,8 @@@
      {
          icon: MoveToIcon,
          name: "Move to",
 -        execute: (dispatch, resource) => {
 -            // add code
 +        execute: (dispatch) => {
 +            dispatch<any>(openMoveToDialog());
          }
      },
      {
index 06ffb9d8f2aa4aabeb5721cdce698ca95b5032ee,1b000c88fcee77ec2c39a844d3476001fca725a7..ca9a4f674a3b817dd1cdafae9953035db5b11d4a
@@@ -2,35 -2,39 +2,47 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { reset } from "redux-form";
+ import { reset, initialize } from "redux-form";
  
  import { ContextMenuActionSet } from "../context-menu-action-set";
- import { projectActions } from "../../../store/project/project-action";
- import { NewProjectIcon, MoveToIcon } from "../../../components/icon/icon";
+ import { projectActions, PROJECT_FORM_NAME } from "~/store/project/project-action";
 -import { NewProjectIcon, RenameIcon } from "~/components/icon/icon";
++import { NewProjectIcon, MoveToIcon, RenameIcon } from "~/components/icon/icon";
  import { ToggleFavoriteAction } from "../actions/favorite-action";
- import { toggleFavorite } from "../../../store/favorites/favorites-actions";
- import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+ import { toggleFavorite } from "~/store/favorites/favorites-actions";
+ import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 +import { openMoveToDialog } from "../../move-to-dialog/move-to-dialog";
  import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
  
- export const projectActionSet: ContextMenuActionSet = [[{
-     icon: NewProjectIcon,
-     name: "New project",
-     execute: (dispatch, resource) => {
-         dispatch(reset(PROJECT_CREATE_DIALOG));
-         dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
-     }
- }, {
-     component: ToggleFavoriteAction,
-     execute: (dispatch, resource) => {
-         dispatch<any>(toggleFavorite(resource)).then(() => {
-             dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
-         });
-     }
- }, {
-     icon: MoveToIcon,
-     name: "Move to",
-     execute: (dispatch) => {
-         dispatch<any>(openMoveToDialog());
-     }
- },]];
+ export const projectActionSet: ContextMenuActionSet = [[
+     {
+         icon: NewProjectIcon,
+         name: "New project",
+         execute: (dispatch, resource) => {
+             dispatch(reset(PROJECT_CREATE_DIALOG));
+             dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+         }
+     },
+     {
+         icon: RenameIcon,
+         name: "Edit project",
+         execute: (dispatch, resource) => {
+             dispatch(projectActions.OPEN_PROJECT_UPDATER({ uuid: resource.uuid }));
+             dispatch(initialize(PROJECT_FORM_NAME, { name: resource.name, description: resource.description }));
+         }
+     },
+     {
+         component: ToggleFavoriteAction,
+         execute: (dispatch, resource) => {
+             dispatch<any>(toggleFavorite(resource)).then(() => {
+                 dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
+             });
+         }
 -    }
++    },
++    {
++        icon: MoveToIcon,
++        name: "Move to",
++        execute: (dispatch) => {
++            dispatch<any>(openMoveToDialog());
++        }
++    },
+ ]];
index ece0639510fb664daa9f01014cf137bfb3e23946,46bc724d7704fdaa067640f8a17675774788d4c7..60ce05c7eab6b8040f6517040de9e2779d8d0074
@@@ -4,15 -4,17 +4,15 @@@
  
  import { Dispatch } from "redux";
  import { reduxForm, reset, startSubmit, stopSubmit } from "redux-form";
- import { withDialog } from "../../store/dialog/with-dialog";
- import { dialogActions } from "../../store/dialog/dialog-actions";
+ 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: {} }));
      };
  
index edaa527c8f403dea939df0efe5834d4643ecc35b,af2536df9512a66b89a1d013a5345c6b579bb690..ad684d780b008a274ce9a99de8f44f13d267da6c
@@@ -5,9 -5,9 +5,9 @@@
  import * as React from "react";
  import { InjectedFormProps, Field, WrappedFieldProps } from "redux-form";
  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";
+ 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/validators";
  import { ProjectTreePicker } from "../project-tree-picker/project-tree-picker";
  
  export const DialogCollectionCreateWithSelected = (props: WithDialogProps<string> & InjectedFormProps<{ name: string }>) =>
@@@ -48,7 -48,9 +48,7 @@@
                      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>
index 0338534d3b78fb4e868428503da34dae2496f291,9143c47a2da5efe25b4d983015946706a8487c7f..e18af9e413b6674ed14d1ddbaeab8e8122af64a6
@@@ -6,138 -6,70 +6,138 @@@ 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, 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";
++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, status, pickerId) => {
 +        getNotSelectedTreePickerKind(pickerId)
 +            .forEach(pickerId => dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id: '', pickerId })));
 +        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id, pickerId }));
  
 -const mapDispatchToProps = (dispatch: Dispatch, props: {onChange: (projectUuid: string) => void}): ProjectTreePickerProps => ({
 -    toggleItemActive: id => {
 -        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id }));
          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}}>
 +    <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} />
 +        <div style={{ flexGrow: 1, overflow: 'auto' }}>
 +            <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>);
  
 +
  // 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;
  
-         const filters = FilterBuilder
-             .create()
-             .addEqual('ownerUuid', ownerUuid);
+         const filters = new FilterBuilder()
+             .addEqual('ownerUuid', ownerUuid)
+             .getFilters();
  
          const { items } = await services.projectService.list({ filters });
  
 -        dispatch<any>(receiveProjectTreePickerData(id, items));
 +        dispatch<any>(receiveTreePickerData(id, items, TreePickerKind.PROJECTS));
      };
  
-             const filters = FilterBuilder
-                 .create()
-                 .addEqual('ownerUuid', id);
 +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 = new FilterBuilder()
++                .addEqual('ownerUuid', id)
++                .getFilters();
 +
 +            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 }));
++
 +        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
      };
 +
index a0eae9f189766297fc1c77c637a93bca3dcda850,09a07443f26b9097ddcccfa9ded1b95d2dea5d85..fb05b81897574994d7aaeea638761c0c8ad0861d
@@@ -3,35 -3,34 +3,35 @@@
  // 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/tree-picker/tree-picker";
- import { getNodeValue, getNodeChildren, Tree as Ttree, createTree } from "../../models/tree";
- import { Dispatch } from "node_modules/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 "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)
index c8d447445a44edeb78b47829ad7f3bb52aefe325,a38afb7ac30e32b8f06f265a1635693aead54ca9..9210fb32d54073c5dea97c269975f8af0e9b5778
@@@ -6,50 -6,50 +6,51 @@@ import * as React from 'react'
  import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
  import Drawer from '@material-ui/core/Drawer';
  import { connect, DispatchProp } from "react-redux";
- import { Route, Switch, RouteComponentProps } from "react-router";
- import { login, logout } from "../../store/auth/auth-action";
- import { User } from "../../models/user";
- import { RootState } from "../../store/store";
- import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
- import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
+ import { Route, RouteComponentProps, Switch, Redirect } from "react-router";
+ import { login, logout } from "~/store/auth/auth-action";
+ import { User } from "~/models/user";
+ import { RootState } from "~/store/store";
+ import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '~/views-components/main-app-bar/main-app-bar';
+ import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
  import { push } from 'react-router-redux';
  import { reset } from 'redux-form';
- import { ProjectTree } from '../../views-components/project-tree/project-tree';
- import { TreeItem } from "../../components/tree/tree";
- import { getTreePath } from '../../store/project/project-reducer';
- import { sidePanelActions } from '../../store/side-panel/side-panel-action';
- import { SidePanel, SidePanelItem } from '../../components/side-panel/side-panel';
- import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
- import { projectActions } from "../../store/project/project-action";
- import { collectionCreateActions } from '../../store/collections/creator/collection-creator-action';
- import { ProjectPanel } from "../project-panel/project-panel";
- import { DetailsPanel } from '../../views-components/details-panel/details-panel';
- import { ArvadosTheme } from '../../common/custom-theme';
- import { CreateProjectDialog } from "../../views-components/create-project-dialog/create-project-dialog";
+ import { ProjectTree } from '~/views-components/project-tree/project-tree';
+ import { TreeItem } from "~/components/tree/tree";
+ import { getTreePath } from '~/store/project/project-reducer';
+ import { sidePanelActions } from '~/store/side-panel/side-panel-action';
+ import { SidePanel, SidePanelItem } from '~/components/side-panel/side-panel';
+ import { ItemMode, setProjectItem } from "~/store/navigation/navigation-action";
+ import { projectActions } from "~/store/project/project-action";
+ import { collectionCreateActions } from '~/store/collections/creator/collection-creator-action';
+ import { ProjectPanel } from "~/views/project-panel/project-panel";
+ import { DetailsPanel } from '~/views-components/details-panel/details-panel';
+ import { ArvadosTheme } from '~/common/custom-theme';
+ import { CreateProjectDialog } from "~/views-components/create-project-dialog/create-project-dialog";
  
- import { detailsPanelActions, loadDetails } from "../../store/details-panel/details-panel-action";
- import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
- import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
- import { ProjectResource } from '../../models/project';
- import { ResourceKind } from '../../models/resource';
- import { ContextMenu, ContextMenuKind } from "../../views-components/context-menu/context-menu";
+ import { detailsPanelActions, loadDetails } from "~/store/details-panel/details-panel-action";
+ import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
+ import { SidePanelIdentifiers } from '~/store/side-panel/side-panel-reducer';
+ import { ProjectResource } from '~/models/project';
+ import { ResourceKind } from '~/models/resource';
+ import { ContextMenu, ContextMenuKind } from "~/views-components/context-menu/context-menu";
  import { FavoritePanel } from "../favorite-panel/favorite-panel";
- import { CurrentTokenDialog } from '../../views-components/current-token-dialog/current-token-dialog';
- import { Snackbar } from '../../views-components/snackbar/snackbar';
- import { favoritePanelActions } from '../../store/favorite-panel/favorite-panel-action';
- import { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
+ import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
+ import { Snackbar } from '~/views-components/snackbar/snackbar';
+ import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
+ import { CreateCollectionDialog } from '~/views-components/create-collection-dialog/create-collection-dialog';
  import { CollectionPanel } from '../collection-panel/collection-panel';
- import { loadCollection, loadCollectionTags } from '../../store/collection-panel/collection-panel-action';
- import { getCollectionUrl } from '../../models/collection';
- import { UpdateCollectionDialog } from '../../views-components/update-collection-dialog/update-collection-dialog.';
- import { AuthService } from "../../services/auth-service/auth-service";
- import { RenameFileDialog } from '../../views-components/rename-file-dialog/rename-file-dialog';
- import { FileRemoveDialog } from '../../views-components/file-remove-dialog/file-remove-dialog';
- import { MultipleFilesRemoveDialog } from '../../views-components/file-remove-dialog/multiple-files-remove-dialog';
- import { DialogCollectionCreateWithSelectedFile } from '../../views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
+ import { loadCollection, loadCollectionTags } from '~/store/collection-panel/collection-panel-action';
+ import { getCollectionUrl } from '~/models/collection';
+ import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.';
+ import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
+ import { AuthService } from "~/services/auth-service/auth-service";
+ import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
+ import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
+ import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
+ import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
 +import { MoveToProjectDialog } from '../../views-components/move-to-dialog/move-to-dialog';
- import { COLLECTION_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-collection-create';
- import { PROJECT_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-project-create';
+ import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
+ import { PROJECT_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-project-create';
  
  const DRAWER_WITDH = 240;
  const APP_BAR_HEIGHT = 100;
@@@ -87,7 -87,8 +88,8 @@@ const styles: StyleRulesCallback<CssRul
      content: {
          padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3}px`,
          overflowY: "auto",
-         flexGrow: 1
+         flexGrow: 1,
+         position: 'relative'
      },
      toolbar: theme.mixins.toolbar
  });
@@@ -228,6 -229,7 +230,7 @@@ export const Workbench = withStyles(sty
                          <main className={classes.contentWrapper}>
                              <div className={classes.content}>
                                  <Switch>
+                                     <Route path='/' exact render={() => <Redirect to={`/projects/${this.props.authService.getUuid()}`}  />} />
                                      <Route path="/projects/:id" render={this.renderProjectPanel} />
                                      <Route path="/favorites" render={this.renderFavoritePanel} />
                                      <Route path="/collections/:id" render={this.renderCollectionPanel} />
                          <CreateProjectDialog />
                          <CreateCollectionDialog />
                          <RenameFileDialog />
 +                        <MoveToProjectDialog />
                          <DialogCollectionCreateWithSelectedFile />
                          <FileRemoveDialog />
                          <MultipleFilesRemoveDialog />
                          <UpdateCollectionDialog />
+                         <UpdateProjectDialog />
                          <CurrentTokenDialog
                              currentToken={this.props.currentToken}
                              open={this.state.isCurrentTokenDialogOpen}
                  );
              }
  
-             renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel 
+             renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
                  onItemRouteChange={(collectionId) => {
                      this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
                      this.props.dispatch<any>(loadCollectionTags(collectionId));
                      } else {
                          kind = ContextMenuKind.RESOURCE;
                      }
-                     
                      this.openContextMenu(event, {
                          uuid: item.uuid,
                          name: item.name,
                          case ResourceKind.COLLECTION:
                              this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
                              this.props.dispatch(push(getCollectionUrl(item.uuid)));
-                         default: 
+                         default:
                              this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
                              this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
                      }
              toggleSidePanelActive = (itemId: string) => {
                  this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
                  this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
                  const panelItem = this.props.sidePanelItems.find(it => it.id === itemId);
                  if (panelItem && panelItem.activeAction) {
-                     panelItem.activeAction(this.props.dispatch);
+                     panelItem.activeAction(this.props.dispatch, this.props.authService.getUuid());
                  }
              }