refs #master Merge branch 'origin/master' into 13828-trash-view
authorDaniel Kos <daniel.kos@contractors.roche.com>
Thu, 30 Aug 2018 17:59:36 +0000 (19:59 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Thu, 30 Aug 2018 17:59:36 +0000 (19:59 +0200)
# Conflicts:
# package.json
# src/models/container-request.ts
# src/models/resource.ts
# src/services/collection-service/collection-service.ts
# src/services/services.ts
# src/store/navigation/navigation-action.ts
# src/store/project/project-action.ts
# src/store/project/project-reducer.test.ts
# src/store/project/project-reducer.ts
# src/store/side-panel/side-panel-reducer.ts
# src/store/store.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/favorite-panel/favorite-panel-item.ts
# src/views/project-panel/project-panel-item.ts
# src/views/workbench/workbench.tsx

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

1  2 
src/models/resource.ts
src/services/collection-service/collection-service.ts
src/store/context-menu/context-menu-actions.ts
src/store/store.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/data-explorer/renderers.tsx
src/views/collection-panel/collection-panel.tsx
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.tsx

index ab487da070c29c5a30632266477c38959d548012,3290bdfe06f07ed60fa27accd067cff47f0d0b6b..aff1b2417d6fe06a04b9ada5e1bf5fbb31282876
@@@ -14,14 -14,10 +14,16 @@@ export interface Resource 
      etag: string;
  }
  
 +export interface TrashResource extends Resource {
 +    trashAt: string;
 +    deleteAt: string;
 +    isTrashed: boolean;
 +}
 +
  export enum ResourceKind {
      COLLECTION = "arvados#collection",
+     CONTAINER = "arvados#container",
+     CONTAINER_REQUEST = "arvados#containerRequest",
      GROUP = "arvados#group",
      PROCESS = "arvados#containerRequest",
      PROJECT = "arvados#group",
index ad493b5a21483dc04677a52998070c5615a62e95,c0d61bd27b4d5d789a09a0096c16154ddcbbb461..e26da78875b91963a6a1e51703f2a1f9ffbc17cc
@@@ -43,141 -28,44 +28,61 @@@ export class CollectionService extends 
          return Promise.reject();
      }
  
