Merge branch '14119-restore-collection-files-upload'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 28 Aug 2018 12:46:26 +0000 (14:46 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 28 Aug 2018 12:46:26 +0000 (14:46 +0200)
refs #14119

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

25 files changed:
src/components/file-upload-dialog/file-upload-dialog.tsx
src/components/file-upload/file-upload.tsx
src/store/collection-panel/collection-panel-action.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collections/collection-create-actions.ts
src/store/collections/collection-partial-copy-actions.ts [new file with mode: 0644]
src/store/collections/collection-upload-actions.ts [new file with mode: 0644]
src/store/collections/collections-reducer.ts [deleted file]
src/store/collections/uploader/collection-uploader-actions.ts [deleted file]
src/store/file-uploader/file-uploader-actions.ts [new file with mode: 0644]
src/store/file-uploader/file-uploader-reducer.ts [moved from src/store/collections/uploader/collection-uploader-reducer.ts with 69% similarity]
src/store/store.ts
src/views-components/collection-panel-files/collection-panel-files.ts
src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx [deleted file]
src/views-components/context-menu/action-sets/collection-files-action-set.ts
src/views-components/dialog-copy/dialog-collection-partial-copy.tsx [new file with mode: 0644]
src/views-components/dialog-create/dialog-collection-create.tsx
src/views-components/dialog-forms/create-collection-dialog.ts
src/views-components/dialog-forms/partial-copy-collection-dialog.ts [new file with mode: 0644]
src/views-components/dialog-forms/upload-collection-files-dialog.ts [new file with mode: 0644]
src/views-components/dialog-upload/dialog-collection-files-upload.tsx [new file with mode: 0644]
src/views-components/file-uploader/file-uploader.tsx [new file with mode: 0644]
src/views-components/form-fields/collection-form-fields.tsx
src/views-components/upload-collection-files-dialog/upload-collection-files-dialog.ts [deleted file]
src/views/workbench/workbench.tsx

index 7810c4915c180dc34d638b93e3c0321167778d62..7215b6d12ad7ee92380b827d7724334354c3898e 100644 (file)
@@ -4,10 +4,10 @@
 
 import * as React from 'react';
 import { FileUpload } from "~/components/file-upload/file-upload";
-import { UploadFile } from '~/store/collections/uploader/collection-uploader-actions';
 import { Dialog, DialogTitle, DialogContent, DialogActions } from '@material-ui/core/';
 import { Button, CircularProgress } from '@material-ui/core';
-import { WithDialogProps } from '../../store/dialog/with-dialog';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { UploadFile } from '~/store/file-uploader/file-uploader-actions';
 
 export interface FilesUploadDialogProps {
     files: UploadFile[];
index 74efe009e9503a95576993985c776f9f2b396282..e7f402cdb247a900e66e3a9aa99676f3e8e2197d 100644 (file)
@@ -14,7 +14,7 @@ import { withStyles } from '@material-ui/core';
 import Dropzone from 'react-dropzone';
 import { CloudUploadIcon } from "../icon/icon";
 import { formatFileSize, formatProgress, formatUploadSpeed } from "~/common/formatters";
-import { UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
+import { UploadFile } from '~/store/file-uploader/file-uploader-actions';
 
 type CssRules = "root" | "dropzone" | "container" | "uploadIcon";
 
@@ -36,7 +36,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     }
 });
 
