Implement single file remove action
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 10 Aug 2018 12:51:05 +0000 (14:51 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Fri, 10 Aug 2018 12:51:05 +0000 (14:51 +0200)
Feature #13990

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

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

index c23cb1e9bc5b69a0bd24c852bd60b3bfb3ea8120..32eeac4f74ef8d6a3eeeee92948a4047f49b3223 100644 (file)
@@ -29,6 +29,11 @@ export class CollectionService extends CommonResourceService<CollectionResource>
         return Promise.reject();
     }
 
         return Promise.reject();
     }
 
+    async deleteFile(collectionUuid: string, filePath: string){
+        return this.webdavClient.delete(`/c=${collectionUuid}${filePath}`);
+    }
+
+
     extractFilesData(document: Document) {
         return Array
             .from(document.getElementsByTagName('D:response'))
     extractFilesData(document: Document) {
         return Array
             .from(document.getElementsByTagName('D:response'))
index e1b3a376e32cbb56173b9159cc94e0f5cd8f0f1f..461c47c42eb59355972dcd745c4f0ace0a285ba4 100644 (file)
@@ -6,7 +6,7 @@ import { unionize, ofType, UnionOf } from "unionize";
 import { Dispatch } from "redux";
 import { ResourceKind } from "../../models/resource";
 import { CollectionResource } from "../../models/collection";
 import { Dispatch } from "redux";
 import { ResourceKind } from "../../models/resource";
 import { CollectionResource } from "../../models/collection";
-import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
+import { collectionPanelFilesAction, loadCollectionFiles } from "./collection-panel-files/collection-panel-files-actions";
 import { createTree } from "../../models/tree";
 import { RootState } from "../store";
 import { ServiceRepository } from "../../services/services";
 import { createTree } from "../../models/tree";
 import { RootState } from "../store";
 import { ServiceRepository } from "../../services/services";
@@ -14,7 +14,7 @@ import { TagResource, TagProperty } from "../../models/tag";
 import { snackbarActions } from "../snackbar/snackbar-actions";
 
 export const collectionPanelActions = unionize({
 import { snackbarActions } from "../snackbar/snackbar-actions";
 
 export const collectionPanelActions = unionize({
-    LOAD_COLLECTION: ofType<{ uuid: string, kind: ResourceKind }>(),
+    LOAD_COLLECTION: ofType<{ uuid: string }>(),
     LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
     LOAD_COLLECTION_TAGS: ofType<{ uuid: string }>(),
     LOAD_COLLECTION_TAGS_SUCCESS: ofType<{ tags: TagResource[] }>(),
     LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
     LOAD_COLLECTION_TAGS: ofType<{ uuid: string }>(),
     LOAD_COLLECTION_TAGS_SUCCESS: ofType<{ tags: TagResource[] }>(),
@@ -28,18 +28,15 @@ export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
 
 export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
 
 
 export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
 
-export const loadCollection = (uuid: string, kind: ResourceKind) =>
+export const loadCollection = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid, kind }));
+        dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid }));
         dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
         return services.collectionService
             .get(uuid)
             .then(item => {
                 dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item }));
         dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
         return services.collectionService
             .get(uuid)
             .then(item => {
                 dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item }));
-                return services.collectionService.files(item.uuid);
-            })
-            .then(files => {
-                dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files));
+                dispatch<any>(loadCollectionFiles(uuid));
             });
     };
 
             });
     };
 
index 463d49c5ef9de106349e7bd22135fe2fb5aa8882..12e64138a4089c7f1ed8dd1ae4e3c7478ba47ad3 100644 (file)
@@ -3,7 +3,13 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
-import { CollectionFilesTree } from "../../../models/collection-file";
+import { Dispatch } from "redux";
+import { CollectionFilesTree, CollectionFileType } from "../../../models/collection-file";
+import { ServiceRepository } from "../../../services/services";
+import { RootState } from "../../store";
+import { snackbarActions } from "../../snackbar/snackbar-actions";
+import { dialogActions } from "../../dialog/dialog-actions";
+import { getNodeValue } from "../../../models/tree";
 
 export const collectionPanelFilesAction = unionize({
     SET_COLLECTION_FILES: ofType<CollectionFilesTree>(),
 
 export const collectionPanelFilesAction = unionize({
     SET_COLLECTION_FILES: ofType<CollectionFilesTree>(),
@@ -13,4 +19,46 @@ export const collectionPanelFilesAction = unionize({
     UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(),
 }, { tag: 'type', value: 'payload' });
 
     UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(),
 }, { tag: 'type', value: 'payload' });
 
-export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
\ No newline at end of file
+export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
+
+export const loadCollectionFiles = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const files = await services.collectionService.files(uuid);
+        dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files));
+    };
+
+export const removeCollectionFiles = (filePaths: string[]) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const { item } = getState().collectionPanel;
+        if (item) {
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+            const promises = filePaths.map(filePath => services.collectionService.deleteFile(item.uuid, filePath));
+            await Promise.all(promises);
+            dispatch<any>(loadCollectionFiles(item.uuid));
+            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+        }
+    };
+
+export const FILE_REMOVE_DIALOG = 'fileRemoveDialog';
+export const openFileRemoveDialog = (filePath: string) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const file = getNodeValue(filePath)(getState().collectionPanelFiles);
+        if (file) {
+            const title = file.type === CollectionFileType.DIRECTORY
+                ? 'Removing directory'
+                : 'Removing file';
+            const text = file.type === CollectionFileType.DIRECTORY
+                ? 'Are you sure you want to remove this directory?'
+                : 'Are you sure you want to remove this file?';
+
+            dispatch(dialogActions.OPEN_DIALOG({
+                id: FILE_REMOVE_DIALOG,
+                data: {
+                    title,
+                    text,
+                    confirmButtonLabel: 'Remove',
+                    filePath
+                }
+            }));
+        }
+    };
\ No newline at end of file
index 7b03c49ade34658700f9268c4bad3fcd3f966173..6beef5e7e77ebd40f0570075de640ddaa35d91d3 100644 (file)
@@ -5,8 +5,8 @@
 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 { ContextMenuActionSet } from "../context-menu-action-set";
 import { RenameIcon, DownloadIcon, RemoveIcon } from "../../../components/icon/icon";
 import { openRenameFileDialog } from "../../rename-file-dialog/rename-file-dialog";
