diff --git a/.dvmrc b/.dvmrc index c8d55f3..94ed04f 100644 --- a/.dvmrc +++ b/.dvmrc @@ -1 +1 @@ -1.45.5 +1.46.2 diff --git a/Dockerfile b/Dockerfile index a49eb76..e46b810 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM denoland/deno:ubuntu-1.45.5 +FROM denoland/deno:ubuntu-1.46.2 EXPOSE 8000 diff --git a/components/files/ListFiles.tsx b/components/files/ListFiles.tsx index 45199b2..7790c39 100644 --- a/components/files/ListFiles.tsx +++ b/components/files/ListFiles.tsx @@ -4,6 +4,10 @@ import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts'; interface ListFilesProps { directories: Directory[]; files: DirectoryFile[]; + chosenDirectories?: Pick[]; + chosenFiles?: Pick[]; + onClickChooseFile?: (parentPath: string, name: string) => void; + onClickChooseDirectory?: (parentPath: string, name: string) => void; onClickOpenRenameDirectory?: (parentPath: string, name: string) => void; onClickOpenRenameFile?: (parentPath: string, name: string) => void; onClickOpenMoveDirectory?: (parentPath: string, name: string) => void; @@ -18,6 +22,10 @@ export default function ListFiles( { directories, files, + chosenDirectories = [], + chosenFiles = [], + onClickChooseFile, + onClickChooseDirectory, onClickOpenRenameDirectory, onClickOpenRenameFile, onClickOpenMoveDirectory, @@ -55,13 +63,38 @@ export default function ListFiles( return null; } + const isAnyItemChosen = chosenDirectories.length > 0 || chosenFiles.length > 0; + + function chooseAllItems() { + if (typeof onClickChooseFile !== 'undefined') { + files.forEach((files) => onClickChooseFile(files.parent_path, files.file_name)); + } + + if (typeof onClickChooseDirectory !== 'undefined') { + directories.forEach((directory) => onClickChooseDirectory(directory.parent_path, directory.directory_name)); + } + } + return (
+ {(directories.length === 0 && files.length === 0) || + (typeof onClickChooseFile === 'undefined' && typeof onClickChooseDirectory === 'undefined') + ? null + : ( + + )} - + {isShowingNotes || isShowingPhotos ? null : } @@ -74,6 +107,21 @@ export default function ListFiles( return ( + {typeof onClickChooseDirectory === 'undefined' ? null : ( + + )} + {typeof onClickChooseFile === 'undefined' ? null : ( + + )}
+ chooseAllItems()} + checked={isAnyItemChosen} + /> + NameLast updateLast updateSize
+ {fullPath === TRASH_PATH ? null : ( + onClickChooseDirectory(directory.parent_path, directory.directory_name)} + checked={Boolean(chosenDirectories.find((_directory) => + _directory.parent_path === directory.parent_path && + _directory.directory_name === directory.directory_name + ))} + /> + )} + (
+ onClickChooseFile(file.parent_path, file.file_name)} + checked={Boolean( + chosenFiles.find((_file) => + _file.parent_path === file.parent_path && _file.file_name === file.file_name + ), + )} + /> + - +
No {itemPluralLabel} to show
diff --git a/components/files/MainFiles.tsx b/components/files/MainFiles.tsx index ce59696..bbd275f 100644 --- a/components/files/MainFiles.tsx +++ b/components/files/MainFiles.tsx @@ -43,7 +43,12 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat const directories = useSignal(initialDirectories); const files = useSignal(initialFiles); const path = useSignal(initialPath); - const areNewOptionsOption = useSignal(false); + 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 @@ -70,7 +75,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat continue; } - areNewOptionsOption.value = false; + areNewOptionsOpen.value = false; const requestBody = new FormData(); requestBody.set('parent_path', path.value); @@ -116,7 +121,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat return; } - areNewOptionsOption.value = false; + areNewOptionsOpen.value = false; isAdding.value = true; try { @@ -149,7 +154,11 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat } function toggleNewOptionsDropdown() { - areNewOptionsOption.value = !areNewOptionsOption.value; + areNewOptionsOpen.value = !areNewOptionsOpen.value; + } + + function toggleBulkOptionsDropdown() { + areBulkOptionsOpen.value = !areBulkOptionsOpen.value; } function onClickOpenRenameDirectory(parentPath: string, name: string) { @@ -328,9 +337,9 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat moveDirectoryOrFileModal.value = null; } - async function onClickDeleteDirectory(parentPath: string, name: string) { - if (confirm('Are you sure you want to delete this directory?')) { - if (isDeleting.value) { + 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; } @@ -360,9 +369,9 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat } } - async function onClickDeleteFile(parentPath: string, name: string) { - if (confirm('Are you sure you want to delete this file?')) { - if (isDeleting.value) { + 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; } @@ -392,12 +401,122 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat } } + 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}
@@ -427,7 +546,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat