refs #master Merge branch 'origin/master' into 14007-ts-paths
authorDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 13 Aug 2018 17:48:41 +0000 (19:48 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 13 Aug 2018 17:48:41 +0000 (19:48 +0200)
# Conflicts:
# src/components/data-explorer/data-explorer.test.tsx
# src/components/data-explorer/data-explorer.tsx
# src/store/navigation/navigation-action.ts
# src/views-components/context-menu/action-sets/root-project-action-set.ts
# src/views-components/details-panel/empty-details.tsx
# src/views/favorite-panel/favorite-panel.tsx
# src/views/project-panel/project-panel.tsx
# src/views/workbench/workbench.test.tsx
# src/views/workbench/workbench.tsx

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

12 files changed:
1  2 
src/components/data-explorer/data-explorer.tsx
src/components/details-attribute/details-attribute.tsx
src/components/side-panel/side-panel.tsx
src/store/navigation/navigation-action.ts
src/store/project/project-action.ts
src/store/side-panel/side-panel-reducer.ts
src/views-components/context-menu/action-sets/root-project-action-set.ts
src/views-components/details-panel/empty-details.tsx
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.test.tsx
src/views/workbench/workbench.tsx

index eaa3464b57bc77442a05e39d7eb8444f508d6ca2,7acc1a835ac2997eddff27220f461318bcfc7f19..2811bd4d1f7e7fada857e6677e7f50c1c576fba6
@@@ -10,9 -10,11 +10,11 @@@ import { DataTable, DataColumns } from 
  import { DataColumn } from "../data-table/data-column";
  import { DataTableFilterItem } from '../data-table-filters/data-table-filters';
  import { SearchInput } from '../search-input/search-input';
 -import { ArvadosTheme } from "../../common/custom-theme";
 +import { ArvadosTheme } from "~/common/custom-theme";
+ import { DefaultView } from '../default-view/default-view';
+ import { IconType } from '../icon/icon';
  
- type CssRules = "searchBox" | "toolbar";
+ type CssRules = 'searchBox' | "toolbar" | 'defaultRoot' | 'defaultMessage' | 'defaultIcon';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      searchBox: {
      },
      toolbar: {
          paddingTop: theme.spacing.unit * 2
+     },
+     defaultRoot: {
+         position: 'absolute',
+         width: '80%',
+         left: '50%',
+         top: '50%',
+         transform: 'translate(-50%, -50%)'
+     },
+     defaultMessage: {
+         fontSize: '1.75rem',
+     },
+     defaultIcon: {
+         fontSize: '6rem'
      }
  });
  
@@@ -31,6 -46,11 +46,11 @@@ interface DataExplorerDataProps<T> 
      rowsPerPage: number;
      rowsPerPageOptions: number[];
      page: number;
+     defaultIcon: IconType;
+     defaultMessages: string[];
+ }
+ interface DataExplorerActionProps<T> {
      onSearch: (value: string) => void;
      onRowClick: (item: T) => void;
      onRowDoubleClick: (item: T) => void;
      extractKey?: (item: T) => React.Key;
  }
  