-import { openFileRemoveDialog } from "../../file-remove-dialog/file-remove-dialog";
 import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
 import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
+import { openFileRemoveDialog } from "../../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
 
 
 export const collectionFilesItemActionSet: ContextMenuActionSet = [[{
 
 
 export const collectionFilesItemActionSet: ContextMenuActionSet = [[{
@@ -22,6 +22,6 @@ export const collectionFilesItemActionSet: ContextMenuActionSet = [[{
     name: "Remove",
     icon: RemoveIcon,
     execute: (dispatch, resource) => {
     name: "Remove",
     icon: RemoveIcon,
     execute: (dispatch, resource) => {
-        dispatch(openFileRemoveDialog(resource.uuid));
+        dispatch<any>(openFileRemoveDialog(resource.uuid));
     }
 }]];
     }
 }]];
index 3678e535450932c8d7e428e9da7280b49e2b00d4..832b9ef6327677c15f57620b83590df90e2ea38b 100644 (file)
@@ -5,34 +5,29 @@
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
 import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
 import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
+import { withDialog, WithDialogProps } from "../../store/dialog/with-dialog";
+import { RootState } from "../../store/store";
+import { removeCollectionFiles, FILE_REMOVE_DIALOG } from "../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
 
 
-const FILE_REMOVE_DIALOG = 'fileRemoveDialog';
+const mapStateToProps = (state: RootState, props: WithDialogProps<{ filePath: string }>) => ({
+    filePath: props.data.filePath
+});
 
 
-const mapDispatchToProps = (dispatch: Dispatch) => ({
-    onConfirm: () => {
-        // TODO: dispatch action that removes single file
-        dispatch(dialogActions.CLOSE_DIALOG({ id: FILE_REMOVE_DIALOG }));
-        dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing file...', hideDuration: 2000 }));
-        setTimeout(() => {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File removed.', hideDuration: 2000 }));
-        }, 1000);
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<{ filePath: string }>) => ({
+    onConfirm: (filePath: string) => {
+        props.closeDialog();
+        dispatch<any>(removeCollectionFiles([filePath]));
     }
 });
 
     }
 });
 
-export const openFileRemoveDialog = (fileId: string) =>
-    dialogActions.OPEN_DIALOG({
-        id: FILE_REMOVE_DIALOG,
-        data: {
-            title: 'Removing file',
-            text: 'Are you sure you want to remove this file?',
-            confirmButtonLabel: 'Remove',
-            fileId
-        }
+const mergeProps = (
+    stateProps: { filePath: string },
+    dispatchProps: { onConfirm: (filePath: string) => void },
+    props: WithDialogProps<{ filePath: string }>) => ({
+        onConfirm: () => dispatchProps.onConfirm(stateProps.filePath),
+        ...props
     });
 
 export const [FileRemoveDialog] = [ConfirmationDialog]
     });
 
 export const [FileRemoveDialog] = [ConfirmationDialog]
-    .map(withDialog(FILE_REMOVE_DIALOG))
-    .map(connect(undefined, mapDispatchToProps));
\ No newline at end of file
+    .map(connect(mapStateToProps, mapDispatchToProps, mergeProps))
+    .map(withDialog(FILE_REMOVE_DIALOG));
\ No newline at end of file
index a8552eef824053bf7c197bc85d18758045a77bdd..7f257542ebd8c6623cc0768476918ae5268e861d 100644 (file)
@@ -248,9 +248,9 @@ export const Workbench = withStyles(styles)(
                 );
             }
 
                 );
             }
 
-            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel 
+            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
                 onItemRouteChange={(collectionId) => {
                 onItemRouteChange={(collectionId) => {
-                    this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
+                    this.props.dispatch<any>(loadCollection(collectionId));
                     this.props.dispatch<any>(loadCollectionTags(collectionId));
                 }}
                 onContextMenu={(event, item) => {
                     this.props.dispatch<any>(loadCollectionTags(collectionId));
                 }}
                 onContextMenu={(event, item) => {
@@ -281,9 +281,9 @@ export const Workbench = withStyles(styles)(
                 onItemDoubleClick={item => {
                     switch (item.kind) {
                         case ResourceKind.COLLECTION:
                 onItemDoubleClick={item => {
                     switch (item.kind) {
                         case ResourceKind.COLLECTION:
-                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
+                            this.props.dispatch(loadCollection(item.uuid));
                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
                             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));
                     }
                             this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
                             this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
                     }
@@ -308,7 +308,7 @@ export const Workbench = withStyles(styles)(
                 onItemDoubleClick={item => {
                     switch (item.kind) {
                         case ResourceKind.COLLECTION:
                 onItemDoubleClick={item => {
                     switch (item.kind) {
                         case ResourceKind.COLLECTION:
-                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
+                            this.props.dispatch(loadCollection(item.uuid));
                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
                         default:
                             this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
                         default:
                             this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));