import React, { memo, useRef, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBoxOpen, faCopy, faEllipsisH, faFileArchive, faFileCode, faFileDownload, faLevelUpAlt, faPencilAlt, faTrashAlt, IconDefinition, } from '@fortawesome/free-solid-svg-icons'; import RenameFileModal from '@/components/server/files/RenameFileModal'; import { ServerContext } from '@/state/server'; import { join } from 'path'; import { compressFiles, copyFiles, decompressFiles, deleteFiles, getFileDownloadUrl, FileObject } from '@/api/server/files'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import Can from '@/components/elements/Can'; import useFlash from '@/plugins/useFlash'; import tw, { styled } from 'twin.macro'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import DropdownMenu from '@/components/elements/DropdownMenu'; import useEventListener from '@/plugins/useEventListener'; import isEqual from 'react-fast-compare'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import ChmodFileModal from '@/components/server/files/ChmodFileModal'; type ModalType = 'rename' | 'move' | 'chmod'; const StyledRow = styled.div<{ $danger?: boolean }>` ${tw`p-2 flex items-center rounded`}; ${props => props.$danger ? tw`hover:bg-red-100 hover:text-red-700` : tw`hover:bg-neutral-100 hover:text-neutral-700`}; `; interface RowProps extends React.HTMLAttributes { icon: IconDefinition; title: string; $danger?: boolean; } const Row = ({ icon, title, ...props }: RowProps) => ( {title} ); const FileDropdownMenu = ({ file }: { file: FileObject }) => { const onClickRef = useRef(null); const [ showSpinner, setShowSpinner ] = useState(false); const [ modal, setModal ] = useState(null); const [ showConfirmation, setShowConfirmation ] = useState(false); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const { mutate } = useFileManagerSwr(); const { clearAndAddHttpError, clearFlashes } = useFlash(); const directory = ServerContext.useStoreState(state => state.files.directory); useEventListener(`pterodactyl:files:ctx:${file.key}`, (e: CustomEvent) => { if (onClickRef.current) { onClickRef.current.triggerMenu(e.detail); } }); const doDeletion = async () => { clearFlashes('files'); // For UI speed, immediately remove the file from the listing before calling the deletion function. // If the delete actually fails, we'll fetch the current directory contents again automatically. await mutate(files => files!.filter(f => f.key !== file.key), false); deleteFiles(uuid, directory, [ file.name ]).catch(async (error) => { await mutate(); clearAndAddHttpError({ key: 'files', error }); }); }; const doCopy = () => { setShowSpinner(true); clearFlashes('files'); copyFiles(uuid, join(directory, file.name)) .then(async () => await mutate()) .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; const doDownload = () => { setShowSpinner(true); clearFlashes('files'); getFileDownloadUrl(uuid, join(directory, file.name)) .then(url => { // @ts-ignore window.location = url; }) .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; const doArchive = () => { setShowSpinner(true); clearFlashes('files'); compressFiles(uuid, directory, [ file.name ]) .then(async () => await mutate()) .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; const doExtraction = () => { setShowSpinner(true); clearFlashes('files'); decompressFiles(uuid, directory, file.name) .then(async () => await mutate()) .catch(error => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; return ( <> setShowConfirmation(false)} > Deleting files is a permanent operation, you cannot undo this action. (
{modal ? modal === 'chmod' ? setModal(null)} /> : setModal(null)} /> : null }
)} > setModal('rename')} icon={faPencilAlt} title={'Rename'}/> setModal('move')} icon={faLevelUpAlt} title={'Move'}/> setModal('chmod')} icon={faFileCode} title={'Permissions'}/> {file.isFile && } {file.isArchiveType() ? : } {file.isFile && } setShowConfirmation(true)} icon={faTrashAlt} title={'Delete'} $danger/>
); }; export default memo(FileDropdownMenu, isEqual);