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 modal confirmation when deleting file #497

Merged
merged 5 commits into from
Dec 7, 2021
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
39 changes: 39 additions & 0 deletions src/fileManager/_components/DeleteConfirmModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';

import ModalConfirm from '../../ui/_components/ModalConfirm';

function DeleteConfirmModal(props) {
const { selected, deleteFile, isDeleting, setIsDeleting } = props;

if (!isDeleting) {
return null;
}

const cancelDelete = () => setIsDeleting(false);
const confirmDelete = () => {
setIsDeleting(false);
deleteFile(selected);
};

return (
<ModalConfirm
confirmAction={confirmDelete}
confirmTitle={'DELETE'}
cancelAction={cancelDelete}
>
Are you sure you want to delete this file?
<br />
This action cannot be undone.
</ModalConfirm>
);
}

DeleteConfirmModal.propTypes = {
deleteFile: PropTypes.func.isRequired,
isDeleting: PropTypes.bool.isRequired,
selected: PropTypes.string.isRequired,
setIsDeleting: PropTypes.func.isRequired,
};

export default DeleteConfirmModal;
12 changes: 6 additions & 6 deletions src/fileManager/_components/FileActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ function FileActions(props) {
action: deleteFile,
isDisabled: !selected,
},
{
icon: 'download',
text: 'Export',
action: exportAsText,
isDisabled: !selected,
},
{
icon: 'print',
text: 'Print',
action: printFile,
isDisabled: !selected,
},
{
icon: 'download',
text: 'Export',
action: exportAsText,
isDisabled: !selected,
},
];

