Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add confirmation modal for removing from workspace #236

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions frontend/src/js/actions/workspaces/deleteItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { GiantState } from '../../types/redux/GiantState';

export function deleteItem(
workspaceId: string,
itemId: string
itemId: string,
onCompleteHandler: (isSuccess: boolean) => void
): ThunkAction<void, GiantState, null, WorkspacesAction | AppAction> {
return dispatch => {
return deleteItemApi(workspaceId, itemId)
.then(() => {
dispatch(getWorkspace(workspaceId));
onCompleteHandler(true);
})
.catch(error => dispatch(errorRenamingItem(error)));
.catch(error => {
onCompleteHandler(false);
dispatch(errorRenamingItem(error))
});
};
}

Expand Down
151 changes: 151 additions & 0 deletions frontend/src/js/components/workspace/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import { GiantState } from '../../types/redux/GiantState';
import { connect } from 'react-redux';
import Modal from "../UtilComponents/Modal";
import {ProgressAnimation} from "../UtilComponents/ProgressAnimation";
import { isTreeLeaf, TreeEntry } from '../../types/Tree';
import { WorkspaceEntry } from '../../types/Workspaces';

export type ModalStatus = "unconfirmed" | "doing" | "done" | "failed"

export function DeleteModal({ deleteItemHandler, isOpen, setModalOpen, deleteStatus, entry }:
{ deleteItemHandler: () => void,
isOpen: boolean,
setModalOpen: (value: boolean) => void,
deleteStatus: ModalStatus,
entry: null | TreeEntry<WorkspaceEntry>
}) {
if (entry !== null) {
const modalTitle: Record<ModalStatus, string> = {
unconfirmed: "Delete item from Giant?",
doing: "Deleting item from Giant",
done: "Item deleted from Giant",
failed: "Failed to delete item from Giant"
}

const modalMessage: Record<ModalStatus, string> = {
unconfirmed: `This will delete the file [${entry.name}] from Giant. It cannot be undone. Are you sure you want to proceed?`,
doing: "",
done: "This item has been successfully deleted from Giant.",
failed: "Failed to delete item from Giant. Please contact the administrator to delete this item."
}

return <ConfirmModal
handler={deleteItemHandler}
isOpen={isOpen}
setModalOpen={setModalOpen}
status={deleteStatus}
modalTitle={modalTitle}
modalMessage={modalMessage}/>
}

return null;
}

export function RemoveFromWorkspaceModal({ removeHandler, isOpen, setModalOpen, removeStatus, entry }:
{ removeHandler: () => void,
isOpen: boolean,
setModalOpen: (value: boolean) => void,
removeStatus: ModalStatus,
entry: null | TreeEntry<WorkspaceEntry>
}) {
if (entry !== null) {
const removeMessage = isTreeLeaf(entry) ?
`This will remove the file [${entry.name}] from the current workspace. It cannot be undone. Are you sure you want to proceed?` :
`This will remove the selection [${entry.name}] and everything nested inside it from your workspace. It cannot be undone. Are you sure you want to proceed?`

const modalTitle: Record<ModalStatus, string> = {
unconfirmed: "Remove item from workspace?",
doing: "Removing item from workspace",
done: "Item removed from workspace",
failed: "Failed to remove item from workspace"
}

const modalMessage: Record<ModalStatus, string> = {
unconfirmed: removeMessage,
doing: "",
done: "This item has been successfully removed from workspace.",
failed: "Failed to remove item. Please contact the administrator to delete this item."
}

return <ConfirmModal
handler={removeHandler}
isOpen={isOpen}
setModalOpen={setModalOpen}
status={removeStatus}
modalTitle={modalTitle}
modalMessage={modalMessage}/>
}

return null;
}

function ConfirmModal({ handler, isOpen, setModalOpen, status, modalTitle, modalMessage }:
{ handler: () => void,
isOpen: boolean,
setModalOpen: (value: boolean) => void,
status: ModalStatus,
modalTitle: Record<ModalStatus, string>,
modalMessage: Record<ModalStatus, string> }) {

const onDismiss = () => {
setModalOpen(false);
}

const handle = () => {
try {
handler();
}
catch (e){
console.error("Error handling item", e);
}
}

const spinner = status === "doing" ? <ProgressAnimation /> : false;

return <React.Fragment>
<Modal
isOpen={isOpen}
isDismissable={true}
dismiss={onDismiss}
>
<div className="form form-full-width">
<h2 className='modal__title'>
{modalTitle[status]}
</h2>
<div className='form__row'>
{modalMessage[status]}
</div>
<div className='form__row btn-group btn-group--left'>
{ status === "unconfirmed" &&
<>
<button className="btn" onClick={onDismiss}>Cancel</button>
<button className="btn" onClick={handle}>Proceed</button>
</>
}
{ status === "done" &&
<>
<button className="btn" onClick={()=>document.location.href="/"}>
Giant Home
</button>
<button className="btn" onClick={onDismiss}>Close</button>
</>
}
{ status === "failed" &&
<button className="btn" onClick={onDismiss}>Cancel</button>
}
{spinner}
</div>
</div>
</Modal>
</React.Fragment>
;
}

function mapStateToProps(state: GiantState) {
return {
resource: state.resource,
};
}

