Mass actions for moving files, mostly working?

This commit is contained in:
Dane Everitt 2020-07-11 16:47:13 -07:00
parent 121f163b81
commit 80ecd58b30
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
6 changed files with 66 additions and 41 deletions

View file

@ -15,7 +15,6 @@ import Button from '@/components/elements/Button';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import useFileManagerSwr from '@/plugins/useFileManagerSwr';
import { Form, Formik } from 'formik';
import MassActionsBar from '@/components/server/files/MassActionsBar'; import MassActionsBar from '@/components/server/files/MassActionsBar';
const sortFiles = (files: FileObject[]): FileObject[] => { const sortFiles = (files: FileObject[]): FileObject[] => {
@ -28,12 +27,14 @@ export default () => {
const { hash } = useLocation(); const { hash } = useLocation();
const { data: files, error, mutate } = useFileManagerSwr(); const { data: files, error, mutate } = useFileManagerSwr();
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
useEffect(() => { useEffect(() => {
// We won't automatically mutate the store when the component re-mounts, otherwise because of // We won't automatically mutate the store when the component re-mounts, otherwise because of
// my (horrible) programming this fires off way more than we intend it to. // my (horrible) programming this fires off way more than we intend it to.
mutate(); mutate();
setSelectedFiles([]);
setDirectory(hash.length > 0 ? hash : '/'); setDirectory(hash.length > 0 ? hash : '/');
}, [ hash ]); }, [ hash ]);
@ -58,11 +59,6 @@ export default () => {
: :
<CSSTransition classNames={'fade'} timeout={150} appear in> <CSSTransition classNames={'fade'} timeout={150} appear in>
<div> <div>
<Formik
onSubmit={() => undefined}
initialValues={{ selectedFiles: [] }}
>
<Form>
{files.length > 250 && {files.length > 250 &&
<div css={tw`rounded bg-yellow-400 mb-px p-3`}> <div css={tw`rounded bg-yellow-400 mb-px p-3`}>
<p css={tw`text-yellow-900 text-sm text-center`}> <p css={tw`text-yellow-900 text-sm text-center`}>
@ -77,8 +73,6 @@ export default () => {
)) ))
} }
<MassActionsBar/> <MassActionsBar/>
</Form>
</Formik>
</div> </div>
</CSSTransition> </CSSTransition>
} }

View file

@ -10,13 +10,13 @@ import { NavLink, useHistory, useRouteMatch } from 'react-router-dom';
import tw from 'twin.macro'; import tw from 'twin.macro';
import isEqual from 'react-fast-compare'; import isEqual from 'react-fast-compare';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import FormikCheckbox from '@/components/elements/Checkbox'; import Input from '@/components/elements/Input';
const Row = styled.div` const Row = styled.div`
${tw`flex bg-neutral-700 rounded-sm mb-px text-sm hover:text-neutral-100 cursor-pointer items-center no-underline hover:bg-neutral-600`}; ${tw`flex bg-neutral-700 rounded-sm mb-px text-sm hover:text-neutral-100 cursor-pointer items-center no-underline hover:bg-neutral-600`};
`; `;
const Checkbox = styled(FormikCheckbox)` const Checkbox = styled(Input)`
&& { && {
${tw`border-neutral-500`}; ${tw`border-neutral-500`};
@ -28,7 +28,9 @@ const Checkbox = styled(FormikCheckbox)`
const FileObjectRow = ({ file }: { file: FileObject }) => { const FileObjectRow = ({ file }: { file: FileObject }) => {
const directory = ServerContext.useStoreState(state => state.files.directory); const directory = ServerContext.useStoreState(state => state.files.directory);
const selectedFiles = ServerContext.useStoreState(state => state.files.selectedFiles);
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory); const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
const history = useHistory(); const history = useHistory();
const match = useRouteMatch(); const match = useRouteMatch();
@ -56,7 +58,19 @@ const FileObjectRow = ({ file }: { file: FileObject }) => {
}} }}
> >
<label css={tw`flex-none p-4 absolute self-center z-30 cursor-pointer`}> <label css={tw`flex-none p-4 absolute self-center z-30 cursor-pointer`}>
<Checkbox name={'selectedFiles'} value={file.name}/> <Checkbox
name={'selectedFiles'}
value={file.name}
checked={selectedFiles.indexOf(file.name) >= 0}
type={'checkbox'}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.currentTarget.checked) {
setSelectedFiles(selectedFiles.filter(f => f !== file.name).concat(file.name));
} else {
setSelectedFiles(selectedFiles.filter(f => f !== file.name));
}
}}
/>
</label> </label>
<NavLink <NavLink
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`} to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}