- type DataExplorerProps<T> = DataExplorerDataProps<T> & WithStyles<CssRules>;
+ type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
  
  export const DataExplorer = withStyles(styles)(
      class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
          render() {
-             return <Paper>
-                 <Toolbar className={this.props.classes.toolbar}>
-                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
-                         <div className={this.props.classes.searchBox}>
-                             <SearchInput
-                                 value={this.props.searchValue}
-                                 onSearch={this.props.onSearch}/>
-                         </div>
-                         <ColumnSelector
-                             columns={this.props.columns}
-                             onColumnToggle={this.props.onColumnToggle}/>
-                     </Grid>
-                 </Toolbar>
-                 <DataTable
-                     columns={[...this.props.columns, this.contextMenuColumn]}
-                     items={this.props.items}
-                     onRowClick={(_, item: T) => this.props.onRowClick(item)}
-                     onContextMenu={this.props.onContextMenu}
-                     onRowDoubleClick={(_, item: T) => this.props.onRowDoubleClick(item)}
-                     onFiltersChange={this.props.onFiltersChange}
-                     onSortToggle={this.props.onSortToggle}
-                     extractKey={this.props.extractKey}/>
-                 <Toolbar>
-                     {this.props.items.length > 0 &&
-                     <Grid container justify="flex-end">
-                         <TablePagination
-                             count={this.props.itemsAvailable}
-                             rowsPerPage={this.props.rowsPerPage}
-                             rowsPerPageOptions={this.props.rowsPerPageOptions}
-                             page={this.props.page}
-                             onChangePage={this.changePage}
-                             onChangeRowsPerPage={this.changeRowsPerPage}
-                             component="div"
-                         />
-                     </Grid>}
-                 </Toolbar>
-             </Paper>;
+             const { 
+                 columns, onContextMenu, onFiltersChange, onSortToggle, extractKey, 
+                 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch, 
+                 items, itemsAvailable, onRowClick, onRowDoubleClick, defaultIcon, defaultMessages, classes 
+             } = this.props;
+             return <div>
+                 { items.length > 0 ? (
+                     <Paper>
+                         <Toolbar className={classes.toolbar}>
+                             <Grid container justify="space-between" wrap="nowrap" alignItems="center">
+                                 <div className={classes.searchBox}>
+                                     <SearchInput
+                                         value={searchValue}
+                                         onSearch={onSearch}/>
+                                 </div>
+                                 <ColumnSelector
+                                     columns={columns}
+                                     onColumnToggle={onColumnToggle}/>
+                             </Grid>
+                         </Toolbar>
+                         <DataTable
+                             columns={[...columns, this.contextMenuColumn]}
+                             items={items}
+                             onRowClick={(_, item: T) => onRowClick(item)}
+                             onContextMenu={onContextMenu}
+                             onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
+                             onFiltersChange={onFiltersChange}
+                             onSortToggle={onSortToggle}
+                             extractKey={extractKey}/>
+                         <Toolbar>
+                             <Grid container justify="flex-end">
+                                 <TablePagination
+                                     count={itemsAvailable}
+                                     rowsPerPage={rowsPerPage}
+                                     rowsPerPageOptions={rowsPerPageOptions}
+                                     page={this.props.page}
+                                     onChangePage={this.changePage}
+                                     onChangeRowsPerPage={this.changeRowsPerPage}
+                                     component="div" />
+                             </Grid>
+                         </Toolbar>
+                     </Paper>
+                 ) : (
+                     <DefaultView 
+                         classRoot={classes.defaultRoot}
+                         icon={defaultIcon}
+                         classIcon={classes.defaultIcon}
+                         messages={defaultMessages}
+                         classMessage={classes.defaultMessage} />
+                 )}
+             </div>;
          }
  
          changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
index d3a83918ec8681da74aa6ea3b49a5e9a1b305cf8,9e58a3e4a0a12f1d8e3ac5eac54f7fb169729129..3888b04b67d596ea84f3e11cbfeba30999adbc03
@@@ -5,7 -5,7 +5,7 @@@
  import * as React from 'react';
  import Typography from '@material-ui/core/Typography';
  import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 -import { ArvadosTheme } from '../../common/custom-theme';
 +import { ArvadosTheme } from '~/common/custom-theme';
  import * as classnames from "classnames";
  
  type CssRules = 'attribute' | 'label' | 'value' | 'link';
@@@ -45,14 -45,15 +45,15 @@@ interface DetailsAttributeDataProps 
  
  type DetailsAttributeProps = DetailsAttributeDataProps & WithStyles<CssRules>;
  
- export const DetailsAttribute = withStyles(styles)(({ label, link, value, children, classes, classLabel, classValue }: DetailsAttributeProps) =>
-     <Typography component="div" className={classes.attribute}>
-         <Typography component="span" className={classnames([classes.label, classLabel])}>{label}</Typography>
-         { link
-             ? <a href={link} className={classes.link} target='_blank'>{value}</a>
-             : <Typography component="span" className={classnames([classes.value, classValue])}>
-                 {value}
-                 {children}
-             </Typography> }
-     </Typography>
+ export const DetailsAttribute = withStyles(styles)(
+     ({ label, link, value, children, classes, classLabel, classValue }: DetailsAttributeProps) =>
+         <Typography component="div" className={classes.attribute}>
+             <Typography component="span" className={classnames([classes.label, classLabel])}>{label}</Typography>
+             { link
+                 ? <a href={link} className={classes.link} target='_blank'>{value}</a>
+                 : <Typography component="span" className={classnames([classes.value, classValue])}>
+                     {value}
+                     {children}
+                 </Typography> }
+         </Typography>
  );
index 0a62bf2d40216b5043187576f052f85142fe68e6,c7649d4d8082edf81ac8d3a4235d035c72812620..206cb6322b84a1d181d451f76d8886328837a969
@@@ -5,7 -5,7 +5,7 @@@
  import * as React from 'react';
  import { ReactElement } from 'react';
  import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 -import { ArvadosTheme } from '../../common/custom-theme';
 +import { ArvadosTheme } from '~/common/custom-theme';
  import { List, ListItem, ListItemIcon, Collapse } from "@material-ui/core";
  import { SidePanelRightArrowIcon, IconType } from '../icon/icon';
  import * as classnames from "classnames";
@@@ -59,7 -59,7 +59,7 @@@ export interface SidePanelItem 
      open?: boolean;
      margin?: boolean;
      openAble?: boolean;
-     activeAction?: (dispatch: Dispatch) => void;
+     activeAction?: (dispatch: Dispatch, uuid?: string) => void;
  }
  
  interface SidePanelDataProps {
index e50bce0a7e9b066761289bf8911e698f5b41f875,dc680215c1cf680db7264e54c73292f02aae7aff..79d24471491fcd5e2ced64a8bdcd8edb6b38bd87
@@@ -5,18 -5,18 +5,18 @@@
  import { Dispatch } from "redux";
  import { projectActions, getProjectList } from "../project/project-action";
  import { push } from "react-router-redux";
 -import { TreeItemStatus } from "../../components/tree/tree";
 +import { TreeItemStatus } from "~/components/tree/tree";
  import { findTreeItem } from "../project/project-reducer";
  import { RootState } from "../store";
 -import { Resource, ResourceKind } from "../../models/resource";
 +import { Resource, ResourceKind } from "~/models/resource";
  import { projectPanelActions } from "../project-panel/project-panel-action";
 -import { getCollectionUrl } from "../../models/collection";
 -import { getProjectUrl, ProjectResource } from "../../models/project";
 -import { ProjectService } from "../../services/project-service/project-service";
 -import { ServiceRepository } from "../../services/services";
 +import { getCollectionUrl } from "~/models/collection";
 +import { getProjectUrl, ProjectResource } from "~/models/project";
 +import { ProjectService } from "~/services/project-service/project-service";
 +import { ServiceRepository } from "~/services/services";
  import { sidePanelActions } from "../side-panel/side-panel-action";
  import { SidePanelIdentifiers } from "../side-panel/side-panel-reducer";
 -import { getUuidObjectType, ObjectTypes } from "../../models/object-types";
 +import { getUuidObjectType, ObjectTypes } from "~/models/object-types";
  
  export const getResourceUrl = <T extends Resource>(resource: T): string => {
      switch (resource.kind) {
@@@ -33,12 -33,11 +33,11 @@@ export enum ItemMode 
  }
  
  export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
-     (dispatch: Dispatch, getState: () => RootState) => {
+     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
          const { projects, router } = getState();
          const treeItem = findTreeItem(projects.items, itemId);
  
          if (treeItem) {
              const resourceUrl = getResourceUrl(treeItem.data);
  
              if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
                      dispatch(projectPanelActions.RESET_PAGINATION());
                      dispatch(projectPanelActions.REQUEST_ITEMS());
                  }));
 -
+         } else {
+             const uuid = services.authService.getUuid();
+             if (itemId === uuid) {
+                 dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(uuid));
+                 dispatch(projectPanelActions.RESET_PAGINATION());
+                 dispatch(projectPanelActions.REQUEST_ITEMS());
+             }
          }
      };
  
