misc_pterodactyl-panel/resources/scripts/components/server/files/MassActionsBar.tsx

113 lines
4.8 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import tw from 'twin.macro';
import { Button } from '@/components/elements/button/index';
import Fade from '@/components/elements/Fade';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
import useFlash from '@/plugins/useFlash';
import compressFiles from '@/api/server/files/compressFiles';
import { ServerContext } from '@/state/server';
import deleteFiles from '@/api/server/files/deleteFiles';
import RenameFileModal from '@/components/server/files/RenameFileModal';
import Portal from '@/components/elements/Portal';
import { Dialog } from '@/components/elements/dialog';
const MassActionsBar = () => {
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const { mutate } = useFileManagerSwr();
const { clearFlashes, clearAndAddHttpError } = useFlash();
const [loading, setLoading] = useState(false);
const [loadingMessage, setLoadingMessage] = useState('');
const [showConfirm, setShowConfirm] = useState(false);
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]);
const onClickCompress = () => {
setLoading(true);
clearFlashes('files');
setLoadingMessage('Archiving files...');
compressFiles(uuid, directory, selectedFiles)
.then(() => mutate())
.then(() => setSelectedFiles([]))
.catch((error) => clearAndAddHttpError({ key: 'files', error }))
.then(() => setLoading(false));
};
const onClickConfirmDeletion = () => {
setLoading(true);
setShowConfirm(false);
clearFlashes('files');
setLoadingMessage('Deleting files...');
deleteFiles(uuid, directory, selectedFiles)
.then(() => {
mutate((files) => files.filter((f) => selectedFiles.indexOf(f.name) < 0), false);
setSelectedFiles([]);
})
.catch((error) => {
mutate();
clearAndAddHttpError({ key: 'files', error });
})
.then(() => setLoading(false));
};
return (
<>
<div css={tw`pointer-events-none fixed bottom-0 z-20 left-0 right-0 flex justify-center`}>
<SpinnerOverlay visible={loading} size={'large'} fixed>
{loadingMessage}
</SpinnerOverlay>
<Dialog.Confirm
title={'Delete Files'}
open={showConfirm}
confirm={'Delete'}
onClose={() => setShowConfirm(false)}
onConfirmed={onClickConfirmDeletion}
>
<p className={'mb-2'}>
Are you sure you want to delete&nbsp;
<span className={'font-semibold text-gray-50'}>{selectedFiles.length} files</span>? This is a
permanent action and the files cannot be recovered.
</p>
{selectedFiles.slice(0, 15).map((file) => (
<li key={file}>{file}</li>
))}
{selectedFiles.length > 15 && <li>and {selectedFiles.length - 15} others</li>}
</Dialog.Confirm>
{showMove && (
<RenameFileModal
files={selectedFiles}
visible
appear
useMoveTerminology
onDismissed={() => setShowMove(false)}
/>
)}
<Portal>
<div className={'pointer-events-none fixed bottom-0 mb-6 flex justify-center w-full z-50'}>
<Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
<div css={tw`flex items-center space-x-4 pointer-events-auto rounded p-4 bg-black/50`}>
<Button onClick={() => setShowMove(true)}>Move</Button>
<Button onClick={onClickCompress}>Archive</Button>
<Button.Danger variant={Button.Variants.Secondary} onClick={() => setShowConfirm(true)}>
Delete
</Button.Danger>
</div>
</Fade>
</div>
</Portal>
</div>
</>
);
};
export default MassActionsBar;