export default connect(mapStateToProps)(DeleteModal);
89 changes: 0 additions & 89 deletions frontend/src/js/components/workspace/DeleteModal.tsx

This file was deleted.

60 changes: 53 additions & 7 deletions frontend/src/js/components/workspace/Workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { processingStageToString, workspaceHasProcessingFiles } from '../../util
import { setWorkspaceIsPublic } from '../../actions/workspaces/setWorkspaceIsPublic';
import { RouteComponentProps } from 'react-router-dom';
import { reprocessBlob } from '../../actions/workspaces/reprocessBlob';
import { DeleteModal, DeleteStatus } from './DeleteModal';
import { DeleteModal, ModalStatus, RemoveFromWorkspaceModal } from './ConfirmModal';
import { PartialUser } from '../../types/User';
import { getMyPermissions } from '../../actions/users/getMyPermissions';
import buildLink from '../../util/buildLink';
Expand All @@ -71,7 +71,12 @@ type State = {
deleteModalContext: {
isOpen: boolean,
entry: null | TreeEntry<WorkspaceEntry>,
status: DeleteStatus,
status: ModalStatus,
},
removeFromWorkspaceModalContext: {
isOpen: boolean,
entry: null | TreeEntry<WorkspaceEntry>,
status: ModalStatus,
}
}

Expand Down Expand Up @@ -298,6 +303,11 @@ class WorkspacesUnconnected extends React.Component<Props, State> {
isOpen: false,
entry: null,
status: "unconfirmed",
},
removeFromWorkspaceModalContext: {
isOpen: false,
entry: null,
status: "unconfirmed",
}
};

Expand Down Expand Up @@ -433,23 +443,46 @@ class WorkspacesUnconnected extends React.Component<Props, State> {
this.setState({deleteModalContext: {entry: null, isOpen, status: "unconfirmed"}});
}

onRemoveFromWorkspaceModalOpen = (isOpen: boolean) => {
if (isOpen)
this.setState({removeFromWorkspaceModalContext: {entry: this.state.removeFromWorkspaceModalContext.entry, isOpen, status: "unconfirmed"}});
else
this.setState({removeFromWorkspaceModalContext: {entry: null, isOpen, status: "unconfirmed"}});
}

onDeleteCompleteHandler = (isSuccess: boolean) => {
const modalContext = this.state.deleteModalContext;
if (modalContext.isOpen) {
const status = isSuccess ? "deleted" : "failed"
const status = isSuccess ? "done" : "failed"
this.setState({deleteModalContext: {...modalContext, status}});
}
}
onRemoveCompleteHandler = (isSuccess: boolean) => {
const modalContext = this.state.removeFromWorkspaceModalContext;
if (modalContext.isOpen) {
const status = isSuccess ? "done" : "failed"
this.setState({removeFromWorkspaceModalContext: {...modalContext, status}});
}
}

onDeleteItem = (workspaceId: string, entry: TreeEntry<WorkspaceEntry> | null) => () => {
if (entry && isWorkspaceLeaf(entry.data)) {
const deleteContext = this.state.deleteModalContext;
this.setState({deleteModalContext: {...deleteContext, status: "deleting"}});
this.setState({deleteModalContext: {...deleteContext, status: "doing"}});
this.props.deleteResourceFromWorkspace(workspaceId, entry.data.uri, this.onDeleteCompleteHandler);
this.props.resetFocusedAndSelectedEntries();
}
}

onRemoveFromWorkspace = (workspaceId: string, entry: TreeEntry<WorkspaceEntry> | null) => () => {
if (entry) {
const removeFromWorkspaceModalContext = this.state.removeFromWorkspaceModalContext;
this.setState({removeFromWorkspaceModalContext: {...removeFromWorkspaceModalContext, status: "doing"}});
this.props.deleteItem(workspaceId, entry.id, this.onRemoveCompleteHandler);
this.props.resetFocusedAndSelectedEntries();
}
}

onClickColumn = (column: string) => {
if (this.state.columnsConfig.sortColumn === column) {

Expand Down Expand Up @@ -570,8 +603,13 @@ class WorkspacesUnconnected extends React.Component<Props, State> {
}

if (menuItemProps.content === 'Remove from workspace') {
this.props.deleteItem(workspaceId, entry.id);
this.props.resetFocusedAndSelectedEntries();
this.setState({
removeFromWorkspaceModalContext: {
isOpen: true,
entry,
status: "unconfirmed",
}
});
}

if (menuItemProps.content === copyFilenameContent || menuItemProps.content === copyFilePathContent) {
Expand Down Expand Up @@ -727,7 +765,15 @@ class WorkspacesUnconnected extends React.Component<Props, State> {
isOpen={this.state.deleteModalContext.isOpen}
setModalOpen={this.onDeleteModalOpen}
deleteStatus={this.state.deleteModalContext.status}
/>
entry={this.state.deleteModalContext.entry}
/>
<RemoveFromWorkspaceModal
removeHandler={this.onRemoveFromWorkspace(this.props.match.params.id, this.state.removeFromWorkspaceModalContext.entry)}
isOpen={this.state.removeFromWorkspaceModalContext.isOpen}
setModalOpen={this.onRemoveFromWorkspaceModalOpen}
removeStatus={this.state.removeFromWorkspaceModalContext.status}
entry={this.state.removeFromWorkspaceModalContext.entry}
/>
</div>
);
}
Expand Down