index 20b255ca812e1c1d864378cc08c45349640eb640,9777dc73641101970f9c99f3b6d8fa997bd4901c..bef50d1197f44939f9e547c577bb0e2f797e6647
@@@ -3,12 -3,12 +3,12 @@@
  // SPDX-License-Identifier: AGPL-3.0
  import { default as unionize, ofType, UnionOf } from "unionize";
  
 -import { ProjectResource } from "../../models/project";
 +import { ProjectResource } from "~/models/project";
  import { Dispatch } from "redux";
 -import { FilterBuilder } from "../../common/api/filter-builder";
 +import { FilterBuilder } from "~/common/api/filter-builder";
  import { RootState } from "../store";
  import { checkPresenceInFavorites } from "../favorites/favorites-actions";
 -import { ServiceRepository } from "../../services/services";
 +import { ServiceRepository } from "~/services/services";
  
  export const projectActions = unionize({
      OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
      value: 'payload'
  });
  
- export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-     dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
-     return services.projectService.list({
-         filters: FilterBuilder
-             .create()
-             .addEqual("ownerUuid", parentUuid)
-     }).then(({ items: projects }) => {
-         dispatch(projectActions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
-         dispatch<any>(checkPresenceInFavorites(projects.map(project => project.uuid)));
-         return projects;
-     });
- };
+ export const getProjectList = (parentUuid: string = '') => 
+     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
+         return services.projectService.list({
+             filters: FilterBuilder
+                 .create()
+                 .addEqual("ownerUuid", parentUuid)
+         }).then(({ items: projects }) => {
+             dispatch(projectActions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
+             dispatch<any>(checkPresenceInFavorites(projects.map(project => project.uuid)));
+             return projects;
+         });
+     };
  
  export const createProject = (project: Partial<ProjectResource>) =>
      (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
index fe0626340d3740695539dd669e3c3ff47debc9bb,7a27e5adbfd4ba9f845fd9f30b71de558afc44b5..8c73a8f596b6c9a96d0359dd804e7838ce307827
@@@ -4,11 -4,14 +4,14 @@@
  
  import * as _ from "lodash";
  import { sidePanelActions, SidePanelAction } from './side-panel-action';
 -import { SidePanelItem } from '../../components/side-panel/side-panel';
 -import { ProjectsIcon, ShareMeIcon, WorkflowIcon, RecentIcon, FavoriteIcon, TrashIcon } from "../../components/icon/icon";
 +import { SidePanelItem } from '~/components/side-panel/side-panel';
 +import { ProjectsIcon, ShareMeIcon, WorkflowIcon, RecentIcon, FavoriteIcon, TrashIcon } from "~/components/icon/icon";
  import { Dispatch } from "redux";
  import { push } from "react-router-redux";
  import { favoritePanelActions } from "../favorite-panel/favorite-panel-action";
+ import { projectPanelActions } from "../project-panel/project-panel-action";
+ import { projectActions } from "../project/project-action";
+ import { getProjectUrl } from "../../models/project";
  
  export type SidePanelState = SidePanelItem[];
  
@@@ -56,7 -59,13 +59,13 @@@ export const sidePanelData = 
          open: false,
          active: false,
          margin: true,
-         openAble: true
+         openAble: true,
+         activeAction: (dispatch: Dispatch, uuid: string) => {
+             dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(uuid));
+             dispatch(push(getProjectUrl(uuid)));
+             dispatch(projectPanelActions.RESET_PAGINATION());
+             dispatch(projectPanelActions.REQUEST_ITEMS()); 
+         }
      },
      {
          id: SidePanelIdentifiers.SHARED_WITH_ME,
index 556ba5dbfff32d294aa574020c09c091e44fed67,c88b1b44bd699097da3eb6b08b4fb0d82c092280..fb380fc4f006201d324d36fadbea8724b3d16111
@@@ -5,15 -5,27 +5,28 @@@
  import { reset } from "redux-form";
  
  import { ContextMenuActionSet } from "../context-menu-action-set";
 -import { projectActions } from "../../../store/project/project-action";
 +import { projectActions } from "~/store/project/project-action";
 +import { NewProjectIcon } from "~/components/icon/icon";
+ import { collectionCreateActions } from "../../../store/collections/creator/collection-creator-action";
  import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
+ import { COLLECTION_CREATE_DIALOG } from "../../dialog-create/dialog-collection-create";
+ import { NewProjectIcon, CollectionIcon } from "../../../components/icon/icon";
  
- export const rootProjectActionSet: ContextMenuActionSet =  [[{
-     icon: NewProjectIcon,
-     name: "New project",
-     execute: (dispatch, resource) => {
-         dispatch(reset(PROJECT_CREATE_DIALOG));
-         dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+ export const rootProjectActionSet: ContextMenuActionSet =  [[
+     {
+         icon: NewProjectIcon,
+         name: "New project",
+         execute: (dispatch, resource) => {
+             dispatch(reset(PROJECT_CREATE_DIALOG));
+             dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+         }
+     }, 
+     {
+         icon: CollectionIcon,
+         name: "New Collection",
+         execute: (dispatch, resource) => {
+             dispatch(reset(COLLECTION_CREATE_DIALOG));
+             dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: resource.uuid }));
+         }
      }
}]];
+ ]];
index ccaa561f9fa19310a25e5c6c775d398e0d633277,47cb4030eddad0f8e11c901d05b90e65072d686e..76778d72b45583b3c374b1bf3416e3528eef5e87
@@@ -3,44 -3,10 +3,10 @@@
  // SPDX-License-Identifier: AGPL-3.0
  
  import * as React from 'react';
