Mass actions for moving files, mostly working?
This commit is contained in:
parent
121f163b81
commit
80ecd58b30
6 changed files with 66 additions and 41 deletions
|
@ -15,7 +15,6 @@ import Button from '@/components/elements/Button';
|
|||
import useServer from '@/plugins/useServer';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||
import { Form, Formik } from 'formik';
|
||||
import MassActionsBar from '@/components/server/files/MassActionsBar';
|
||||
|
||||
const sortFiles = (files: FileObject[]): FileObject[] => {
|
||||
|
@ -28,12 +27,14 @@ export default () => {
|
|||
const { hash } = useLocation();
|
||||
const { data: files, error, mutate } = useFileManagerSwr();
|
||||
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
|
||||
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
|
||||
|
||||
useEffect(() => {
|
||||
// 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.
|
||||
mutate();
|
||||
|
||||
setSelectedFiles([]);
|
||||
setDirectory(hash.length > 0 ? hash : '/');
|
||||
}, [ hash ]);
|
||||
|
||||
|
@ -58,27 +59,20 @@ export default () => {
|
|||
:
|
||||
<CSSTransition classNames={'fade'} timeout={150} appear in>
|
||||
<div>
|
||||
<Formik
|
||||
onSubmit={() => undefined}
|
||||
initialValues={{ selectedFiles: [] }}
|
||||
>
|
||||
<Form>
|
||||
{files.length > 250 &&
|
||||
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
|
||||
<p css={tw`text-yellow-900 text-sm text-center`}>
|
||||
This directory is too large to display in the browser,
|
||||
limiting the output to the first 250 files.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
sortFiles(files.slice(0, 250)).map(file => (
|
||||
<FileObjectRow key={file.uuid} file={file}/>
|
||||
))
|
||||
}
|
||||
<MassActionsBar/>
|
||||
</Form>
|
||||
</Formik>
|
||||
{files.length > 250 &&
|
||||
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
|
||||
<p css={tw`text-yellow-900 text-sm text-center`}>
|
||||
This directory is too large to display in the browser,
|
||||
limiting the output to the first 250 files.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
sortFiles(files.slice(0, 250)).map(file => (
|
||||
<FileObjectRow key={file.uuid} file={file}/>
|
||||
))
|
||||
}
|
||||
<MassActionsBar/>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import { NavLink, useHistory, useRouteMatch } from 'react-router-dom';
|
|||
import tw from 'twin.macro';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import styled from 'styled-components/macro';
|
||||
import FormikCheckbox from '@/components/elements/Checkbox';
|
||||
import Input from '@/components/elements/Input';
|
||||
|
||||
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`};
|
||||
`;
|
||||
|
||||
const Checkbox = styled(FormikCheckbox)`
|
||||
const Checkbox = styled(Input)`
|
||||
&& {
|
||||
${tw`border-neutral-500`};
|
||||
|
||||
|
@ -28,7 +28,9 @@ const Checkbox = styled(FormikCheckbox)`
|
|||
|
||||
const FileObjectRow = ({ file }: { file: FileObject }) => {
|
||||
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 setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
|
||||
|
||||
const history = useHistory();
|
||||
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`}>
|
||||
<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>
|
||||
<NavLink
|
||||
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import { useFormikContext } from 'formik';
|
||||
import Fade from '@/components/elements/Fade';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
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 ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
import deleteFiles from '@/api/server/files/deleteFiles';
|
||||
import RenameFileModal from '@/components/server/files/RenameFileModal';
|
||||
|
||||
const MassActionsBar = () => {
|
||||
const { uuid } = useServer();
|
||||
|
@ -21,9 +21,12 @@ const MassActionsBar = () => {
|
|||
const [ loading, setLoading ] = useState(false);
|
||||
const [ loadingMessage, setLoadingMessage ] = useState('');
|
||||
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 selectedFiles = ServerContext.useStoreState(state => state.files.selectedFiles);
|
||||
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) setLoadingMessage('');
|
||||
}, [ loading ]);
|
||||
|
@ -33,9 +36,9 @@ const MassActionsBar = () => {
|
|||
clearFlashes('files');
|
||||
setLoadingMessage('Archiving files...');
|
||||
|
||||
compressFiles(uuid, directory, values.selectedFiles)
|
||||
compressFiles(uuid, directory, selectedFiles)
|
||||
.then(() => mutate())
|
||||
.then(() => setFieldValue('selectedFiles', []))
|
||||
.then(() => setSelectedFiles([]))
|
||||
.catch(error => clearAndAddHttpError({ key: 'files', error }))
|
||||
.then(() => setLoading(false));
|
||||
};
|
||||
|
@ -46,10 +49,10 @@ const MassActionsBar = () => {
|
|||
clearFlashes('files');
|
||||
setLoadingMessage('Deleting files...');
|
||||
|
||||
deleteFiles(uuid, directory, values.selectedFiles)
|
||||
deleteFiles(uuid, directory, selectedFiles)
|
||||
.then(() => {
|
||||
mutate(files => files.filter(f => values.selectedFiles.indexOf(f.name) < 0), false);
|
||||
setFieldValue('selectedFiles', []);
|
||||
mutate(files => files.filter(f => selectedFiles.indexOf(f.name) < 0), false);
|
||||
setSelectedFiles([]);
|
||||
})
|
||||
.catch(error => {
|
||||
mutate();
|
||||
|
@ -59,7 +62,7 @@ const MassActionsBar = () => {
|
|||
};
|
||||
|
||||
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`}>
|
||||
<SpinnerOverlay visible={loading} size={'large'} fixed>
|
||||
{loadingMessage}
|
||||
|
@ -73,15 +76,17 @@ const MassActionsBar = () => {
|
|||
>
|
||||
Deleting files is a permanent operation, you cannot undo this action.
|
||||
</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)' }}>
|
||||
<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
|
||||
</Button>
|
||||
<Button
|
||||
size={'xsmall'}
|
||||
css={tw`mr-4`}
|
||||
onClick={onClickCompress}
|
||||
>
|
||||
<Button size={'xsmall'} css={tw`mr-4`} onClick={onClickCompress}>
|
||||
<FontAwesomeIcon icon={faFileArchive} css={tw`mr-2`}/> Archive
|
||||
</Button>
|
||||
<Button size={'xsmall'} color={'red'} isSecondary onClick={() => setShowConfirm(true)}>
|
||||
|
|
|
@ -22,6 +22,7 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
|
|||
const { mutate } = useFileManagerSwr();
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
||||
const setSelectedFiles = ServerContext.useStoreActions(actions => actions.files.setSelectedFiles);
|
||||
|
||||
const submit = ({ name }: FormikValues, { setSubmitting }: FormikHelpers<FormikValues>) => {
|
||||
clearFlashes('files');
|
||||
|
@ -45,12 +46,14 @@ export default ({ files, useMoveTerminology, ...props }: Props) => {
|
|||
}
|
||||
|
||||
renameFiles(uuid, directory, data)
|
||||
.then(() => props.onDismissed())
|
||||
.then((): Promise<any> => files.length > 0 ? mutate() : Promise.resolve())
|
||||
.then(() => setSelectedFiles([]))
|
||||
.catch(error => {
|
||||
mutate();
|
||||
setSubmitting(false);
|
||||
clearAndAddHttpError({ key: 'files', error });
|
||||
});
|
||||
})
|
||||
.then(() => props.onDismissed());
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,15 +3,23 @@ import { cleanDirectoryPath } from '@/helpers';
|
|||
|
||||
export interface ServerFileStore {
|
||||
directory: string;
|
||||
selectedFiles: string[];
|
||||
|
||||
setDirectory: Action<ServerFileStore, string>;
|
||||
setSelectedFiles: Action<ServerFileStore, string[]>;
|
||||
}
|
||||
|
||||
const files: ServerFileStore = {
|
||||
directory: '/',
|
||||
selectedFiles: [],
|
||||
|
||||
setDirectory: action((state, payload) => {
|
||||
state.directory = cleanDirectoryPath(payload);
|
||||
}),
|
||||
|
||||
setSelectedFiles: action((state, payload) => {
|
||||
state.selectedFiles = payload;
|
||||
}),
|
||||
};
|
||||
|
||||
export default files;
|
||||
|
|
|
@ -77,6 +77,7 @@ export const ServerContext = createContextStore<ServerStore>({
|
|||
state.databases.data = [];
|
||||
state.subusers.data = [];
|
||||
state.files.directory = '/';
|
||||
state.files.selectedFiles = [];
|
||||
state.backups.data = [];
|
||||
state.schedules.data = [];
|
||||
|
||||
|
|
Loading…
Reference in a new issue