-     async deleteFile(collectionUuid: string, filePath: string) {
-         return this.webdavClient.delete(`/c=${collectionUuid}${filePath}`);
-     }
-     extractFilesData(document: Document) {
-         const collectionUrlPrefix = /\/c=[0-9a-zA-Z\-]*/;
-         return Array
-             .from(document.getElementsByTagName('D:response'))
-             .slice(1) // omit first element which is collection itself
-             .map(element => {
-                 const name = getTagValue(element, 'D:displayname', '');
-                 const size = parseInt(getTagValue(element, 'D:getcontentlength', '0'), 10);
-                 const pathname = getTagValue(element, 'D:href', '');
-                 const nameSuffix = `/${name || ''}`;
-                 const directory = pathname
-                     .replace(collectionUrlPrefix, '')
-                     .replace(nameSuffix, '');
-                 const href = this.webdavClient.defaults.baseURL + pathname + '?api_token=' + this.authService.getApiToken();
-                 const data = {
-                     url: href,
-                     id: `${directory}/${name}`,
-                     name,
-                     path: directory,
-                 };
-                 return getTagValue(element, 'D:resourcetype', '')
-                     ? createCollectionDirectory(data)
-                     : createCollectionFile({ ...data, size });
-             });
+     async deleteFiles(collectionUuid: string, filePaths: string[]) {
+         for (const path of filePaths) {
+             await this.webdavClient.delete(`c=${collectionUuid}${path}`);
+         }
      }
  
-     private readFile(file: File): Promise<ArrayBuffer> {
-         return new Promise<ArrayBuffer>(resolve => {
-             const reader = new FileReader();
-             reader.onload = () => {
-                 resolve(reader.result as ArrayBuffer);
-             };
-             reader.readAsArrayBuffer(file);
-         });
+     async uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress) {
+         // files have to be uploaded sequentially
+         for (let idx = 0; idx < files.length; idx++) {
+             await this.uploadFile(collectionUuid, files[idx], idx, onProgress);
+         }
      }
  
-     private uploadFile(keepServiceHost: string, file: File, fileId: number, onProgress?: UploadProgress): Promise<CollectionFile> {
-         return this.readFile(file).then(content => {
-             return axios.post<string>(keepServiceHost, content, {
-                 headers: {
-                     'Content-Type': 'text/octet-stream'
-                 },
-                 onUploadProgress: (e: ProgressEvent) => {
-                     if (onProgress) {
-                         onProgress(fileId, e.loaded, e.total, Date.now());
-                     }
-                     console.log(`${e.loaded} / ${e.total}`);
-                 }
-             }).then(data => createCollectionFile({
-                 id: data.data,
-                 name: file.name,
-                 size: file.size
-             }));
-         });
+     moveFile(collectionUuid: string, oldPath: string, newPath: string) {
+         return this.webdavClient.move(
+             `c=${collectionUuid}${oldPath}`,
+             `c=${collectionUuid}${encodeURI(newPath)}`
+         );
      }
  
-     private async updateManifest(collectionUuid: string, files: CollectionFile[]): Promise<CollectionResource> {
-         const collection = await this.get(collectionUuid);
-         const manifest: KeepManifestStream[] = parseKeepManifestText(collection.manifestText);
-         files.forEach(f => {
-             let kms = manifest.find(stream => stream.name === f.path);
-             if (!kms) {
-                 kms = {
-                     files: [],
-                     locators: [],
-                     name: f.path
-                 };
-                 manifest.push(kms);
+     private extendFileURL = (file: CollectionDirectory | CollectionFile) => ({
+         ...file,
+         url: this.webdavClient.defaults.baseURL + file.url + '?api_token=' + this.authService.getApiToken()
+     })
+     private async uploadFile(collectionUuid: string, file: File, fileId: number, onProgress: UploadProgress = () => { return; }) {
+         const fileURL = `c=${collectionUuid}/${file.name}`;
+         const fileContent = await fileToArrayBuffer(file);
+         const requestConfig = {
+             headers: {
+                 'Content-Type': 'text/octet-stream'
+             },
+             onUploadProgress: (e: ProgressEvent) => {
+                 onProgress(fileId, e.loaded, e.total, Date.now());
              }
-             kms.locators.push(f.id);
-             const len = kms.files.length;
-             const nextPos = len > 0
-                 ? parseInt(kms.files[len - 1].position, 10) + kms.files[len - 1].size
-                 : 0;
-             kms.files.push({
-                 name: f.name,
-                 position: nextPos.toString(),
-                 size: f.size
-             });
-         });
-         console.log(manifest);
-         const manifestText = stringifyKeepManifest(manifest);
-         const data = { ...collection, manifestText };
-         return this.update(collectionUuid, CommonResourceService.mapKeys(_.snakeCase)(data));
-     }
-     uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress): Promise<CollectionResource | never> {
-         const filters = new FilterBuilder()
-             .addEqual("service_type", "proxy");
-         return this.keepService.list({ filters: filters.getFilters() }).then(data => {
-             if (data.items && data.items.length > 0) {
-                 const serviceHost =
-                     (data.items[0].serviceSslFlag ? "https://" : "http://") +
-                     data.items[0].serviceHost +
-                     ":" + data.items[0].servicePort;
-                 console.log("serviceHost", serviceHost);
+         };
+         return this.webdavClient.put(fileURL, fileContent, requestConfig);
  
-                 const files$ = files.map((f, idx) => this.uploadFile(serviceHost, f, idx, onProgress));
-                 return Promise.all(files$).then(values => {
-                     return this.updateManifest(collectionUuid, values);
-                 });
-             } else {
-                 return Promise.reject("Missing keep service host");
-             }
-         });
      }
  
 +    trash(uuid: string): Promise<CollectionResource> {
 +        return this.serverApi
 +            .post(this.resourceType + `${uuid}/trash`)
 +            .then(CommonResourceService.mapResponseKeys);
 +    }
 +
 +    untrash(uuid: string): Promise<CollectionResource> {
 +        const params = {
 +            ensure_unique_name: true
 +        };
 +        return this.serverApi
 +            .post(this.resourceType + `${uuid}/untrash`, {
 +                params: CommonResourceService.mapKeys(_.snakeCase)(params)
 +            })
 +            .then(CommonResourceService.mapResponseKeys);
 +    }
++    
  }
index 8e5eb1e795791260474d92e0fffe6e596560081d,cf66a53d2361587823219d3d698cfa2572fd07d5..a1ed6c5536bc4b71bb2b6b8e26ed5fc4f16dfcf7
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { default as unionize, ofType, UnionOf } from "unionize";
+ import { unionize, ofType, UnionOf } from '~/common/unionize';
  import { ContextMenuPosition, ContextMenuResource } from "./context-menu-reducer";
 -import { UserResource } from '../../models/user';
+ import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+ import { Dispatch } from 'redux';
+ import { RootState } from '~/store/store';
+ import { getResource } from '../resources/resources';
+ import { ProjectResource } from '~/models/project';
++import { UserResource } from '~/models/user';
+ import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
+ import { extractUuidKind, ResourceKind } from '~/models/resource';
  
  export const contextMenuActions = unionize({
      OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
      CLOSE_CONTEXT_MENU: ofType<{}>()
- }, {
-         tag: 'type',
-         value: 'payload'
-     });
+ });
  
  export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
 -export const openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; description?: string; kind: ContextMenuKind; }) =>
 -        const userResource = getResource<UserResource>(projectUuid)(getState().resources);
 -        if (userResource) {
++export type ContextMenuResource = {
++    name: string;
++    uuid: string;
++    ownerUuid: string;
++    description?: string;
++    kind: ContextMenuKind;
++    isTrashed?: boolean;
++}
++
++export const openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource) =>
+     (dispatch: Dispatch) => {
+         event.preventDefault();
+         dispatch(
+             contextMenuActions.OPEN_CONTEXT_MENU({
+                 position: { x: event.clientX, y: event.clientY },
+                 resource
+             })
+         );
+     };
+ export const openRootProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
 -                uuid: userResource.uuid,
 -                kind: ContextMenuKind.ROOT_PROJECT