- import { DefaultIcon, IconType, ProjectsIcon } from '~/components/icon/icon';
 -import { DefaultIcon, ProjectsIcon } from '../../components/icon/icon';
 -import { EmptyResource } from '../../models/empty';
++import { DefaultIcon, ProjectsIcon } from '~/components/icon/icon';
 +import { EmptyResource } from '~/models/empty';
  import { DetailsData } from "./details-data";
- import Typography from "@material-ui/core/Typography";
- import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core/styles";
- import { ArvadosTheme } from "~/common/custom-theme";
- import Icon from "@material-ui/core/Icon/Icon";
- type CssRules = 'container' | 'icon';
- const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-     container: {
-         textAlign: 'center'
-     },
-     icon: {
-         color: theme.palette.grey["500"],
-         fontSize: '72px'
-     }
- });
- export interface EmptyStateDataProps {
-     message: string;
-     icon: IconType;
-     details?: string;
-     children?: React.ReactNode;
- }
- type EmptyStateProps = EmptyStateDataProps & WithStyles<CssRules>;
- const EmptyState = withStyles(styles)(
-     ({ classes, details, message, children, icon: Icon }: EmptyStateProps) =>
-         <Typography className={classes.container} component="div">
-             <Icon className={classes.icon}/>
-             <Typography variant="body1" gutterBottom>{message}</Typography>
-             {details && <Typography gutterBottom>{details}</Typography>}
-             {children && <Typography gutterBottom>{children}</Typography>}
-         </Typography>
- );
 -import { DefaultView } from '../../components/default-view/default-view';
