import { useSignal } from '@preact/signals'; import { Directory, DirectoryFile } from '/lib/types.ts'; import { baseUrl } from '/lib/utils/misc.ts'; import { ResponseBody as UploadResponseBody } from '/routes/api/files/upload.tsx'; import { RequestBody as RenameRequestBody, ResponseBody as RenameResponseBody } from '/routes/api/files/rename.tsx'; import { RequestBody as MoveRequestBody, ResponseBody as MoveResponseBody } from '/routes/api/files/move.tsx'; import { RequestBody as DeleteRequestBody, ResponseBody as DeleteResponseBody } from '/routes/api/files/delete.tsx'; import { RequestBody as CreateDirectoryRequestBody, ResponseBody as CreateDirectoryResponseBody, } from '/routes/api/files/create-directory.tsx'; import { RequestBody as RenameDirectoryRequestBody, ResponseBody as RenameDirectoryResponseBody, } from '/routes/api/files/rename-directory.tsx'; import { RequestBody as MoveDirectoryRequestBody, ResponseBody as MoveDirectoryResponseBody, } from '/routes/api/files/move-directory.tsx'; import { RequestBody as DeleteDirectoryRequestBody, ResponseBody as DeleteDirectoryResponseBody, } from '/routes/api/files/delete-directory.tsx'; import SearchFiles from './SearchFiles.tsx'; import ListFiles from './ListFiles.tsx'; import FilesBreadcrumb from './FilesBreadcrumb.tsx'; import CreateDirectoryModal from './CreateDirectoryModal.tsx'; import RenameDirectoryOrFileModal from './RenameDirectoryOrFileModal.tsx'; import MoveDirectoryOrFileModal from './MoveDirectoryOrFileModal.tsx'; interface MainFilesProps { initialDirectories: Directory[]; initialFiles: DirectoryFile[]; initialPath: string; } export default function MainFiles({ initialDirectories, initialFiles, initialPath }: MainFilesProps) { const isAdding = useSignal(false); const isUploading = useSignal(false); const isDeleting = useSignal(false); const isUpdating = useSignal(false); const directories = useSignal(initialDirectories); const files = useSignal(initialFiles); const path = useSignal(initialPath); const chosenDirectories = useSignal[]>([]); const chosenFiles = useSignal[]>([]); const isAnyItemChosen = chosenDirectories.value.length > 0 || chosenFiles.value.length > 0; const bulkItemsCount = chosenDirectories.value.length + chosenFiles.value.length; const areNewOptionsOpen = useSignal(false); const areBulkOptionsOpen = useSignal(false); const isNewDirectoryModalOpen = useSignal(false); const renameDirectoryOrFileModal = useSignal< { isOpen: boolean; isDirectory: boolean; parentPath: string; name: string } | null >(null); const moveDirectoryOrFileModal = useSignal< { isOpen: boolean; isDirectory: boolean; path: string; name: string } | null >(null); function onClickUploadFile() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = true; fileInput.click(); fileInput.onchange = async (event) => { const chosenFilesList = (event.target as HTMLInputElement)?.files!; const chosenFiles = Array.from(chosenFilesList); isUploading.value = true; for (const chosenFile of chosenFiles) { if (!chosenFile) { continue; } areNewOptionsOpen.value = false; const requestBody = new FormData(); requestBody.set('parent_path', path.value); requestBody.set('name', chosenFile.name); requestBody.set('contents', chosenFile); try { const response = await fetch(`/api/files/upload`, { method: 'POST', body: requestBody, }); const result = await response.json() as UploadResponseBody; if (!result.success) { throw new Error('Failed to upload file!'); } files.value = [...result.newFiles]; } catch (error) { console.error(error); } } isUploading.value = false; }; } function onClickCreateDirectory() { if (isNewDirectoryModalOpen.value) { isNewDirectoryModalOpen.value = false; return; } isNewDirectoryModalOpen.value = true; } async function onClickSaveDirectory(newDirectoryName: string) { if (isAdding.value) { return; } if (!newDirectoryName) { return; } areNewOptionsOpen.value = false; isAdding.value = true; try { const requestBody: CreateDirectoryRequestBody = { parentPath: path.value, name: newDirectoryName, }; const response = await fetch(`/api/files/create-directory`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as CreateDirectoryResponseBody; if (!result.success) { throw new Error('Failed to create directory!'); } directories.value = [...result.newDirectories]; isNewDirectoryModalOpen.value = false; } catch (error) { console.error(error); } isAdding.value = false; } function onCloseCreateDirectory() { isNewDirectoryModalOpen.value = false; } function toggleNewOptionsDropdown() { areNewOptionsOpen.value = !areNewOptionsOpen.value; } function toggleBulkOptionsDropdown() { areBulkOptionsOpen.value = !areBulkOptionsOpen.value; } function onClickOpenRenameDirectory(parentPath: string, name: string) { renameDirectoryOrFileModal.value = { isOpen: true, isDirectory: true, parentPath, name, }; } function onClickOpenRenameFile(parentPath: string, name: string) { renameDirectoryOrFileModal.value = { isOpen: true, isDirectory: false, parentPath, name, }; } function onClickCloseRename() { renameDirectoryOrFileModal.value = null; } async function onClickSaveRenameDirectory(newName: string) { if ( isUpdating.value || !renameDirectoryOrFileModal.value?.isOpen || !renameDirectoryOrFileModal.value?.isDirectory ) { return; } isUpdating.value = true; try { const requestBody: RenameDirectoryRequestBody = { parentPath: renameDirectoryOrFileModal.value.parentPath, oldName: renameDirectoryOrFileModal.value.name, newName, }; const response = await fetch(`/api/files/rename-directory`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as RenameDirectoryResponseBody; if (!result.success) { throw new Error('Failed to rename directory!'); } directories.value = [...result.newDirectories]; } catch (error) { console.error(error); } isUpdating.value = false; renameDirectoryOrFileModal.value = null; } async function onClickSaveRenameFile(newName: string) { if ( isUpdating.value || !renameDirectoryOrFileModal.value?.isOpen || renameDirectoryOrFileModal.value?.isDirectory ) { return; } isUpdating.value = true; try { const requestBody: RenameRequestBody = { parentPath: renameDirectoryOrFileModal.value.parentPath, oldName: renameDirectoryOrFileModal.value.name, newName, }; const response = await fetch(`/api/files/rename`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as RenameResponseBody; if (!result.success) { throw new Error('Failed to rename file!'); } files.value = [...result.newFiles]; } catch (error) { console.error(error); } isUpdating.value = false; renameDirectoryOrFileModal.value = null; } function onClickOpenMoveDirectory(parentPath: string, name: string) { moveDirectoryOrFileModal.value = { isOpen: true, isDirectory: true, path: parentPath, name, }; } function onClickOpenMoveFile(parentPath: string, name: string) { moveDirectoryOrFileModal.value = { isOpen: true, isDirectory: false, path: parentPath, name, }; } function onClickCloseMove() { moveDirectoryOrFileModal.value = null; } async function onClickSaveMoveDirectory(newPath: string) { if (isUpdating.value || !moveDirectoryOrFileModal.value?.isOpen || !moveDirectoryOrFileModal.value?.isDirectory) { return; } isUpdating.value = true; try { const requestBody: MoveDirectoryRequestBody = { oldParentPath: moveDirectoryOrFileModal.value.path, newParentPath: newPath, name: moveDirectoryOrFileModal.value.name, }; const response = await fetch(`/api/files/move-directory`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as MoveDirectoryResponseBody; if (!result.success) { throw new Error('Failed to move directory!'); } directories.value = [...result.newDirectories]; } catch (error) { console.error(error); } isUpdating.value = false; moveDirectoryOrFileModal.value = null; } async function onClickSaveMoveFile(newPath: string) { if (isUpdating.value || !moveDirectoryOrFileModal.value?.isOpen || moveDirectoryOrFileModal.value?.isDirectory) { return; } isUpdating.value = true; try { const requestBody: MoveRequestBody = { oldParentPath: moveDirectoryOrFileModal.value.path, newParentPath: newPath, name: moveDirectoryOrFileModal.value.name, }; const response = await fetch(`/api/files/move`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as MoveResponseBody; if (!result.success) { throw new Error('Failed to move file!'); } files.value = [...result.newFiles]; } catch (error) { console.error(error); } isUpdating.value = false; moveDirectoryOrFileModal.value = null; } async function onClickDeleteDirectory(parentPath: string, name: string, isBulkDeleting = false) { if (isBulkDeleting || confirm('Are you sure you want to delete this directory?')) { if (!isBulkDeleting && isDeleting.value) { return; } isDeleting.value = true; try { const requestBody: DeleteDirectoryRequestBody = { parentPath, name, }; const response = await fetch(`/api/files/delete-directory`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as DeleteDirectoryResponseBody; if (!result.success) { throw new Error('Failed to delete directory!'); } directories.value = [...result.newDirectories]; } catch (error) { console.error(error); } isDeleting.value = false; } } async function onClickDeleteFile(parentPath: string, name: string, isBulkDeleting = false) { if (isBulkDeleting || confirm('Are you sure you want to delete this file?')) { if (!isBulkDeleting && isDeleting.value) { return; } isDeleting.value = true; try { const requestBody: DeleteRequestBody = { parentPath, name, }; const response = await fetch(`/api/files/delete`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as DeleteResponseBody; if (!result.success) { throw new Error('Failed to delete file!'); } files.value = [...result.newFiles]; } catch (error) { console.error(error); } isDeleting.value = false; } } function onClickChooseDirectory(parentPath: string, name: string) { if (parentPath === '/' && name === '.Trash') { return; } const chosenDirectoryIndex = chosenDirectories.value.findIndex((directory) => directory.parent_path === parentPath && directory.directory_name === name ); if (chosenDirectoryIndex === -1) { chosenDirectories.value = [...chosenDirectories.value, { parent_path: parentPath, directory_name: name }]; } else { const newChosenDirectories = chosenDirectories.peek(); newChosenDirectories.splice(chosenDirectoryIndex, 1); chosenDirectories.value = [...newChosenDirectories]; } } function onClickChooseFile(parentPath: string, name: string) { const chosenFileIndex = chosenFiles.value.findIndex((file) => file.parent_path === parentPath && file.file_name === name ); if (chosenFileIndex === -1) { chosenFiles.value = [...chosenFiles.value, { parent_path: parentPath, file_name: name }]; } else { const newChosenFiles = chosenFiles.peek(); newChosenFiles.splice(chosenFileIndex, 1); chosenFiles.value = [...newChosenFiles]; } } async function onClickBulkDelete() { if ( confirm( `Are you sure you want to delete ${bulkItemsCount === 1 ? 'this' : 'these'} ${bulkItemsCount} item${ bulkItemsCount === 1 ? '' : 's' }?`, ) ) { if (isDeleting.value) { return; } isDeleting.value = true; try { for (const directory of chosenDirectories.value) { await onClickDeleteDirectory(directory.parent_path, directory.directory_name, true); } for (const file of chosenFiles.value) { await onClickDeleteDirectory(file.parent_path, file.file_name, true); } chosenDirectories.value = []; chosenFiles.value = []; } catch (error) { console.error(error); } isDeleting.value = false; } } return ( <>
{isAnyItemChosen ? (
) : null}
{isDeleting.value ? ( <> Deleting... ) : null} {isAdding.value ? ( <> Creating... ) : null} {isUploading.value ? ( <> Uploading... ) : null} {isUpdating.value ? ( <> Updating... ) : null} {!isDeleting.value && !isAdding.value && !isUploading.value && !isUpdating.value ? <>  : null}
WebDav URL:{' '} {baseUrl}/dav
); }