++        const res = getResource<UserResource>(projectUuid)(getState().resources);
++        if (res) {
+             dispatch<any>(openContextMenu(event, {
+                 name: '',
 -        const projectResource = getResource<ProjectResource>(projectUuid)(getState().resources);
 -        if (projectResource) {
++                uuid: res.uuid,
++                ownerUuid: res.uuid,
++                kind: ContextMenuKind.ROOT_PROJECT,
++                isTrashed: false
+             }));
+         }
+     };
+ export const openProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
 -                name: projectResource.name,
 -                uuid: projectResource.uuid,
 -                kind: ContextMenuKind.PROJECT
++        const res = getResource<ProjectResource>(projectUuid)(getState().resources);
++        if (res) {
+             dispatch<any>(openContextMenu(event, {
++                name: res.name,
++                uuid: res.uuid,
++                kind: ContextMenuKind.PROJECT,
++                ownerUuid: res.ownerUuid,
++                isTrashed: res.isTrashed
+             }));
+         }
+     };
+ export const openSidePanelContextMenu = (event: React.MouseEvent<HTMLElement>, id: string) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
+         if (!isSidePanelTreeCategory(id)) {
+             const kind = extractUuidKind(id);
+             if (kind === ResourceKind.USER) {
+                 dispatch<any>(openRootProjectContextMenu(event, id));
+             } else if (kind === ResourceKind.PROJECT) {
+                 dispatch<any>(openProjectContextMenu(event, id));
+             }
+         }
+     };
+ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>) =>
+     (dispatch: Dispatch, getState: () => RootState) => {
+         const resource = {
+             uuid: '',
+             name: '',
+             description: '',
+             kind: ContextMenuKind.PROCESS
+         };
+         dispatch<any>(openContextMenu(event, resource));
+     };
+ export const resourceKindToContextMenuKind = (uuid: string) => {
+     const kind = extractUuidKind(uuid);
+     switch (kind) {
+         case ResourceKind.PROJECT:
+             return ContextMenuKind.PROJECT;
+         case ResourceKind.COLLECTION:
+             return ContextMenuKind.COLLECTION_RESOURCE;
+         case ResourceKind.USER:
+             return ContextMenuKind.ROOT_PROJECT;
+         default:
+             return;
+     }
+ };
Simple merge
index c8fb3cbc927d7c51f1f93fcea2b12ea407e1fd0d,b3fdc3fbab642561d823cddd003a70b9ab288149..f26003653382701cf8b700143bd30db830788c46
@@@ -6,10 -6,10 +6,12 @@@ import { ContextMenuActionSet } from ".
  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 { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
  import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
+ import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
+ import { openCollectionCopyDialog } from "~/store/collections/collection-copy-actions";
 +import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
 +import { toggleCollectionTrashed } from "~/store/collections/collection-trash-actions";
  
  export const collectionActionSet: ContextMenuActionSet = [[
      {
index dbc9e23698b1d3814889d814bf98adb69b94b837,a299b9370ca93f39a437338b486ab639935b323e..a1df8385a4e1ad1a4a369412c5815d31a9cebb83
@@@ -4,12 -4,12 +4,13 @@@
  
  import { ContextMenuActionSet } from "../context-menu-action-set";
  import { ToggleFavoriteAction } from "../actions/favorite-action";
++import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-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 { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
  import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
- import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
- import { toggleCollectionTrashed } from "~/store/collections/collection-trash-actions";
+ import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
+ import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions';
  
  export const collectionResourceActionSet: ContextMenuActionSet = [[
      {
index 9e32700d034e8eeb5d87bb8c4db65006ae279730,348b548bdb4845eef161ce30210bcf5fde7833d0..8a0e2f8108332c5110218b1711d5d4ac36242767
@@@ -60,76 -63,98 +63,96 @@@ type CollectionPanelProps = CollectionP
  
  
  export const CollectionPanel = withStyles(styles)(
-     connect((state: RootState) => ({
-         item: state.collectionPanel.item,
-         tags: state.collectionPanel.tags
-     }))(
+     connect((state: RootState, props: RouteComponentProps<{ id: string }>) => {
+         const collection = getResource(props.match.params.id)(state.resources);
+         return {
+             item: collection,
+             tags: state.collectionPanel.tags
+         };
+     })(
          class extends React.Component<CollectionPanelProps> {
 -
              render() {
-                 const { classes, item, tags, onContextMenu } = this.props;
+                 const { classes, item, tags } = this.props;
                  return <div>
-                         <Card className={classes.card}>
-                             <CardHeader
-                                 avatar={ <CollectionIcon className={classes.iconHeader} /> }
-                                 action={
-                                     <IconButton
-                                         aria-label="More options"
-                                         onClick={event => onContextMenu(event, item)}>
-                                         <MoreOptionsIcon />
-                                     </IconButton>
-                                 }
-                                 title={item && item.name }
-                                 subheader={item && item.description} />
-                             <CardContent>
-                                 <Grid container direction="column">
-                                     <Grid item xs={6}>
-                                     <DetailsAttribute classValue={classes.value}
-                                             label='Collection UUID'
-                                             value={item && item.uuid}>
-                                         <CopyToClipboard text={item && item.uuid}>
-                                             <CopyIcon className={classes.copyIcon} />
-                                         </CopyToClipboard>
+                     <Card className={classes.card}>
+                         <CardHeader
+                             avatar={<CollectionIcon className={classes.iconHeader} />}
+                             action={
+                                 <IconButton
+                                     aria-label="More options"
+                                     onClick={this.handleContextMenu}>
+                                     <MoreOptionsIcon />
+                                 </IconButton>
+                             }
+                             title={item && item.name}
+                             subheader={item && item.description} />
+                         <CardContent>
+                             <Grid container direction="column">
+                                 <Grid item xs={6}>
+                                     <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                                         label='Collection UUID'
+                                         value={item && item.uuid}>
+                                         <Tooltip title="Copy uuid">
+                                             <CopyToClipboard text={item && item.uuid} onCopy={() => this.onCopy()}>
+                                                 <CopyIcon className={classes.copyIcon} />
+                                             </CopyToClipboard>
+                                         </Tooltip>
                                      </DetailsAttribute>
-                                     <DetailsAttribute label='Number of files' value='14' />
-                                     <DetailsAttribute label='Content size' value='54 MB' />
-                                     <DetailsAttribute classValue={classes.value} label='Owner' value={item && item.ownerUuid} />
-                                     </Grid>
+                                     <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                                         label='Number of files' value='14' />
+                                     <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                                         label='Content size' value='54 MB' />
+                                     <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                                         label='Owner' value={item && item.ownerUuid} />
                                  </Grid>
-                             </CardContent>
-                         </Card>
+                             </Grid>
+                         </CardContent>
+                     </Card>
  
-                         <Card className={classes.card}>
-                             <CardHeader title="Properties" />
-                             <CardContent>
-                                 <Grid container direction="column">
-                                     <Grid item xs={12}><CollectionTagForm /></Grid>
-                                     <Grid item xs={12}>
-                                         {
-                                             tags.map(tag => {
-                                                 return <Chip key={tag.etag} className={classes.tag}
-                                                     onDelete={this.handleDelete(tag.uuid)}
-                                                     label={renderTagLabel(tag)}  />;
-                                             })
-                                         }
-                                     </Grid>
+                     <Card className={classes.card}>
+                         <CardHeader title="Properties" />
+                         <CardContent>
+                             <Grid container direction="column">
+                                 <Grid item xs={12}><CollectionTagForm /></Grid>
+                                 <Grid item xs={12}>
+                                     {
+                                         tags.map(tag => {
+                                             return <Chip key={tag.etag} className={classes.tag}
+                                                 onDelete={this.handleDelete(tag.uuid)}
+                                                 label={renderTagLabel(tag)} />;
+                                         })
+                                     }
                                  </Grid>
-                             </CardContent>
-                         </Card>
-                         <div className={classes.card}>
-                             <CollectionPanelFiles/>
-                         </div>
-                     </div>;
+                             </Grid>
+                         </CardContent>
+                     </Card>
+                     <div className={classes.card}>
+                         <CollectionPanelFiles />
+                     </div>
+                 </div>;
+             }
+             handleContextMenu = (event: React.MouseEvent<any>) => {
+                 const { uuid, name, description } = this.props.item;
+                 const resource = {
+                     uuid,
+                     name,
+                     description,
+                     kind: ContextMenuKind.COLLECTION
+                 };
+                 this.props.dispatch<any>(openContextMenu(event, resource));
              }
  
              handleDelete = (uuid: string) => () => {
                  this.props.dispatch<any>(deleteCollectionTag(uuid));
              }
  
-             componentWillReceiveProps({ match, item, onItemRouteChange }: CollectionPanelProps) {
-                 if (!item || match.params.id !== item.uuid) {
-                     onItemRouteChange(match.params.id);
-                 }
+             onCopy = () => {
+                 this.props.dispatch(snackbarActions.OPEN_SNACKBAR({
+                     message: "Uuid has been copied",
+                     hideDuration: 2000
+                 }));
              }
 -
          }
      )
  );
index 49f1f4ab3a593bc96fb2e10bdddeca294dc49c03,9fbae5ced889902d4771af6dbc7bb821e6f15360..62b037e3a56da989baa16aa1c91679c2d47a7a77
@@@ -9,9 -8,8 +8,8 @@@ import { DataExplorer } from "~/views-c
  import { DispatchProp, connect } from 'react-redux';
  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 { ProcessState } from '~/models/process';
  import { SortDirection } from '~/components/data-table/data-column';
  import { ResourceKind } from '~/models/resource';
  import { resourceLabel } from '~/common/labels';
@@@ -42,10 -45,10 +45,10 @@@ export enum FavoritePanelColumnNames 
  }
  
  export interface FavoritePanelFilter extends DataTableFilterItem {
 -    type: ResourceKind | ContainerRequestState;
 +    type: ResourceKind | ProcessState;
  }
  
- export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
+ export const favoritePanelColumns: DataColumns<string, FavoritePanelFilter> = [
      {
          name: FavoritePanelColumnNames.NAME,
          selected: true,
          sortDirection: SortDirection.NONE,
          filters: [
              {
 -                name: ContainerRequestState.COMMITTED,
 +                name: ProcessState.COMMITTED,
                  selected: true,
 -                type: ContainerRequestState.COMMITTED
 +                type: ProcessState.COMMITTED
              },
              {
 -                name: ContainerRequestState.FINAL,
 +                name: ProcessState.FINAL,
                  selected: true,
 -                type: ContainerRequestState.FINAL
 +                type: ProcessState.FINAL
              },
              {
 -                name: ContainerRequestState.UNCOMMITTED,
 +                name: ProcessState.UNCOMMITTED,
                  selected: true,
 -                type: ContainerRequestState.UNCOMMITTED
 +                type: ProcessState.UNCOMMITTED
              }
          ],
-         render: renderStatus,
+         render: uuid => <ProcessStatus uuid={uuid} />,
          width: "75px"
      },
      {
index f63584b7866d40b9ce766f9eb01bb2530e55ae98,06946430e71909d711f1bdc6c32b1ac4c0f80021..37a6d202214a6fb1b80114ad4805fac08aace93f
@@@ -47,10 -57,10 +57,10 @@@ export enum ProjectPanelColumnNames 
  }
  
  export interface ProjectPanelFilter extends DataTableFilterItem {
 -    type: ResourceKind | ContainerRequestState;
 +    type: ResourceKind | ProcessState;
  }
  
- export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
+ export const projectPanelColumns: DataColumns<string, ProjectPanelFilter> = [
      {
          name: ProjectPanelColumnNames.NAME,
          selected: true,
          sortDirection: SortDirection.NONE,
          filters: [
              {
 -                name: ContainerRequestState.COMMITTED,
 +                name: ProcessState.COMMITTED,
                  selected: true,
 -                type: ContainerRequestState.COMMITTED
 +                type: ProcessState.COMMITTED
              },
              {
 -                name: ContainerRequestState.FINAL,
 +                name: ProcessState.FINAL,
                  selected: true,
 -                type: ContainerRequestState.FINAL
 +                type: ProcessState.FINAL
              },
              {
 -                name: ContainerRequestState.UNCOMMITTED,
 +                name: ProcessState.UNCOMMITTED,
                  selected: true,
 -                type: ContainerRequestState.UNCOMMITTED
 +                type: ProcessState.UNCOMMITTED
              }
          ],
-         render: renderStatus,
+         render: uuid => <ProcessStatus uuid={uuid} />,
          width: "75px"
      },
      {
index 8028f2c30ffc10cd8c883e2676aedd5a9175531f,ef5fe215290e4e33dfac8988840d0cb4740ed83d..ea3a278bf9090cbe73b29a2a0b64f4b357d6e399
@@@ -46,16 -24,24 +24,27 @@@ import { AuthService } from "~/services
  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 { Routes } from '~/routes/routes';
+ import { SidePanel } from '~/views-components/side-panel/side-panel';
+ import { ProcessPanel } from '~/views/process-panel/process-panel';
+ import { Breadcrumbs } from '~/views-components/breadcrumbs/breadcrumbs';
+ import { CreateProjectDialog } from '~/views-components/dialog-forms/create-project-dialog';
+ import { CreateCollectionDialog } from '~/views-components/dialog-forms/create-collection-dialog';
+ import { CopyCollectionDialog } from '~/views-components/dialog-forms/copy-collection-dialog';
+ import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-collection-dialog';
+ import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog';
+ import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-dialog';
+ import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog';
+ import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
+ import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
 +import { TrashPanel } from "~/views/trash-panel/trash-panel";
 +import { trashPanelActions } from "~/store/trash-panel/trash-panel-action";
 +
- const DRAWER_WITDH = 240;
  const APP_BAR_HEIGHT = 100;
  
- type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar';
+ type CssRules = 'root' | 'appBar' | 'content' | 'contentWrapper';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      root: {
@@@ -233,11 -163,10 +166,11 @@@ 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={Routes.PROJECTS} component={ProjectPanel} />
+                                     <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
+                                     <Route path={Routes.FAVORITES} component={FavoritePanel} />
+                                     <Route path={Routes.PROCESSES} component={ProcessPanel} />
 +                                    <Route path="/trash" render={this.renderTrashPanel} />
-                                     <Route path="/collections/:id" render={this.renderCollectionPanel} />
                                  </Switch>
                              </div>
                              {user && <DetailsPanel />}