++import { DefaultView } from '~/components/default-view/default-view';
  
  export class EmptyDetails extends DetailsData<EmptyResource> {
      getIcon(className?: string) {
@@@ -48,6 -14,6 +14,6 @@@
      }
  
      getDetails() {
-        return <EmptyState icon={DefaultIcon} message='Select a file or folder to view its details.'/>;
+         return <DefaultView icon={DefaultIcon} messages={['Select a file or folder to view its details.']} />;
      }
  }
index 899ef3a06b276399ac8ece773647adff7d7031e1,a48395d4b2d03f83557f4ce46631580c96930689..618f0aba426b1ec6d44c00189437414ef5a2e901
@@@ -5,19 -5,20 +5,20 @@@
  import * as React from 'react';
  import { FavoritePanelItem } from './favorite-panel-item';
  import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 -import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
 +import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
  import { DispatchProp, connect } from 'react-redux';
 -import { DataColumns } from '../../components/data-table/data-table';
 +import { DataColumns } from '~/components/data-table/data-table';
  import { RouteComponentProps } from 'react-router';
 -import { RootState } from '../../store/store';
 -import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
 -import { ContainerRequestState } from '../../models/container-request';
 -import { SortDirection } from '../../components/data-table/data-column';
 -import { ResourceKind } from '../../models/resource';
 -import { resourceLabel } from '../../common/labels';
 -import { ArvadosTheme } from '../../common/custom-theme';
 -import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
 -import { FAVORITE_PANEL_ID } from "../../store/favorite-panel/favorite-panel-action";
 -import { FavoriteIcon } from '../../components/icon/icon';
 +import { RootState } from '~/store/store';
 +import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 +import { ContainerRequestState } from '~/models/container-request';
 +import { SortDirection } from '~/components/data-table/data-column';
 +import { ResourceKind } from '~/models/resource';
 +import { resourceLabel } from '~/common/labels';
 +import { ArvadosTheme } from '~/common/custom-theme';
 +import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
 +import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