-interface FileUploadProps {
+export interface FileUploadProps {
     files: UploadFile[];
     disabled: boolean;
     onDrop: (files: File[]) => void;
@@ -44,21 +44,17 @@ interface FileUploadProps {
 
 export const FileUpload = withStyles(styles)(
     ({ classes, files, disabled, onDrop }: FileUploadProps & WithStyles<CssRules>) =>
-    <Grid container direction={"column"}>
-        <Typography variant={"subheading"}>
-            Upload data
-        </Typography>
         <Dropzone className={classes.dropzone} onDrop={files => onDrop(files)} disabled={disabled}>
             {files.length === 0 &&
-            <Grid container justify="center" alignItems="center" className={classes.container}>
-                <Grid item component={"span"}>
-                    <Typography variant={"subheading"}>
-                        <CloudUploadIcon className={classes.uploadIcon}/> Drag and drop data or click to browse
+                <Grid container justify="center" alignItems="center" className={classes.container}>
+                    <Grid item component={"span"}>
+                        <Typography variant={"subheading"}>
+                            <CloudUploadIcon className={classes.uploadIcon} /> Drag and drop data or click to browse
                     </Typography>
-                </Grid>
-            </Grid>}
+                    </Grid>
+                </Grid>}
             {files.length > 0 &&
-                <Table style={{width: "100%"}}>
+                <Table style={{ width: "100%" }}>
                     <TableHead>
                         <TableRow>
                             <TableCell>File name</TableCell>
@@ -68,17 +64,16 @@ export const FileUpload = withStyles(styles)(
                         </TableRow>
                     </TableHead>
                     <TableBody>
-                    {files.map(f =>
-                        <TableRow key={f.id}>
-                            <TableCell>{f.file.name}</TableCell>
-                            <TableCell>{formatFileSize(f.file.size)}</TableCell>
-                            <TableCell>{formatUploadSpeed(f.prevLoaded, f.loaded, f.prevTime, f.currentTime)}</TableCell>
-                            <TableCell>{formatProgress(f.loaded, f.total)}</TableCell>
-                        </TableRow>
-                    )}
+                        {files.map(f =>
+                            <TableRow key={f.id}>
+                                <TableCell>{f.file.name}</TableCell>
+                                <TableCell>{formatFileSize(f.file.size)}</TableCell>
+                                <TableCell>{formatUploadSpeed(f.prevLoaded, f.loaded, f.prevTime, f.currentTime)}</TableCell>
+                                <TableCell>{formatProgress(f.loaded, f.total)}</TableCell>
+                            </TableRow>
+                        )}
                     </TableBody>
                 </Table>
             }
         </Dropzone>
-    </Grid>
 );
index 5b2690bfaf7a763f8e56620a25eee66f598847d5..97b6d49c7d25d154bd51471c94ef992fcb2d27f8 100644 (file)
@@ -34,6 +34,7 @@ export const loadCollectionPanel = (uuid: string) =>
         dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid }));
         dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
         const collection = await services.collectionService.get(uuid);
+        dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection }));
         dispatch(resourcesActions.SET_RESOURCES([collection]));
         dispatch<any>(loadCollectionFiles(collection.uuid));
         dispatch<any>(loadCollectionTags(collection.uuid));
index 01b4fe4fac2f489e1aebf7402d3569322745d85d..d509218ecfa2ca8cd0c203780ad8985ae734f85b 100644 (file)
@@ -86,63 +86,7 @@ export const openMultipleFilesRemoveDialog = () =>
         }
     });
 
-export const COLLECTION_PARTIAL_COPY = 'COLLECTION_PARTIAL_COPY';
 
-export interface CollectionPartialCopyFormData {
-    name: string;
-    description: string;
-    projectUuid: string;
-}
-
-export const openCollectionPartialCopyDialog = () =>
-    (dispatch: Dispatch, getState: () => RootState) => {
-        const currentCollection = getState().collectionPanel.item;
-        if (currentCollection) {
-            const initialData = {
-                name: currentCollection.name,
-                description: currentCollection.description,
-                projectUuid: ''
-            };
-            dispatch(initialize(COLLECTION_PARTIAL_COPY, initialData));
-            dispatch<any>(resetPickerProjectTree());
-            dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY, data: {} }));
-        }
-    };
-
-export const doCollectionPartialCopy = ({ name, description, projectUuid }: CollectionPartialCopyFormData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(startSubmit(COLLECTION_PARTIAL_COPY));
-        const state = getState();
-        const currentCollection = state.collectionPanel.item;
-        if (currentCollection) {
-            try {
-                const collection = await services.collectionService.get(currentCollection.uuid);
-                const collectionCopy = {
-                    ...collection,
-                    name,
-                    description,
-                    ownerUuid: projectUuid,
-                    uuid: undefined
-                };
-                const newCollection = await services.collectionService.create(collectionCopy);
-                const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, false).map(file => file.id);
-                await services.collectionService.deleteFiles(newCollection.uuid, paths);
-                dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY }));
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'New collection created.', hideDuration: 2000 }));
-            } catch (e) {
-                const error = getCommonResourceServiceError(e);
-                if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
-                    dispatch(stopSubmit(COLLECTION_PARTIAL_COPY, { name: 'Collection with this name already exists.' }));
-                } else if (error === CommonResourceServiceError.UNKNOWN) {
-                    dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY }));
-                    dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000 }));
-                } else {
-                    dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY }));
-                    dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000 }));
-                }
-            }
-        }
-    };
 
 export const RENAME_FILE_DIALOG = 'renameFileDialog';
 export interface RenameFileDialogData {
index 1981af0d41ddae3e596e926c15cca15ac291eb9b..5a1246a7b0891b4a387cfb7a92dd538b4c34cc8a 100644 (file)
@@ -5,17 +5,16 @@
 import { Dispatch } from "redux";
 import { reset, startSubmit, stopSubmit, initialize } from 'redux-form';
 import { RootState } from '~/store/store';
-import { uploadCollectionFiles } from '~/store/collections/uploader/collection-uploader-actions';
 import { dialogActions } from "~/store/dialog/dialog-actions";
-import { CollectionResource } from '~/models/collection';
 import { ServiceRepository } from '~/services/services';
 import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service";
+import { uploadCollectionFiles } from './collection-upload-actions';
+import { fileUploaderActions } from '~/store/file-uploader/file-uploader-actions';
 
 export interface CollectionCreateFormDialogData {
     ownerUuid: string;
     name: string;
     description: string;
-    files: File[];
 }
 
 export const COLLECTION_CREATE_FORM_NAME = "collectionCreateFormName";
@@ -23,14 +22,15 @@ export const COLLECTION_CREATE_FORM_NAME = "collectionCreateFormName";
 export const openCollectionCreateDialog = (ownerUuid: string) =>
     (dispatch: Dispatch) => {
         dispatch(initialize(COLLECTION_CREATE_FORM_NAME, { ownerUuid }));
+        dispatch(fileUploaderActions.CLEAR_UPLOAD());
         dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_CREATE_FORM_NAME, data: { ownerUuid } }));
     };
 