View file

@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import tw from 'twin.macro'; import tw from 'twin.macro';
import Button from '@/components/elements/Button'; import Button from '@/components/elements/Button';
import { useFormikContext } from 'formik';
import Fade from '@/components/elements/Fade'; import Fade from '@/components/elements/Fade';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFileArchive, faLevelUpAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { faFileArchive, faLevelUpAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
@ -13,6 +12,7 @@ import useServer from '@/plugins/useServer';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import ConfirmationModal from '@/components/elements/ConfirmationModal'; import ConfirmationModal from '@/components/elements/ConfirmationModal';
import deleteFiles from '@/api/server/files/deleteFiles'; import deleteFiles from '@/api/server/files/deleteFiles';
import RenameFileModal from '@/components/server/files/RenameFileModal';
const MassActionsBar = () => { const MassActionsBar = () => {
const { uuid } = useServer(); const { uuid } = useServer();
@ -21,9 +21,12 @@ const MassActionsBar = () => {
const [ loading, setLoading ] = useState(false); const [ loading, setLoading ] = useState(false);
const [ loadingMessage, setLoadingMessage ] = useState(''); const [ loadingMessage, setLoadingMessage ] = useState('');
const [ showConfirm, setShowConfirm ] = useState(false); const [ showConfirm, setShowConfirm ] = useState(false);
const { values, setFieldValue } = useFormikContext<{ selectedFiles: string[] }>(); const [ showMove, setShowMove ] = useState(false);
const directory = ServerContext.useStoreState(state => state.files.directory); const directory = ServerContext.useStoreState(state => state.files.directory);
const selectedFiles = ServerContext.useStoreState(state => state.files.selectedFiles);
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
useEffect(() => { useEffect(() => {
if (!loading) setLoadingMessage(''); if (!loading) setLoadingMessage('');
}, [ loading ]); }, [ loading ]);
@ -33,9 +36,9 @@ const MassActionsBar = () => {
clearFlashes('files'); clearFlashes('files');
setLoadingMessage('Archiving files...'); setLoadingMessage('Archiving files...');
compressFiles(uuid, directory, values.selectedFiles) compressFiles(uuid, directory, selectedFiles)
.then(() => mutate()) .then(() => mutate())
.then(() => setFieldValue('selectedFiles', [])) .then(() => setSelectedFiles([]))
.catch(error => clearAndAddHttpError({ key: 'files', error })) .catch(error => clearAndAddHttpError({ key: 'files', error }))
.then(() => setLoading(false)); .then(() => setLoading(false));
}; };
@ -46,10 +49,10 @@ const MassActionsBar = () => {
clearFlashes('files'); clearFlashes('files');
setLoadingMessage('Deleting files...'); setLoadingMessage('Deleting files...');
deleteFiles(uuid, directory, values.selectedFiles) deleteFiles(uuid, directory, selectedFiles)
.then(() => { .then(() => {
mutate(files => files.filter(f => values.selectedFiles.indexOf(f.name) < 0), false); mutate(files => files.filter(f => selectedFiles.indexOf(f.name) < 0), false);
setFieldValue('selectedFiles', []); setSelectedFiles([]);
}) })
.catch(error => { .catch(error => {
mutate(); mutate();
@ -59,7 +62,7 @@ const MassActionsBar = () => {
}; };
return ( return (
<Fade timeout={75} in={values.selectedFiles.length > 0} unmountOnExit> <Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
<div css={tw`fixed bottom-0 z-50 left-0 right-0 flex justify-center`}> <div css={tw`fixed bottom-0 z-50 left-0 right-0 flex justify-center`}>
<SpinnerOverlay visible={loading} size={'large'} fixed> <SpinnerOverlay visible={loading} size={'large'} fixed>
{loadingMessage} {loadingMessage}
@ -73,15 +76,17 @@ const MassActionsBar = () => {
> >
Deleting files is a permanent operation, you cannot undo this action. Deleting files is a permanent operation, you cannot undo this action.
</ConfirmationModal> </ConfirmationModal>
<RenameFileModal
files={selectedFiles}
visible={showMove}
useMoveTerminology
onDismissed={() => setShowMove(false)}
/>
<div css={tw`rounded p-4 mb-6`} style={{ background: 'rgba(0, 0, 0, 0.35)' }}> <div css={tw`rounded p-4 mb-6`} style={{ background: 'rgba(0, 0, 0, 0.35)' }}>
<Button size={'xsmall'} css={tw`mr-4`}> <Button size={'xsmall'} css={tw`mr-4`} onClick={() => setShowMove(true)}>
<FontAwesomeIcon icon={faLevelUpAlt} css={tw`mr-2`}/> Move <FontAwesomeIcon icon={faLevelUpAlt} css={tw`mr-2`}/> Move
</Button> </Button>
<Button <Button size={'xsmall'} css={tw`mr-4`} onClick={onClickCompress}>
size={'xsmall'}
css={tw`mr-4`}
onClick={onClickCompress}
>
<FontAwesomeIcon icon={faFileArchive} css={tw`mr-2`}/> Archive <FontAwesomeIcon icon={faFileArchive} css={tw`mr-2`}/> Archive
</Button> </Button>
<Button size={'xsmall'} color={'red'} isSecondary onClick={() => setShowConfirm(true)}> <Button size={'xsmall'} color={'red'} isSecondary onClick={() => setShowConfirm(true)}>

View file

@ -22,6 +22,7 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
const { mutate } = useFileManagerSwr(); const { mutate } = useFileManagerSwr();
const { clearFlashes, clearAndAddHttpError } = useFlash(); const { clearFlashes, clearAndAddHttpError } = useFlash();
const directory = ServerContext.useStoreState(state => state.files.directory); const directory = ServerContext.useStoreState(state => state.files.directory);
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers<FormikValues>) => { const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers<FormikValues>) => {
clearFlashes('files'); clearFlashes('files');
@ -45,12 +46,14 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
} }
renameFiles(uuid, directory, data) renameFiles(uuid, directory, data)
.then(() => props.onDismissed()) .then((): Promise<any> => files.length > 0 ? mutate() : Promise.resolve())
.then(() => setSelectedFiles([]))
.catch(error => { .catch(error => {
mutate(); mutate();
setSubmitting(false); setSubmitting(false);
clearAndAddHttpError({ key: 'files', error }); clearAndAddHttpError({ key: 'files', error });
}); })
.then(() => props.onDismissed());
}; };
return ( return (

View file

@ -3,15 +3,23 @@ import { cleanDirectoryPath } from '@/helpers';
export interface ServerFileStore { export interface ServerFileStore {
directory: string; directory: string;
selectedFiles: string[];
setDirectory: Action<ServerFileStore, string>; setDirectory: Action<ServerFileStore, string>;
setSelectedFiles: Action<ServerFileStore, string[]>;
} }
const files: ServerFileStore = { const files: ServerFileStore = {
directory: '/', directory: '/',
selectedFiles: [],
setDirectory: action((state, payload) => { setDirectory: action((state, payload) => {
state.directory = cleanDirectoryPath(payload); state.directory = cleanDirectoryPath(payload);
}), }),
setSelectedFiles: action((state, payload) => {
state.selectedFiles = payload;
}),
}; };
export default files; export default files;

View file

@ -77,6 +77,7 @@ export const ServerContext = createContextStore<ServerStore>({
state.databases.data = []; state.databases.data = [];
state.subusers.data = []; state.subusers.data = [];
state.files.directory = '/'; state.files.directory = '/';
state.files.selectedFiles = [];
state.backups.data = []; state.backups.data = [];
state.schedules.data = []; state.schedules.data = [];