++import { FavoriteIcon } from '~/components/icon/icon';
  
  type CssRules = "toolbar" | "button";
  
@@@ -150,7 -151,9 +151,9 @@@ export const FavoritePanel = withStyles
                      onRowClick={this.props.onItemClick}
                      onRowDoubleClick={this.props.onItemDoubleClick}
                      onContextMenu={this.props.onContextMenu}
-                     extractKey={(item: FavoritePanelItem) => item.uuid} />
+                     extractKey={(item: FavoritePanelItem) => item.uuid} 
+                     defaultIcon={FavoriteIcon}
+                     defaultMessages={['Your favorites list is empty.']}/>
                  ;
              }
  
index fccd93efd39045b20111b4d9ee0748f2297e6fb0,cf4aca5b6aacf074ebb7490a298d75f689a4903c..e9179a118ddc759de11d4fccba3109c6309fc600
@@@ -5,23 -5,29 +5,29 @@@
  import * as React from 'react';
  import { ProjectPanelItem } from './project-panel-item';
  import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 -import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
 +import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
  import { DispatchProp, connect } from 'react-redux';
 -import { DataColumns } from '../../components/data-table/data-table';
 +import { DataColumns } from '~/components/data-table/data-table';
  import { RouteComponentProps } from 'react-router';
 -import { RootState } from '../../store/store';
 -import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
 -import { ContainerRequestState } from '../../models/container-request';
 -import { SortDirection } from '../../components/data-table/data-column';
 -import { ResourceKind } from '../../models/resource';
 -import { resourceLabel } from '../../common/labels';
 -import { ArvadosTheme } from '../../common/custom-theme';
 -import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
 -import { restoreBranch } from '../../store/navigation/navigation-action';
 -import { ProjectIcon } from '../../components/icon/icon';
 +import { RootState } from '~/store/store';
 +import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 +import { ContainerRequestState } from '~/models/container-request';
 +import { SortDirection } from '~/components/data-table/data-column';
 +import { ResourceKind } from '~/models/resource';
 +import { resourceLabel } from '~/common/labels';
 +import { ArvadosTheme } from '~/common/custom-theme';
 +import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
 +import { restoreBranch } from '~/store/navigation/navigation-action';