return (
Expand Down
13 changes: 11 additions & 2 deletions src/fileManager/_components/FileManager.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import './FileManager.scss';

import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import exportSelectedFileAsText from '../exportSelectedFileAsText';

import DeleteConfirmModal from './DeleteConfirmModal';
import Icon from '../../ui/_components/Icon';
import FileActions from './FileActions';
import FileEntry from './FileEntry';

function FileManager(props) {
const [isDeleting, setIsDeleting] = useState(false);

const {
allTitles,
selected,
Expand All @@ -27,6 +30,12 @@ function FileManager(props) {

return (
<div className={'fileManager'}>
<DeleteConfirmModal
deleteFile={deleteFile}
isDeleting={isDeleting}
selected={selected}
setIsDeleting={setIsDeleting}
/>
<div className={'fileManager-isCollapsed'}>
<span className={'fileManager-icon'}>
<Icon iconName={'file_copy'} />
Expand All @@ -37,7 +46,7 @@ function FileManager(props) {
<FileActions
selected={selected}
createFile={() => createFile(defaultTitle)}
deleteFile={() => deleteFile(selected)}
deleteFile={() => setIsDeleting(true)}
enableRename={() => enableRename(selected)}
startImport={() => startImport()}
exportAsText={() => {
Expand Down
1 change: 0 additions & 1 deletion src/songImporter/_themes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ $themes: (
modal-bg: cv(dark-secondary, dark5),
modal-border: cv(dark-foreground, dark20),
modal-txt: cv(dark-foreground),
//modal-txtDisabled: cv(dark-foreground, dark20),
input-bg: cv(dark-secondary, dark10),
input-txt: cv(dark-foreground),
inputHeader-txt: cv(dark-foreground, fade),
Expand Down
8 changes: 5 additions & 3 deletions src/state/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ export function createStore() {
const persistedState = loadState();

// store migrations
delete persistedState.db.options.rendering; // remove old options before the options refactor in v0.9.0

if (persistedState && persistedState.db && persistedState.db.options) {
delete persistedState.db.options.rendering; // remove old options before the options refactor in v0.9.0
}

/* Reset all options * /
Object.keys(persistedState.db.files.allFiles).forEach((fileId) => {
delete persistedState.db.files.allFiles[fileId].options;
});
delete persistedState.db.options;
/**/
/* reset song Importer state * /
/* misc * /
delete persistedState.songImporter;
delete persistedState.fileManager.selected;
/**/
Expand Down
38 changes: 37 additions & 1 deletion src/ui/_components/Modal.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
@import '../../../scss/abstract';
@import '../layout/app/themes';

$themes: (
light: (),
dark: (
modal-bg: cv(dark-secondary, dark5),
modal-border: cv(dark-foreground, dark20),
modal-txt: cv(dark-foreground),
),
);

.mod-ModalContainer {
z-index: $zindex-modal;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
}

.mod-Overlay {
Expand All @@ -17,3 +30,26 @@

.mod-ContentContainer {
}

.mod-ModalConfirmContainer {
z-index: $zindex-modal;

position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: auto;
height: auto;

padding: 10px 20px;

@include themify($themes) {
background-color: themed('modal-bg');
border: 1px solid themed('modal-border');
color: themed('modal-txt');
}
}

.mod-ModalConfirmButtons {
text-align: center;
}
49 changes: 49 additions & 0 deletions src/ui/_components/ModalConfirm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';

import Button from './Button';
import Modal from './Modal';

function ModalConfirm(props) {
const {
confirmAction,
confirmTitle = 'OK',
cancelAction,
cancelTitle = 'CANCEL',
children,
} = props;

return (
<Modal closeModal={cancelAction}>
<section className={'mod-ModalConfirmContainer'}>
<div className={'mod-ModalConfirmMessage'}>{children}</div>
<div className={'mod-ModalConfirmButtons'}>
<Button
onClick={cancelAction}
type={'secondary'}
buttonName={'cancel'}
>
{cancelTitle}
</Button>
<Button
onClick={confirmAction}
type={'primary'}
buttonName={'confirm'}
>
{confirmTitle}
</Button>
</div>
</section>
</Modal>
);
}

ModalConfirm.propTypes = {
confirmAction: PropTypes.func.isRequired,
confirmTitle: PropTypes.string,
cancelAction: PropTypes.func.isRequired,
cancelTitle: PropTypes.string,
children: PropTypes.node.isRequired,
};

export default ModalConfirm;
5 changes: 5 additions & 0 deletions src/ui/layout/app/_themes.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
$themes: (
light: (),
dark: (
// leftbar
leftBar-txt: cv(dark-foreground),
leftBar-bg: cv(dark-background),
leftBar-bgHover: cv(dark-background, light10),
Expand All @@ -9,6 +10,7 @@ $themes: (
leftBar-collapser-bg: cv(dark-primary, light10),
leftBar-collapser-bgHover: cv(dark-primary, light20),
leftBar-collapser-border: cv(dark-foreground, dark30),
// header
header-txt: cv(dark-foreground, fade),
header-txtHover: cv(dark-foreground),
header-txtActive: cv(dark-foreground),
Expand All @@ -18,9 +20,11 @@ $themes: (
header-bgActive: cv(dark-background, dark10),
header-bgDisabled: cv(dark-background),
header-border: cv(dark-background, dark10),
// footer
footer-txt: cv(dark-foreground, fade),
footer-bg: cv(dark-background),
footer-border: cv(dark-secondary, dark20),
// rightbar
rightBar-txt: cv(dark-foreground),
rightBar-bg: cv(dark-background),
rightBar-bgHover: cv(dark-background, light10),
Expand All @@ -29,5 +33,6 @@ $themes: (
rightBar-collapser-bg: cv(dark-primary, light10),
rightBar-collapser-bgHover: cv(dark-primary, light20),
rightBar-collapser-border: cv(dark-foreground, dark30),
//
),
);
6 changes: 6 additions & 0 deletions tests/integration/fileManager/_containers/FileManager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ describe('FileManager', () => {
fireEvent.click(deleteFileBtn);
});

const confirmBtn = getByText('DELETE');

act(() => {
fireEvent.click(confirmBtn);
});

// Check file has been removed
allFiles = fileSelectors.getAllTitles(getState());
expect(allFiles.length).toBe(0);
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/fileManager/_components/FileManager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,16 +329,40 @@ describe('FileManager', () => {
});

describe('deleteFile', () => {
test('should call deleteFile() on Delete Action click with selected id', () => {
test('should ask for confirmation before deleting a file', () => {
const { getByText } = render(
<FileManager {...props} selected={props.allTitles[2].id} />
);
const input = getByText('Delete');

fireEvent.click(input);

expect(deleteFile).toHaveBeenCalledTimes(0);

const deleteConfirmBtn = getByText('DELETE');

fireEvent.click(deleteConfirmBtn);

expect(deleteFile).toHaveBeenCalledTimes(1);
expect(deleteFile).toHaveBeenCalledWith(props.allTitles[2].id);
});

test('should allow to cancel file deletion', () => {
const { getByText } = render(
<FileManager {...props} selected={props.allTitles[2].id} />
);
const input = getByText('Delete');

fireEvent.click(input);

expect(deleteFile).toHaveBeenCalledTimes(0);

const deleteConfirmBtn = getByText('CANCEL');

fireEvent.click(deleteConfirmBtn);

expect(deleteFile).toHaveBeenCalledTimes(0);
});
});

describe('import', () => {
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/state/store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ describe('localStorage persistence', () => {
);
});
});

describe.each([
/* */
['non existant store', undefined],
['empty object', {}],
['empty db', { db: {} }],
['empty option', { db: { options: {} } }],
['with rendering options', { db: { options: { rendering: {} } } }],
/**/
])('migration: %s', (title, state) => {
test('should remove rendering options', () => {
localStorage.__STORE__.state = JSON.stringify(state);

reducers.mockImplementation((initialState) => initialState);

createStore();
const actualState = getStore().getState();

expect(actualState.db.options.rendering).toBeUndefined();
});
});
Loading