-export const createCollection = (collection: Partial<CollectionResource>) =>
+export const createCollection = (data: CollectionCreateFormDialogData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(startSubmit(COLLECTION_CREATE_FORM_NAME));
         try {
-            const newCollection = await services.collectionService.create(collection);
+            const newCollection = await services.collectionService.create(data);
             await dispatch<any>(uploadCollectionFiles(newCollection.uuid));
             dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_CREATE_FORM_NAME }));
             dispatch(reset(COLLECTION_CREATE_FORM_NAME));
@@ -42,4 +42,4 @@ export const createCollection = (collection: Partial<CollectionResource>) =>
             }
             return ;
         }
-    };
\ No newline at end of file
+    };
diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts
new file mode 100644 (file)
index 0000000..f0dd278
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { initialize, startSubmit, stopSubmit } from 'redux-form';
+import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { ServiceRepository } from '~/services/services';
+import { filterCollectionFilesBySelection } from '../collection-panel/collection-panel-files/collection-panel-files-state';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { getCommonResourceServiceError, CommonResourceServiceError } from '~/common/api/common-resource-service';
+
+export const COLLECTION_PARTIAL_COPY_FORM_NAME = 'COLLECTION_PARTIAL_COPY_DIALOG';
+
+export interface CollectionPartialCopyFormData {
+    name: string;
+    description: string;
+    projectUuid: string;
+}
+
+export const openCollectionPartialCopyDialog = () =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const currentCollection = getState().collectionPanel.item;
+        if (currentCollection) {
+            const initialData = {
+                name: currentCollection.name,
+                description: currentCollection.description,
+                projectUuid: ''
+            };
+            dispatch(initialize(COLLECTION_PARTIAL_COPY_FORM_NAME, initialData));
+            dispatch<any>(resetPickerProjectTree());
+            dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME, data: {} }));
+        }
+    };
+
+export const doCollectionPartialCopy = ({ name, description, projectUuid }: CollectionPartialCopyFormData) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(startSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME));
+        const state = getState();
+        const currentCollection = state.collectionPanel.item;
+        if (currentCollection) {
+            try {
+                const collection = await services.collectionService.get(currentCollection.uuid);
+                const collectionCopy = {
+                    ...collection,
+                    name,
+                    description,
+                    ownerUuid: projectUuid,
+                    uuid: undefined
+                };
+                const newCollection = await services.collectionService.create(collectionCopy);
+                const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, false).map(file => file.id);
+                await services.collectionService.deleteFiles(newCollection.uuid, paths);
+                dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
+                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'New collection created.', hideDuration: 2000 }));
+            } catch (e) {
+                const error = getCommonResourceServiceError(e);
+                if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
+                    dispatch(stopSubmit(COLLECTION_PARTIAL_COPY_FORM_NAME, { name: 'Collection with this name already exists.' }));
+                } else if (error === CommonResourceServiceError.UNKNOWN) {
+                    dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
+                    dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not create a copy of collection', hideDuration: 2000 }));
+                } else {
+                    dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
+                    dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied but may contain incorrect files.', hideDuration: 2000 }));
+                }
+            }
+        }
+    };
\ No newline at end of file
diff --git a/src/store/collections/collection-upload-actions.ts b/src/store/collections/collection-upload-actions.ts
new file mode 100644 (file)
index 0000000..4a5aff3
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { loadCollectionFiles } from '../collection-panel/collection-panel-files/collection-panel-files-actions';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { fileUploaderActions } from '~/store/file-uploader/file-uploader-actions';
+import { reset, startSubmit } from 'redux-form';
+
+export const uploadCollectionFiles = (collectionUuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(fileUploaderActions.START_UPLOAD());
+        const files = getState().fileUploader.map(file => file.file);
+        await services.collectionService.uploadFiles(collectionUuid, files, handleUploadProgress(dispatch));
+        dispatch(fileUploaderActions.CLEAR_UPLOAD());
+    };
+
+export const COLLECTION_UPLOAD_FILES_DIALOG = 'uploadCollectionFilesDialog';
+
+export const openUploadCollectionFilesDialog = () => (dispatch: Dispatch) => {
+    dispatch(reset(COLLECTION_UPLOAD_FILES_DIALOG));
+    dispatch(fileUploaderActions.CLEAR_UPLOAD());
+    dispatch<any>(dialogActions.OPEN_DIALOG({ id: COLLECTION_UPLOAD_FILES_DIALOG, data: {} }));
+};
+
+export const submitCollectionFiles = () =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const currentCollection = getState().collectionPanel.item;
+        if (currentCollection) {
+            dispatch(startSubmit(COLLECTION_UPLOAD_FILES_DIALOG));
+            await dispatch<any>(uploadCollectionFiles(currentCollection.uuid));
+            dispatch<any>(loadCollectionFiles(currentCollection.uuid));
+            dispatch(closeUploadCollectionFilesDialog());
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Data has been uploaded.', hideDuration: 2000 }));
+        }
+    };
+
+export const closeUploadCollectionFilesDialog = () => dialogActions.CLOSE_DIALOG({ id: COLLECTION_UPLOAD_FILES_DIALOG });
+
+const handleUploadProgress = (dispatch: Dispatch) => (fileId: number, loaded: number, total: number, currentTime: number) => {
+    dispatch(fileUploaderActions.SET_UPLOAD_PROGRESS({ fileId, loaded, total, currentTime }));
+};
\ No newline at end of file
diff --git a/src/store/collections/collections-reducer.ts b/src/store/collections/collections-reducer.ts
deleted file mode 100644 (file)
index c760150..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { combineReducers } from 'redux';
-import { collectionUploaderReducer, CollectionUploaderState } from "./uploader/collection-uploader-reducer";
-
-export type CollectionsState = {
-    uploader: CollectionUploaderState
-};
-
-export const collectionsReducer = combineReducers({
-    uploader: collectionUploaderReducer
-});
diff --git a/src/store/collections/uploader/collection-uploader-actions.ts b/src/store/collections/uploader/collection-uploader-actions.ts
deleted file mode 100644 (file)
index 58dcdc4..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.\r
-//\r
-// SPDX-License-Identifier: AGPL-3.0\r
-\r
-import { unionize, ofType, UnionOf } from "~/common/unionize";\r
-import { Dispatch } from 'redux';\r
-import { RootState } from '~/store/store';\r
-import { ServiceRepository } from '~/services/services';\r
-import { dialogActions } from '~/store/dialog/dialog-actions';\r
-import { loadCollectionFiles } from '../../collection-panel/collection-panel-files/collection-panel-files-actions';\r
-import { snackbarActions } from "~/store/snackbar/snackbar-actions";\r
-\r
-export interface UploadFile {\r
-    id: number;\r
-    file: File;\r
-    prevLoaded: number;\r
-    loaded: number;\r
-    total: number;\r
-    startTime: number;\r
-    prevTime: number;\r
-    currentTime: number;\r
-}\r
-\r
-export const collectionUploaderActions = unionize({\r
-    SET_UPLOAD_FILES: ofType<File[]>(),\r
-    START_UPLOAD: ofType(),\r
-    SET_UPLOAD_PROGRESS: ofType<{ fileId: number, loaded: number, total: number, currentTime: number }>(),\r
-    CLEAR_UPLOAD: ofType()\r
-});\r
-\r
-export type CollectionUploaderAction = UnionOf<typeof collectionUploaderActions>;\r
-\r
-export const uploadCollectionFiles = (collectionUuid: string) =>\r
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {\r
-        dispatch(collectionUploaderActions.START_UPLOAD());\r
-        const files = getState().collections.uploader.map(file => file.file);\r
-        await services.collectionService.uploadFiles(collectionUuid, files, handleUploadProgress(dispatch));\r
-        dispatch(collectionUploaderActions.CLEAR_UPLOAD());\r
-    };\r
-\r
-\r
-export const uploadCurrentCollectionFiles = () =>\r
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {\r
-        const currentCollection = getState().collectionPanel.item;\r
-        if (currentCollection) {\r
-            await dispatch<any>(uploadCollectionFiles(currentCollection.uuid));\r
-            dispatch<any>(loadCollectionFiles(currentCollection.uuid));\r
-            dispatch(closeUploadCollectionFilesDialog());\r
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Data has been uploaded.', hideDuration: 2000 }));\r
-        }\r
-    };\r
-\r
-export const UPLOAD_COLLECTION_FILES_DIALOG = 'uploadCollectionFilesDialog';\r
-export const openUploadCollectionFilesDialog = () => (dispatch: Dispatch) => {\r
-    dispatch(collectionUploaderActions.CLEAR_UPLOAD());\r
-    dispatch<any>(dialogActions.OPEN_DIALOG({ id: UPLOAD_COLLECTION_FILES_DIALOG, data: {} }));\r
-};\r
-\r
-export const closeUploadCollectionFilesDialog = () => dialogActions.CLOSE_DIALOG({ id: UPLOAD_COLLECTION_FILES_DIALOG });\r
-\r
-const handleUploadProgress = (dispatch: Dispatch) => (fileId: number, loaded: number, total: number, currentTime: number) => {\r
-    dispatch(collectionUploaderActions.SET_UPLOAD_PROGRESS({ fileId, loaded, total, currentTime }));\r
-};
\ No newline at end of file
diff --git a/src/store/file-uploader/file-uploader-actions.ts b/src/store/file-uploader/file-uploader-actions.ts
new file mode 100644 (file)
index 0000000..906263f
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { unionize, ofType, UnionOf } from "~/common/unionize";
+
+export interface UploadFile {
+    id: number;
+    file: File;
+    prevLoaded: number;
+    loaded: number;
+    total: number;
+    startTime: number;
+    prevTime: number;
+    currentTime: number;
+}
+
+export const fileUploaderActions = unionize({
+    CLEAR_UPLOAD: ofType(),
+    SET_UPLOAD_FILES: ofType<File[]>(),
+    SET_UPLOAD_PROGRESS: ofType<{ fileId: number, loaded: number, total: number, currentTime: number }>(),
+    START_UPLOAD: ofType(),
+});
+
+export type FileUploaderAction = UnionOf<typeof fileUploaderActions>;
similarity index 69%
rename from src/store/collections/uploader/collection-uploader-reducer.ts
rename to src/store/file-uploader/file-uploader-reducer.ts
index 79a8a62339e5e3633912049ca7701aacfce2925a..625306f0c3a50afcb87e8158ddbd7719f9668df2 100644 (file)
@@ -2,14 +2,14 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { CollectionUploaderAction, collectionUploaderActions, UploadFile } from "./collection-uploader-actions";
+import { UploadFile, fileUploaderActions, FileUploaderAction } from "./file-uploader-actions";
 
