From 2ddeeb1b278734e895128feb2caa2bf720192b3d Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Fri, 10 Aug 2018 14:51:05 +0200 Subject: [PATCH] Implement single file remove action Feature #13990 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- .../collection-service/collection-service.ts | 5 ++ .../collection-panel-action.ts | 13 ++--- .../collection-panel-files-actions.ts | 52 ++++++++++++++++++- .../collection-files-item-action-set.ts | 4 +- .../file-remove-dialog/file-remove-dialog.ts | 41 +++++++-------- src/views/workbench/workbench.tsx | 10 ++-- 6 files changed, 85 insertions(+), 40 deletions(-) diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts index c23cb1e9bc..32eeac4f74 100644 --- a/src/services/collection-service/collection-service.ts +++ b/src/services/collection-service/collection-service.ts @@ -29,6 +29,11 @@ export class CollectionService extends CommonResourceService 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')) diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts index e1b3a376e3..461c47c42e 100644 --- a/src/store/collection-panel/collection-panel-action.ts +++ b/src/store/collection-panel/collection-panel-action.ts @@ -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 { 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"; @@ -14,7 +14,7 @@ import { TagResource, TagProperty } from "../../models/tag"; 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[] }>(), @@ -28,18 +28,15 @@ export type CollectionPanelAction = UnionOf; 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(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 })); - return services.collectionService.files(item.uuid); - }) - .then(files => { - dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files)); + dispatch(loadCollectionFiles(uuid)); }); }; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts index 463d49c5ef..12e64138a4 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts @@ -3,7 +3,13 @@ // 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(), @@ -13,4 +19,46 @@ export const collectionPanelFilesAction = unionize({ UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(), }, { tag: 'type', value: 'payload' }); -export type CollectionPanelFilesAction = UnionOf; \ No newline at end of file +export type CollectionPanelFilesAction = UnionOf; + +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(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 diff --git a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts index 7b03c49ade..6beef5e7e7 100644 --- a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts @@ -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 { openFileRemoveDialog } from "../../file-remove-dialog/file-remove-dialog"; 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 = [[{ @@ -22,6 +22,6 @@ export const collectionFilesItemActionSet: ContextMenuActionSet = [[{ name: "Remove", icon: RemoveIcon, execute: (dispatch, resource) => { - dispatch(openFileRemoveDialog(resource.uuid)); + dispatch(openFileRemoveDialog(resource.uuid)); } }]]; diff --git a/src/views-components/file-remove-dialog/file-remove-dialog.ts b/src/views-components/file-remove-dialog/file-remove-dialog.ts index 3678e53545..832b9ef632 100644 --- a/src/views-components/file-remove-dialog/file-remove-dialog.ts +++ b/src/views-components/file-remove-dialog/file-remove-dialog.ts @@ -5,34 +5,29 @@ 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(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] - .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 diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index a8552eef82..7f257542eb 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -248,9 +248,9 @@ export const Workbench = withStyles(styles)( ); } - renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => ) => { - this.props.dispatch(loadCollection(collectionId, ResourceKind.COLLECTION)); + this.props.dispatch(loadCollection(collectionId)); this.props.dispatch(loadCollectionTags(collectionId)); }} onContextMenu={(event, item) => { @@ -281,9 +281,9 @@ export const Workbench = withStyles(styles)( 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: + default: 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: - 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)); -- 2.39.5