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 deleteFiles from '@/api/server/files/deleteFiles'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import copyFile from '@/api/server/files/copyFile'; import Can from '@/components/elements/Can'; import getFileDownloadUrl from '@/api/server/files/getFileDownloadUrl'; import useFlash from '@/plugins/useFlash'; import tw from 'twin.macro'; import { FileObject } from '@/api/server/files/loadDirectory'; import useFileManagerSwr from '@/plugins/useFileManagerSwr'; import DropdownMenu from '@/components/elements/DropdownMenu'; import styled from 'styled-components/macro'; import useEventListener from '@/plugins/useEventListener'; import compressFiles from '@/api/server/files/compressFiles'; import decompressFiles from '@/api/server/files/decompressFiles'; import isEqual from 'react-fast-compare'; import ChmodFileModal from '@/components/server/files/ChmodFileModal'; import { Dialog } from '@/components/elements/dialog'; import DropdownItems from '@/blueprint/components/Server/Files/Browse/DropdownItems'; 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 = () => { 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. mutate((files) => files.filter((f) => f.key !== file.key), false); deleteFiles(uuid, directory, [file.name]).catch((error) => { mutate(); clearAndAddHttpError({ key: 'files', error }); }); }; const doCopy = () => { setShowSpinner(true); clearFlashes('files'); copyFile(uuid, join(directory, file.name)) .then(() => 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-expect-error this is valid window.location = url; }) .catch((error) => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; const doArchive = () => { setShowSpinner(true); clearFlashes('files'); compressFiles(uuid, directory, [file.name]) .then(() => mutate()) .catch((error) => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; const doUnarchive = () => { setShowSpinner(true); clearFlashes('files'); decompressFiles(uuid, directory, file.name) .then(() => mutate()) .catch((error) => clearAndAddHttpError({ key: 'files', error })) .then(() => setShowSpinner(false)); }; return ( <> setShowConfirmation(false)} title={`Delete ${file.isFile ? 'File' : 'Directory'}`} confirm={'Delete'} onConfirmed={doDeletion} > You will not be able to recover the contents of  {file.name} once deleted. (
{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);