Merge branch '14015-rename-a-file'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 20 Aug 2018 14:29:33 +0000 (16:29 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 20 Aug 2018 14:29:33 +0000 (16:29 +0200)
refs #14015

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

src/components/form-dialog/form-dialog.tsx
src/services/collection-service/collection-service.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/dialog/dialog-reducer.ts
src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
src/views-components/rename-file-dialog/rename-file-dialog.tsx

index 29c0b1635f8ba25df4e895498b6fc17a7fe59340..dee89249e231dc3b17e406572e4e8de5952b7870 100644 (file)
@@ -8,7 +8,7 @@ import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/
 import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
 import { WithDialogProps } from '~/store/dialog/with-dialog';
 
-type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "dialog" | "dialogTitle" | "progressIndicator" | "dialogActions";
+type CssRules = "button" | "lastButton" | "formContainer" | "dialogTitle" | "progressIndicator" | "dialogActions";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -26,13 +26,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     dialogTitle: {
         paddingBottom: "0"
     },
-    textField: {
-        marginTop: "32px",
-    },
-    dialog: {
-        minWidth: "600px",
-        minHeight: "320px"
-    },
     progressIndicator: {
         position: "absolute",
         minWidth: "20px",
@@ -45,7 +38,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 interface DialogProjectProps {
     cancelLabel?: string;
     dialogTitle: string;
-    formFields: React.ComponentType<InjectedFormProps<any>>;
+    formFields: React.ComponentType<InjectedFormProps<any> & WithDialogProps<any>>;
     submitLabel?: string;
 }
 
@@ -54,35 +47,34 @@ export const FormDialog = withStyles(styles)((props: DialogProjectProps & WithDi
         open={props.open}
         onClose={props.closeDialog}
         disableBackdropClick={props.submitting}
-        disableEscapeKeyDown={props.submitting}>
-        <div className={props.classes.dialog}>
-            <form>
-                <DialogTitle className={props.classes.dialogTitle}>
-                    {props.dialogTitle}
-                </DialogTitle>
-                <DialogContent className={props.classes.formContainer}>
-                    <props.formFields {...props} />
-                </DialogContent>
-                <DialogActions className={props.classes.dialogActions}>
-                    <Button
-                        onClick={props.closeDialog}
-                        className={props.classes.button}
-                        color="primary"
-                        disabled={props.submitting}>
-                        {props.cancelLabel || 'Cancel'}
-                    </Button>
-                    <Button
-                        onClick={props.handleSubmit}
-                        className={props.classes.lastButton}
-                        color="primary"
-                        disabled={props.invalid || props.submitting || props.pristine}
-                        variant="contained">
-                        {props.submitLabel || 'Submit'}
-                        {props.submitting && <CircularProgress size={20} className={props.classes.progressIndicator} />}
-                    </Button>
-                </DialogActions>
-            </form>
-        </div>
+        disableEscapeKeyDown={props.submitting}
+        fullWidth>
+        <form>
+            <DialogTitle className={props.classes.dialogTitle}>
+                {props.dialogTitle}
+            </DialogTitle>
+            <DialogContent className={props.classes.formContainer}>
+                <props.formFields {...props} />
+            </DialogContent>
+            <DialogActions className={props.classes.dialogActions}>
+                <Button
+                    onClick={props.closeDialog}
+                    className={props.classes.button}
+                    color="primary"
+                    disabled={props.submitting}>
+                    {props.cancelLabel || 'Cancel'}
+                </Button>
+                <Button
+                    onClick={props.handleSubmit}
+                    className={props.classes.lastButton}
+                    color="primary"
+                    disabled={props.invalid || props.submitting || props.pristine}
+                    variant="contained">
+                    {props.submitLabel || 'Submit'}
+                    {props.submitting && <CircularProgress size={20} className={props.classes.progressIndicator} />}
+                </Button>
+            </DialogActions>
+        </form>
     </Dialog>
 );
 
index 6aa2123d0c6ac228d7e2517116807ad247791a1e..1c62ec5a68b471b89e646e6d698d99b423a421b8 100644 (file)
@@ -41,6 +41,13 @@ export class CollectionService extends CommonResourceService<CollectionResource>
         }
     }
 
+    moveFile(collectionUuid: string, oldPath: string, newPath: string) {
+        return this.webdavClient.move(
+            `/c=${collectionUuid}${oldPath}`,
+            `/c=${collectionUuid}${encodeURI(newPath)}`
+        );
+    }
+
     private extendFileURL = (file: CollectionDirectory | CollectionFile) => ({
         ...file,
         url: this.webdavClient.defaults.baseURL + file.url + '?api_token=' + this.authService.getApiToken()
index e7a7ec86b6b017eabee18c8a48575fa800b20961..39d565938da85f8df779ed03c76f0406304b3150 100644 (file)
@@ -11,9 +11,10 @@ import { snackbarActions } from "../../snackbar/snackbar-actions";
 import { dialogActions } from '../../dialog/dialog-actions';
 import { getNodeValue } from "~/models/tree";
 import { filterCollectionFilesBySelection } from './collection-panel-files-state';
-import { startSubmit, initialize, SubmissionError, stopSubmit } from 'redux-form';
+import { startSubmit, initialize, SubmissionError, stopSubmit, reset } from 'redux-form';
 import { loadProjectTreePickerProjects } from '../../../views-components/project-tree-picker/project-tree-picker';
 import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service";
+import { getDialog } from "~/store/dialog/dialog-reducer";
 
 export const collectionPanelFilesAction = unionize({
     SET_COLLECTION_FILES: ofType<CollectionFilesTree>(),
@@ -143,3 +144,33 @@ export const doCollectionPartialCopy = ({ name, description, projectUuid }: Coll
         }
     };
 
+export const RENAME_FILE_DIALOG = 'renameFileDialog';
+export interface RenameFileDialogData {
+    name: string;
+    id: string;
+}
+
+export const openRenameFileDialog = (data: RenameFileDialogData) =>
+    (dispatch: Dispatch) => {
+        dispatch(reset(RENAME_FILE_DIALOG));
+        dispatch(dialogActions.OPEN_DIALOG({ id: RENAME_FILE_DIALOG, data }));
+    };
+
+export const renameFile = (newName: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const dialog = getDialog<RenameFileDialogData>(getState().dialog, RENAME_FILE_DIALOG);
+        const currentCollection = getState().collectionPanel.item;
+        if (dialog && currentCollection) {
+            dispatch(startSubmit(RENAME_FILE_DIALOG));
+            const oldPath = dialog.data.id;
+            const newPath = dialog.data.id.replace(dialog.data.name, newName);
+            try {
+                await services.collectionService.moveFile(currentCollection.uuid, oldPath, newPath);
+                dispatch<any>(loadCollectionFiles(currentCollection.uuid));
+                dispatch(dialogActions.CLOSE_DIALOG({ id: RENAME_FILE_DIALOG }));
+                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File name changed.', hideDuration: 2000 }));
+            } catch (e) {
+                dispatch(stopSubmit(RENAME_FILE_DIALOG, { name: 'Could not rename the file' }));
+            }
+        }
+    };
index 34d38fdf4ea6e2d39dc28814a2f2a1dcee5e9733..48f8ee8a1e6caac149fb07b8c94a10a46a3f2377 100644 (file)
@@ -4,11 +4,11 @@
 
 import { DialogAction, dialogActions } from "./dialog-actions";
 
-export type DialogState = Record<string, Dialog>;
+export type DialogState = Record<string, Dialog<any>>;
 
-export interface Dialog {
+export interface Dialog <T> {
     open: boolean;
-    data: any;
+    data: T;
 }
 
 export const dialogReducer = (state: DialogState = {}, action: DialogAction) =>
@@ -20,3 +20,5 @@ export const dialogReducer = (state: DialogState = {}, action: DialogAction) =>
         default: () => state,
     });
 
+export const getDialog = <T>(state: DialogState, id: string) => 
+    state[id] ? state[id] as Dialog<T> : undefined;
index a3bfa0b95cb30736ae8c995fcfa00e3deb463b0f..b55648917329723d6cec917b8e73b53725695673 100644 (file)
@@ -3,17 +3,16 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { RenameIcon, DownloadIcon, RemoveIcon } from "~/components/icon/icon";
-import { openRenameFileDialog } from "../../rename-file-dialog/rename-file-dialog";
+import { RenameIcon, RemoveIcon } from "~/components/icon/icon";
 import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
-import { openFileRemoveDialog } from "../../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
+import { openFileRemoveDialog, openRenameFileDialog } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
 
 
 export const collectionFilesItemActionSet: ContextMenuActionSet = [[{
     name: "Rename",
     icon: RenameIcon,
     execute: (dispatch, resource) => {
-        dispatch<any>(openRenameFileDialog(resource.name));
+        dispatch<any>(openRenameFileDialog({ name: resource.name, id: resource.uuid }));
     }
 }, {
     component: DownloadCollectionFileAction,
index 37028f9db5adc5b4cffe7cfffa25b1d969a7fcee..20116fcda747c0f322223b48fc6d83b67a024762 100644 (file)
@@ -2,27 +2,37 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Dispatch } from "redux";
-import { reduxForm, reset, startSubmit, stopSubmit } from "redux-form";
-import { withDialog } from "~/store/dialog/with-dialog";
-import { dialogActions } from "~/store/dialog/dialog-actions";
-import { RenameDialog } from "~/components/rename-dialog/rename-dialog";
+import * as React from 'react';
+import { compose } from 'redux';
+import { reduxForm, reset, startSubmit, stopSubmit, InjectedFormProps, Field } from 'redux-form';
+import { withDialog, WithDialogProps } from '~/store/dialog/with-dialog';
+import { FormDialog } from '~/components/form-dialog/form-dialog';
+import { DialogContentText } from '@material-ui/core';
+import { TextField } from '~/components/text-field/text-field';
+import { RENAME_FILE_DIALOG, RenameFileDialogData, renameFile } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
 
-export const RENAME_FILE_DIALOG = 'renameFileDialog';
-
-export const openRenameFileDialog = (originalName: string) =>
-    (dispatch: Dispatch) => {
-        dispatch(reset(RENAME_FILE_DIALOG));
-        dispatch(dialogActions.OPEN_DIALOG({ id: RENAME_FILE_DIALOG, data: originalName }));
-    };
-
-export const [RenameFileDialog] = [RenameDialog]
-    .map(withDialog(RENAME_FILE_DIALOG))
-    .map(reduxForm({
+export const RenameFileDialog = compose(
+    withDialog(RENAME_FILE_DIALOG),
+    reduxForm({
         form: RENAME_FILE_DIALOG,
-        onSubmit: (data, dispatch) => {
-            dispatch(startSubmit(RENAME_FILE_DIALOG));
-            // TODO: call collection file renaming action here
-            setTimeout(() => dispatch(stopSubmit(RENAME_FILE_DIALOG, { name: 'Invalid name' })), 2000);
+        onSubmit: (data: { name: string }, dispatch) => {
+            dispatch<any>(renameFile(data.name));
         }
-    }));
+    })
+)((props: WithDialogProps<RenameFileDialogData> & InjectedFormProps<{ name: string }>) =>
+    <FormDialog
+        dialogTitle='Rename'
+        formFields={RenameDialogFormFields}
+        submitLabel='Ok'
+        {...props}
+    />);
+
+const RenameDialogFormFields = (props: WithDialogProps<RenameFileDialogData>) => <>
+    <DialogContentText>
+        {`Please, enter a new name for ${props.data.name}`}
+    </DialogContentText>
+    <Field
+        name='name'
+        component={TextField}
+    />
+</>;