++import { ProjectIcon } from '~/components/icon/icon';
  
- type CssRules = "toolbar" | "button";
+ type CssRules = 'root' | "toolbar" | "button";
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+     root: {
+         position: 'relative',
+         width: '100%',
+         height: '100%'
+     },
      toolbar: {
          paddingBottom: theme.spacing.unit * 3,
          textAlign: "right"
@@@ -148,7 -154,7 +154,7 @@@ export const ProjectPanel = withStyles(
          class extends React.Component<ProjectPanelProps> {
              render() {
                  const { classes } = this.props;
-                 return <div>
+                 return <div className={classes.root}>
                      <div className={classes.toolbar}>
                          <Button color="primary" onClick={this.handleNewCollectionClick} variant="raised" className={classes.button}>
                              Create a collection
                          onRowClick={this.props.onItemClick}
                          onRowDoubleClick={this.props.onItemDoubleClick}
                          onContextMenu={this.props.onContextMenu}
-                         extractKey={(item: ProjectPanelItem) => item.uuid} />
+                         extractKey={(item: ProjectPanelItem) => item.uuid}
+                         defaultIcon={ProjectIcon}
+                         defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
                  </div>;
              }
  
index 3587283e1159af31e2608dedca0374173c1219ce,588b6d9b8ca7f98db074e7fffb359d7630b633b2..d03e1172fb4fc963c99fcaaeb9ea014c9dda6751
@@@ -4,26 -4,30 +4,27 @@@
  
  import * as React from 'react';
  import * as ReactDOM from 'react-dom';
 -import { Workbench } from '../../views/workbench/workbench';
 +import { Workbench } from './workbench';
  import { Provider } from "react-redux";
 -import { configureStore } from "../../store/store";
 +import { configureStore } from "~/store/store";
  import createBrowserHistory from "history/createBrowserHistory";
  import { ConnectedRouter } from "react-router-redux";
  import { MuiThemeProvider } from '@material-ui/core/styles';
 -import { CustomTheme } from '../../common/custom-theme';
 -import { createServices } from "../../services/services";
 -import { AuthService } from "../../services/auth-service/auth-service";
 -import Axios from "axios";
 +import { CustomTheme } from '~/common/custom-theme';
 +import { createServices } from "~/services/services";
  
  const history = createBrowserHistory();
 -const authService = new AuthService(Axios.create(), '/arvados/v1');
 -
 -authService.getUuid = jest.fn().mockReturnValueOnce('test');
  
  it('renders without crashing', () => {
      const div = document.createElement('div');
 -    const store = configureStore(createBrowserHistory(), createServices("/arvados/v1"));
 +    const services = createServices("/arvados/v1");
++      services.authService.getUuid = jest.fn().mockReturnValueOnce('test');
 +    const store = configureStore(createBrowserHistory(), services);
      ReactDOM.render(
          <MuiThemeProvider theme={CustomTheme}>
              <Provider store={store}>
                  <ConnectedRouter history={history}>
 -                    <Workbench authService={authService} />
 +                    <Workbench authService={services.authService}/>
                  </ConnectedRouter>
              </Provider>
          </MuiThemeProvider>,
index a7f14aad4ed1232e7148cab5290d5d2f3c71093d,356a6706c65014d4e61a634052059800d13c9778..a0be3729964eb87a33b33d802cedbc265a4376d1
@@@ -6,49 -6,49 +6,49 @@@ 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, RouteComponentProps, Switch } from "react-router";
 -import { Route, Switch, RouteComponentProps, 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 { 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 { COLLECTION_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-collection-create';
 -import { PROJECT_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-project-create';
 +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 { 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;
@@@ -86,7 -86,8 +86,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
  });
@@@ -227,6 -228,7 +228,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} />
                  );
              }
  
 -            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());
                  }
              }