-export type CollectionUploaderState = UploadFile[];
+export type UploaderState = UploadFile[];
 
-const initialState: CollectionUploaderState = [];
+const initialState: UploaderState = [];
 
-export const collectionUploaderReducer = (state: CollectionUploaderState = initialState, action: CollectionUploaderAction) => {
-    return collectionUploaderActions.match(action, {
+export const fileUploaderReducer = (state: UploaderState = initialState, action: FileUploaderAction) => {
+    return fileUploaderActions.match(action, {
         SET_UPLOAD_FILES: files => files.map((f, idx) => ({
             id: idx,
             file: f,
index abfe187c93252f73a0612f1daea063debb837096..4fe0f97a697718ee2e5c37b0375291d658b33e25 100644 (file)
@@ -22,12 +22,12 @@ import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-mid
 import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
 import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
 import { dialogReducer } from './dialog/dialog-reducer';
-import { collectionsReducer } from './collections/collections-reducer';
 import { ServiceRepository } from "~/services/services";
 import { treePickerReducer } from './tree-picker/tree-picker-reducer';
 import { resourcesReducer } from '~/store/resources/resources-reducer';
 import { propertiesReducer } from './properties/properties-reducer';
 import { RootState } from './store';
+import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -60,18 +60,18 @@ export function configureStore(history: History, services: ServiceRepository): R
 
 const createRootReducer = (services: ServiceRepository) => combineReducers({
     auth: authReducer(services),
-    collections: collectionsReducer,
-    router: routerReducer,
-    dataExplorer: dataExplorerReducer,
     collectionPanel: collectionPanelReducer,
-    detailsPanel: detailsPanelReducer,
+    collectionPanelFiles: collectionPanelFilesReducer,
     contextMenu: contextMenuReducer,
-    form: formReducer,
+    dataExplorer: dataExplorerReducer,
+    detailsPanel: detailsPanelReducer,
+    dialog: dialogReducer,
     favorites: favoritesReducer,
+    form: formReducer,
+    properties: propertiesReducer,
+    resources: resourcesReducer,
+    router: routerReducer,
     snackbar: snackbarReducer,
-    collectionPanelFiles: collectionPanelFilesReducer,
-    dialog: dialogReducer,
     treePicker: treePickerReducer,
-    resources: resourcesReducer,
-    properties: propertiesReducer,
+    fileUploader: fileUploaderReducer,
 });
index 1046995702ee11d9aab99531cfc781b651aa92b4..ccb18c8f98d715eac025855d502a2c03bc021880 100644 (file)
@@ -10,12 +10,11 @@ import { CollectionPanelFilesState, CollectionPanelDirectory, CollectionPanelFil
 import { FileTreeData } from "~/components/file-tree/file-tree-data";
 import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
 import { ContextMenuKind } from "../context-menu/context-menu";
 import { Tree, getNodeChildrenIds, getNode } from "~/models/tree";
 import { CollectionFileType } from "~/models/collection-file";
-import { openUploadCollectionFilesDialog } from '~/store/collections/uploader/collection-uploader-actions';
-import { openContextMenu } from '../../store/context-menu/context-menu-actions';
+import { openContextMenu } from '~/store/context-menu/context-menu-actions';
+import { openUploadCollectionFilesDialog } from '~/store/collections/collection-upload-actions';
 
 const memoizedMapStateToProps = () => {
     let prevState: CollectionPanelFilesState;
diff --git a/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx b/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx
deleted file mode 100644 (file)
index 86fc360..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from "react";
-import { compose } from "redux";
-import { reduxForm, InjectedFormProps } from 'redux-form';
-import { withDialog, WithDialogProps } from '~/store/dialog/with-dialog';
-import { COLLECTION_PARTIAL_COPY, doCollectionPartialCopy, CollectionPartialCopyFormData } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
-import { CollectionPartialCopyFields } from '~/views-components/form-fields/collection-form-fields';
-import { FormDialog } from '~/components/form-dialog/form-dialog';
-
-export const CollectionPartialCopyDialog = compose(
-    withDialog(COLLECTION_PARTIAL_COPY),
-    reduxForm({
-        form: COLLECTION_PARTIAL_COPY,
-        onSubmit: (data: CollectionPartialCopyFormData, dispatch) => {
-            dispatch(doCollectionPartialCopy(data));
-        }
-    }))((props: WithDialogProps<string> & InjectedFormProps<CollectionPartialCopyFormData>) =>
-        <FormDialog
-            dialogTitle='Create a collection'
-            formFields={CollectionPartialCopyFields}
-            submitLabel='Create a collection'
-            {...props}
-        />);
index 965109cadcaeaae12b4add5f788416175ab8822e..5c4dab30e537f86ad4b079bf71e92339c1da895b 100644 (file)
@@ -4,7 +4,7 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { openCollectionPartialCopyDialog } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
+import { openCollectionPartialCopyDialog } from '~/store/collections/collection-partial-copy-actions';
 
 export const collectionFilesActionSet: ContextMenuActionSet = [[{
     name: "Select all",
diff --git a/src/views-components/dialog-copy/dialog-collection-partial-copy.tsx b/src/views-components/dialog-copy/dialog-collection-partial-copy.tsx
new file mode 100644 (file)
index 0000000..7fc301f
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { FormDialog } from '~/components/form-dialog/form-dialog';
+import { CollectionNameField, CollectionDescriptionField, CollectionProjectPickerField } from '~/views-components/form-fields/collection-form-fields';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { InjectedFormProps } from 'redux-form';
+import { CollectionPartialCopyFormData } from '~/store/collections/collection-partial-copy-actions';
+
+type DialogCollectionPartialCopyProps = WithDialogProps<string> & InjectedFormProps<CollectionPartialCopyFormData>;
+
+export const DialogCollectionPartialCopy = (props: DialogCollectionPartialCopyProps) =>
+    <FormDialog
+        dialogTitle='Create a collection'
+        formFields={CollectionPartialCopyFields}
+        submitLabel='Create a collection'
+        {...props}
+    />;
+
+export const CollectionPartialCopyFields = () => <div style={{ display: 'flex' }}>
+    <div>
+        <CollectionNameField />
+        <CollectionDescriptionField />
+    </div>
+    <CollectionProjectPickerField />
+</div>;
index 2fb007b8a6a6e102039f75e39912a2bfc67812b7..38a8114de2856d4d38ca739f549ef3386831e1c2 100644 (file)
@@ -3,22 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { InjectedFormProps } from 'redux-form';
+import { InjectedFormProps, Field } from 'redux-form';
 import { WithDialogProps } from '~/store/dialog/with-dialog';
 import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions';
-import { collectionUploaderActions, UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
 import { FormDialog } from '~/components/form-dialog/form-dialog';
 import { CollectionNameField, CollectionDescriptionField } from '~/views-components/form-fields/collection-form-fields';
-import { FileUpload } from '~/components/file-upload/file-upload';
+import { require } from '~/validators/require';
+import { FileUploaderField } from '../file-uploader/file-uploader';
 
-// interface DialogCollectionDataProps {
-//     open: boolean;
-//     handleSubmit: any;
-//     submitting: boolean;
-//     invalid: boolean;
-//     pristine: boolean;
-//     files: UploadFile[];
-// }
 
 type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps<CollectionCreateFormDialogData>;
 
@@ -33,8 +25,10 @@ export const DialogCollectionCreate = (props: DialogCollectionProps) =>
 const CollectionAddFields = () => <span>
     <CollectionNameField />
     <CollectionDescriptionField />
-    {/* <FileUpload
-        files={this.props.files}
-        disabled={busy}
-        onDrop={files => this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))} /> */}
-</span>;
\ No newline at end of file
+    <Field
+        name='files'
+        validate={[require]}
+        label='Files'
+        component={FileUploaderField} />
+</span>;
+
index 581743e03fddfd6b1e7284bb9346315f4ee387c2..785be787f92952daaa606a77a1d51bda436f9d27 100644 (file)
@@ -18,5 +18,3 @@ export const CreateCollectionDialog = compose(
         }
     })
 )(DialogCollectionCreate);
-
-// onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void;
\ No newline at end of file
diff --git a/src/views-components/dialog-forms/partial-copy-collection-dialog.ts b/src/views-components/dialog-forms/partial-copy-collection-dialog.ts
new file mode 100644 (file)
index 0000000..9a350f6
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { compose } from "redux";
+import { reduxForm } from 'redux-form';
+import { withDialog, } from '~/store/dialog/with-dialog';
+import { CollectionPartialCopyFormData, doCollectionPartialCopy, COLLECTION_PARTIAL_COPY_FORM_NAME } from '~/store/collections/collection-partial-copy-actions';
+import { DialogCollectionPartialCopy } from "~/views-components/dialog-copy/dialog-collection-partial-copy";
+
+
+export const PartialCopyCollectionDialog = compose(
+    withDialog(COLLECTION_PARTIAL_COPY_FORM_NAME),
+    reduxForm({
+        form: COLLECTION_PARTIAL_COPY_FORM_NAME,
+        onSubmit: (data: CollectionPartialCopyFormData, dispatch) => {
+            dispatch(doCollectionPartialCopy(data));
+        }
+    }))(DialogCollectionPartialCopy);
\ No newline at end of file
diff --git a/src/views-components/dialog-forms/upload-collection-files-dialog.ts b/src/views-components/dialog-forms/upload-collection-files-dialog.ts
new file mode 100644 (file)
index 0000000..38f0333
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { compose } from "redux";
+import { reduxForm } from 'redux-form';
+import { withDialog } from "~/store/dialog/with-dialog";
+import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions';
+import { COLLECTION_UPLOAD_FILES_DIALOG, submitCollectionFiles } from '~/store/collections/collection-upload-actions';
+import { DialogCollectionFilesUpload } from '../dialog-upload/dialog-collection-files-upload';
+
+export const UploadCollectionFilesDialog = compose(
+    withDialog(COLLECTION_UPLOAD_FILES_DIALOG),
+    reduxForm<CollectionCreateFormDialogData>({
+        form: COLLECTION_UPLOAD_FILES_DIALOG,
+        onSubmit: (data, dispatch) => {
+            dispatch(submitCollectionFiles());
+        }
+    })
+)(DialogCollectionFilesUpload);
diff --git a/src/views-components/dialog-upload/dialog-collection-files-upload.tsx b/src/views-components/dialog-upload/dialog-collection-files-upload.tsx
new file mode 100644 (file)
index 0000000..35c1ed6
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { InjectedFormProps, Field } from 'redux-form';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions';
+import { FormDialog } from '~/components/form-dialog/form-dialog';
+import { require } from '~/validators/require';
+import { FileUploaderField } from '~/views-components/file-uploader/file-uploader';
+
+
+type DialogCollectionFilesUploadProps = WithDialogProps<{}> & InjectedFormProps<CollectionCreateFormDialogData>;
+
+export const DialogCollectionFilesUpload = (props: DialogCollectionFilesUploadProps) =>
+    <FormDialog
+        dialogTitle='Upload data'
+        formFields={UploadCollectionFilesFields}
+        submitLabel='Upload data'
+        {...props}
+    />;
+
+const UploadCollectionFilesFields = () =>
+    <Field
+        name='files'
+        validate={FILES_FIELD_VALIDATION}
+        component={FileUploaderField} />;
+
+const FILES_FIELD_VALIDATION = [require];
+
+
diff --git a/src/views-components/file-uploader/file-uploader.tsx b/src/views-components/file-uploader/file-uploader.tsx
new file mode 100644 (file)
index 0000000..d9111f4
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { FileUpload } from '~/components/file-upload/file-upload';
+import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { FileUploadProps } from '../../components/file-upload/file-upload';
+import { Dispatch } from 'redux';
+import { fileUploaderActions } from '~/store/file-uploader/file-uploader-actions';
+import { WrappedFieldProps } from 'redux-form';
+import { Typography } from '@material-ui/core';
+
+export type FileUploaderProps = Pick<FileUploadProps, 'disabled' | 'onDrop'>;
+
+const mapStateToProps = (state: RootState, { disabled }: FileUploaderProps): Pick<FileUploadProps, 'files' | 'disabled'> => ({
+    disabled,
+    files: state.fileUploader,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch, { onDrop }: FileUploaderProps): Pick<FileUploadProps, 'onDrop'> => ({
+    onDrop: files => {
+        dispatch(fileUploaderActions.SET_UPLOAD_FILES(files));
+        onDrop(files);
+    },
+});
+
+export const FileUploader = connect(mapStateToProps, mapDispatchToProps)(FileUpload);
+
+export const FileUploaderField = (props: WrappedFieldProps & { label?: string }) =>
+    <div>
+        <Typography variant='caption'>{props.label}</Typography>
+        <FileUploader disabled={props.meta.submitting} onDrop={props.input.onChange} />
+    </div>;
index 10c807b6211bbacf0316abcf95d04b8407708f1e..af240fc5666c1735bf4938ade1b0bffe3ad358fe 100644 (file)
@@ -8,14 +8,6 @@ import { TextField } from "~/components/text-field/text-field";
 import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators";
 import { ProjectTreePicker } from "~/views-components/project-tree-picker/project-tree-picker";
 
-export const CollectionPartialCopyFields = () => <div style={{ display: 'flex' }}>
-    <div>
-        <CollectionNameField />
-        <CollectionDescriptionField />
-    </div>
-    <CollectionProjectPickerField />
-</div>;
-
 export const CollectionNameField = () =>
     <Field
         name='name'
diff --git a/src/views-components/upload-collection-files-dialog/upload-collection-files-dialog.ts b/src/views-components/upload-collection-files-dialog/upload-collection-files-dialog.ts
deleted file mode 100644 (file)
index 1f3a50e..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { connect } from "react-redux";
-import { Dispatch, compose } from "redux";
-import { withDialog } from '~/store/dialog/with-dialog';
-import { FilesUploadDialog } from '~/components/file-upload-dialog/file-upload-dialog';
-import { RootState } from '../../store/store';
-import { uploadCurrentCollectionFiles, UPLOAD_COLLECTION_FILES_DIALOG, collectionUploaderActions } from '~/store/collections/uploader/collection-uploader-actions';
-
-const mapStateToProps = (state: RootState) => ({
-    files: state.collections.uploader,
-    uploading: state.collections.uploader.some(file => file.loaded < file.total)
-});
-
-const mapDispatchToProps = (dispatch: Dispatch) => ({
-    onSubmit: () => {
-        dispatch<any>(uploadCurrentCollectionFiles());
-    },
-    onChange: (files: File[]) => {
-        dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files));
-    }
-});
-
-export const UploadCollectionFilesDialog = compose(
-    withDialog(UPLOAD_COLLECTION_FILES_DIALOG),
-    connect(mapStateToProps, mapDispatchToProps)
-)(FilesUploadDialog);
\ No newline at end of file
index 12010acc0eb04733877c3ac2245a39ed35b227e0..82a868e6dece3fb472d53d11b1fa99fa8c20461d 100644 (file)
@@ -24,8 +24,6 @@ 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 { UploadCollectionFilesDialog } from '~/views-components/upload-collection-files-dialog/upload-collection-files-dialog';
-import { CollectionPartialCopyDialog } from '~/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog';
 import { SidePanel } from '~/views-components/side-panel/side-panel';
 import { Routes } from '~/routes/routes';
 import { Breadcrumbs } from '~/views-components/breadcrumbs/breadcrumbs';
@@ -36,6 +34,8 @@ import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-c
 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 { UploadCollectionFilesDialog } from '~/views-components/dialog-forms/upload-collection-files-dialog';
+import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
 
 
 const APP_BAR_HEIGHT = 100;
@@ -174,7 +174,7 @@ export const Workbench = withStyles(styles)(
                         <CreateProjectDialog />
                         <CreateCollectionDialog />
                         <RenameFileDialog />
-                        <CollectionPartialCopyDialog />
+                        <PartialCopyCollectionDialog />
                         <FileRemoveDialog />
                         <CopyCollectionDialog />
                         <MultipleFilesRemoveDialog />