From 1868d4b3dc509e4cb1d941eeef4bfc25059e7723 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 04:13:19 +0800 Subject: [PATCH 01/16] alloc 1 col in file listing for file selection --- components/FileListing.tsx | 44 ++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 26be464..66eb144 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -68,8 +68,8 @@ const FileListItem: FunctionComponent<{ const renderEmoji = emojiIcon && !emojiIcon.index return ( -
-
+
+
{/*
{c.file ? c.file.mimeType : 'folder'}
*/}
{renderEmoji ? ( @@ -176,10 +176,32 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = return (
-
Name
+
Name
Last Modified
Size
Actions
+
+
+ + + + + + +
+
= ({ query }) = {children.map((c: any) => (
{ e.preventDefault() @@ -279,6 +301,20 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
)} +
+ + + +
))} From d1b70221274eda4d26f49391a670cb77bae6c574 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 06:36:23 +0800 Subject: [PATCH 02/16] add file selection checkbox behavior --- components/FileListing.tsx | 99 ++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 25 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 66eb144..08b741a 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -4,7 +4,7 @@ import emojiRegex from 'emoji-regex' import { useClipboard } from 'use-clipboard-copy' import { ParsedUrlQuery } from 'querystring' -import { FunctionComponent, useState } from 'react' +import { FunctionComponent, useEffect, useRef, useState } from 'react' import { ImageDecorator } from 'react-viewer/lib/ViewerProps' import { useRouter } from 'next/router' @@ -99,9 +99,43 @@ const FileListItem: FunctionComponent<{ ) } +const Checkbox: FunctionComponent<{ + checked: 0|1|2, onChange: () => void, title: string, indeterminate?: boolean +}> = ({ checked, onChange, title, indeterminate }) => { + const ref = useRef(null) + useEffect(() => { + if (ref.current) { + ref.current.checked = Boolean(checked) + if (indeterminate) { + ref.current.indeterminate = checked == 1 + } + } + }, [ref, checked, indeterminate]) + const handleClick = () => { ref.current ? ref.current.click() : null } + + return ( + + + + ) +} + const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => { const [imageViewerVisible, setImageViewerVisibility] = useState(false) const [activeImageIdx, setActiveImageIdx] = useState(0) + const [selected, setSelected] = useState<{[key: string]: boolean}>({}) + const [totalSelected, setTotalSelected] = useState<0|1|2>(0) const router = useRouter() const clipboard = useClipboard() @@ -173,6 +207,34 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = } }) + // File selection + const genTotalSelected = (selected: {[key: string]: boolean}) => { + const selectInfo = children.map((c :any) => Boolean(selected[c.id])) + const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] + console.log(hasT, hasF) + return hasT && hasF ? 1 : (!hasF ? 2 : 0) + } + const toggleItemSelected = (id: string) => { + let val + if (selected[id]) { + val = {...selected} + delete val[id] + } else { + val = {...selected, [id]: true} + } + setSelected(val) + setTotalSelected(genTotalSelected(val)) + } + const toggleTotalSelected = () => { + if (genTotalSelected(selected) == 2) { + setSelected({}) + setTotalSelected(0) + } else { + setSelected(Object.fromEntries(children.map((c :any) => [c.id, true]))) + setTotalSelected(2) + } + } + return (
@@ -182,18 +244,12 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
Actions
- - - + = ({ query }) =
)}
- - - + toggleItemSelected(c.id)} + title={"Select file"} + />
))} From 61dd08c1aa1b389d098e07ecd681880a12125825 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 07:28:05 +0800 Subject: [PATCH 03/16] add download button style and action Add jszip to dependencies --- components/FileListing.tsx | 26 +++++++++++++++++++------- package-lock.json | 1 + package.json | 1 + utils/tools.ts | 25 +++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 08b741a..bcbb9d3 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -12,7 +12,7 @@ import dynamic from 'next/dynamic' import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon' import { extensions, preview } from '../utils/getPreviewType' -import { getBaseUrl, useProtectedSWRInfinite } from '../utils/tools' +import { getBaseUrl, saveFiles, useProtectedSWRInfinite } from '../utils/tools' import { VideoPreview } from './previews/VideoPreview' import { AudioPreview } from './previews/AudioPreview' @@ -235,6 +235,15 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = } } + // Selected file download + const handleDownloadSelected = () => { + const folderName = path.substr(path.lastIndexOf('/') + 1) + const folder = folderName ? folderName : undefined + const files = children.filter((c: any) => selected[c.id]) + .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) + saveFiles(files, folder) + } + return (
@@ -250,12 +259,15 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = indeterminate={true} title={"Select files"} /> - - - + {totalSelected ? ( + + + + ) : ''}
diff --git a/package-lock.json b/package-lock.json index 14744c7..7da8e35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "axios": "^0.21.1", "crypto-js": "^4.1.1", "emoji-regex": "^9.2.2", + "jszip": "^3.7.1", "next": "^11.1.0", "preview-office-docs": "^1.0.2", "prismjs": "^1.23.0", diff --git a/package.json b/package.json index ca4de1c..4fbfae6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "axios": "^0.21.1", "crypto-js": "^4.1.1", "emoji-regex": "^9.2.2", + "jszip": "^3.7.1", "next": "^11.1.0", "preview-office-docs": "^1.0.2", "prismjs": "^1.23.0", diff --git a/utils/tools.ts b/utils/tools.ts index 904e263..c9dd3e1 100644 --- a/utils/tools.ts +++ b/utils/tools.ts @@ -1,6 +1,7 @@ import axios from 'axios' import sha256 from 'crypto-js/sha256' import useSWR, { cache, Key, useSWRInfinite } from 'swr' +import JSZip from 'jszip' import siteConfig from '../config/site.json' @@ -123,3 +124,27 @@ export const matchProtectedRoute = (route: string) => { } return authTokenPath } + +/** + * Download multiple files after compressing them into a zip + * @param files Files to be downloaded + * @param folder Optional folder name to hold files, otherwise flatten files in the zip + */ +export const saveFiles = (files: { name: string, url: string }[], folder?: string) => { + const zip = new JSZip() + const dir = folder ? zip.folder(folder)! : zip + files.forEach(({ name, url }) => { + dir.file(name, fetch(url).then(r => r.blob())) + }) + dir.generateAsync({ type: 'blob' }).then(b => { + const el = document.createElement('a') + el.style.display = 'none' + document.body.appendChild(el) + const bUrl = window.URL.createObjectURL(b) + el.href = bUrl + el.download = folder ? folder + '.zip' : 'download.zip' + el.click() + window.URL.revokeObjectURL(bUrl) + el.remove() + }) +} From f8777682cc9890f54c9cec156798600764a826e6 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 07:51:28 +0800 Subject: [PATCH 04/16] exclude folders from selection --- components/FileListing.tsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index bcbb9d3..f168ae0 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -209,7 +209,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = // File selection const genTotalSelected = (selected: {[key: string]: boolean}) => { - const selectInfo = children.map((c :any) => Boolean(selected[c.id])) + const selectInfo = children.filter((c :any) => !c.folder).map((c :any) => Boolean(selected[c.id])) const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] console.log(hasT, hasF) return hasT && hasF ? 1 : (!hasF ? 2 : 0) @@ -230,7 +230,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = setSelected({}) setTotalSelected(0) } else { - setSelected(Object.fromEntries(children.map((c :any) => [c.id, true]))) + setSelected(Object.fromEntries(children.filter((c :any) => !c.folder).map((c :any) => [c.id, true]))) setTotalSelected(2) } } @@ -239,7 +239,9 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const handleDownloadSelected = () => { const folderName = path.substr(path.lastIndexOf('/') + 1) const folder = folderName ? folderName : undefined - const files = children.filter((c: any) => selected[c.id]) + const files = children + .filter((c :any) => !c.folder) + .filter((c: any) => selected[c.id]) .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) saveFiles(files, folder) } @@ -370,11 +372,13 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
)}
- toggleItemSelected(c.id)} - title={"Select file"} - /> + {c.folder ? '' : ( + toggleItemSelected(c.id)} + title="Select file" + /> + )}
))} From c1454db48f85923e997031b1dc061463972ec314 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 16:21:42 +0800 Subject: [PATCH 05/16] add spinner when downloading selected files --- components/FileListing.tsx | 30 +++++++++++++++++++++++++++--- utils/tools.ts | 6 +++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index f168ae0..839602a 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -136,6 +136,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const [activeImageIdx, setActiveImageIdx] = useState(0) const [selected, setSelected] = useState<{[key: string]: boolean}>({}) const [totalSelected, setTotalSelected] = useState<0|1|2>(0) + const [totalGenerating, setTotalGenerating] = useState(false) const router = useRouter() const clipboard = useClipboard() @@ -237,13 +238,14 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = // Selected file download const handleDownloadSelected = () => { + setTotalGenerating(true) const folderName = path.substr(path.lastIndexOf('/') + 1) const folder = folderName ? folderName : undefined const files = children .filter((c :any) => !c.folder) .filter((c: any) => selected[c.id]) .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) - saveFiles(files, folder) + saveFiles(files, folder, () => setTotalGenerating(false)) } return ( @@ -261,7 +263,29 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = indeterminate={true} title={"Select files"} /> - {totalSelected ? ( + {totalGenerating ? ( + + + + + + + ) : (totalSelected ? ( = ({ query }) = > - ) : ''} + ) : '')}
diff --git a/utils/tools.ts b/utils/tools.ts index c9dd3e1..3ad0986 100644 --- a/utils/tools.ts +++ b/utils/tools.ts @@ -129,8 +129,9 @@ export const matchProtectedRoute = (route: string) => { * Download multiple files after compressing them into a zip * @param files Files to be downloaded * @param folder Optional folder name to hold files, otherwise flatten files in the zip + * @param onFinish Optional hook triggered after sending generated zip to browser to download */ -export const saveFiles = (files: { name: string, url: string }[], folder?: string) => { +export const saveFiles = (files: { name: string, url: string }[], folder?: string, onFinish?: () => void) => { const zip = new JSZip() const dir = folder ? zip.folder(folder)! : zip files.forEach(({ name, url }) => { @@ -146,5 +147,8 @@ export const saveFiles = (files: { name: string, url: string }[], folder?: strin el.click() window.URL.revokeObjectURL(bUrl) el.remove() + if (onFinish) { + onFinish() + } }) } From 54de2651ce2b90c43da3e16422c0010b3629c8ec Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 16:38:00 +0800 Subject: [PATCH 06/16] exclude selecting only one file from multi file download --- components/FileListing.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 839602a..b86de3f 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -238,14 +238,23 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = // Selected file download const handleDownloadSelected = () => { - setTotalGenerating(true) const folderName = path.substr(path.lastIndexOf('/') + 1) const folder = folderName ? folderName : undefined const files = children .filter((c :any) => !c.folder) .filter((c: any) => selected[c.id]) .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) - saveFiles(files, folder, () => setTotalGenerating(false)) + if (files.length == 1) { + const el = document.createElement('a') + el.style.display = 'none' + document.body.appendChild(el) + el.href = files[0].url + el.click() + el.remove() + } else if (files.length > 1) { + setTotalGenerating(true) + saveFiles(files, folder, () => setTotalGenerating(false)) + } } return ( From c4c31e0ddae0c0f80fb1df44566cb4f3149a5c46 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 21:31:07 +0800 Subject: [PATCH 07/16] exclude .password from downloading --- components/FileListing.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index b86de3f..a31a41b 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -208,9 +208,12 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = } }) + // Filtered file list helper + const getFiles = () => children.filter((c :any) => !c.folder && c.name !== '.password') + // File selection const genTotalSelected = (selected: {[key: string]: boolean}) => { - const selectInfo = children.filter((c :any) => !c.folder).map((c :any) => Boolean(selected[c.id])) + const selectInfo = getFiles().map((c :any) => Boolean(selected[c.id])) const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] console.log(hasT, hasF) return hasT && hasF ? 1 : (!hasF ? 2 : 0) @@ -231,7 +234,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = setSelected({}) setTotalSelected(0) } else { - setSelected(Object.fromEntries(children.filter((c :any) => !c.folder).map((c :any) => [c.id, true]))) + setSelected(Object.fromEntries(getFiles().map((c :any) => [c.id, true]))) setTotalSelected(2) } } @@ -240,8 +243,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const handleDownloadSelected = () => { const folderName = path.substr(path.lastIndexOf('/') + 1) const folder = folderName ? folderName : undefined - const files = children - .filter((c :any) => !c.folder) + const files = getFiles() .filter((c: any) => selected[c.id]) .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) if (files.length == 1) { From d7dd74772efda589db1ae9c27f414a27f77032e5 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 19 Nov 2021 21:37:14 +0800 Subject: [PATCH 08/16] hide selecting checkbox for .password .password is still downloadable via per-file download button, but it is completely excluded from selecting downloading --- components/FileListing.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index a31a41b..82bc7a8 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -407,7 +407,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
)}
- {c.folder ? '' : ( + {c.folder || c.name === '.password' ? '' : ( toggleItemSelected(c.id)} From 6c63aa90be44fc74ab88dd25469422eb6eb9ded0 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sat, 27 Nov 2021 16:22:44 +0800 Subject: [PATCH 09/16] fix code style problem --- components/FileListing.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 82bc7a8..6e70c4c 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -100,7 +100,7 @@ const FileListItem: FunctionComponent<{ } const Checkbox: FunctionComponent<{ - checked: 0|1|2, onChange: () => void, title: string, indeterminate?: boolean + checked: 0 | 1 | 2, onChange: () => void, title: string, indeterminate?: boolean }> = ({ checked, onChange, title, indeterminate }) => { const ref = useRef(null) useEffect(() => { @@ -111,7 +111,9 @@ const Checkbox: FunctionComponent<{ } } }, [ref, checked, indeterminate]) - const handleClick = () => { ref.current ? ref.current.click() : null } + const handleClick = () => { + if (ref.current) ref.current.click() + } return ( = ({ query }) => { const [imageViewerVisible, setImageViewerVisibility] = useState(false) const [activeImageIdx, setActiveImageIdx] = useState(0) - const [selected, setSelected] = useState<{[key: string]: boolean}>({}) - const [totalSelected, setTotalSelected] = useState<0|1|2>(0) + const [selected, setSelected] = useState<{ [key: string]: boolean }>({}) + const [totalSelected, setTotalSelected] = useState<0 | 1 | 2>(0) const [totalGenerating, setTotalGenerating] = useState(false) const router = useRouter() @@ -209,11 +211,11 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = }) // Filtered file list helper - const getFiles = () => children.filter((c :any) => !c.folder && c.name !== '.password') + const getFiles = () => children.filter((c: any) => !c.folder && c.name !== '.password') // File selection - const genTotalSelected = (selected: {[key: string]: boolean}) => { - const selectInfo = getFiles().map((c :any) => Boolean(selected[c.id])) + const genTotalSelected = (selected: { [key: string]: boolean }) => { + const selectInfo = getFiles().map((c: any) => Boolean(selected[c.id])) const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] console.log(hasT, hasF) return hasT && hasF ? 1 : (!hasF ? 2 : 0) @@ -221,10 +223,10 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const toggleItemSelected = (id: string) => { let val if (selected[id]) { - val = {...selected} + val = { ...selected } delete val[id] } else { - val = {...selected, [id]: true} + val = { ...selected, [id]: true } } setSelected(val) setTotalSelected(genTotalSelected(val)) @@ -234,7 +236,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = setSelected({}) setTotalSelected(0) } else { - setSelected(Object.fromEntries(getFiles().map((c :any) => [c.id, true]))) + setSelected(Object.fromEntries(getFiles().map((c: any) => [c.id, true]))) setTotalSelected(2) } } From 1d6c40affea87d2df9900ff8ba66f976ec1aef9a Mon Sep 17 00:00:00 2001 From: myl7 Date: Sat, 27 Nov 2021 16:26:22 +0800 Subject: [PATCH 10/16] update download function Rename it and turn it into async other than using callback --- components/FileListing.tsx | 6 +++--- utils/tools.ts | 27 +++++++++++---------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 6e70c4c..ece6fb8 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -242,7 +242,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = } // Selected file download - const handleDownloadSelected = () => { + const handleSelectedDownload = () => { const folderName = path.substr(path.lastIndexOf('/') + 1) const folder = folderName ? folderName : undefined const files = getFiles() @@ -257,7 +257,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = el.remove() } else if (files.length > 1) { setTotalGenerating(true) - saveFiles(files, folder, () => setTotalGenerating(false)) + saveFiles(files, folder).then(() => setTotalGenerating(false)) } } @@ -302,7 +302,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = diff --git a/utils/tools.ts b/utils/tools.ts index 3ad0986..e134db5 100644 --- a/utils/tools.ts +++ b/utils/tools.ts @@ -129,26 +129,21 @@ export const matchProtectedRoute = (route: string) => { * Download multiple files after compressing them into a zip * @param files Files to be downloaded * @param folder Optional folder name to hold files, otherwise flatten files in the zip - * @param onFinish Optional hook triggered after sending generated zip to browser to download */ -export const saveFiles = (files: { name: string, url: string }[], folder?: string, onFinish?: () => void) => { +export const saveFiles = async (files: { name: string, url: string }[], folder?: string) => { const zip = new JSZip() const dir = folder ? zip.folder(folder)! : zip files.forEach(({ name, url }) => { dir.file(name, fetch(url).then(r => r.blob())) }) - dir.generateAsync({ type: 'blob' }).then(b => { - const el = document.createElement('a') - el.style.display = 'none' - document.body.appendChild(el) - const bUrl = window.URL.createObjectURL(b) - el.href = bUrl - el.download = folder ? folder + '.zip' : 'download.zip' - el.click() - window.URL.revokeObjectURL(bUrl) - el.remove() - if (onFinish) { - onFinish() - } - }) + const b = await dir.generateAsync({ type: 'blob' }) + const el = document.createElement('a') + el.style.display = 'none' + document.body.appendChild(el) + const bUrl = window.URL.createObjectURL(b) + el.href = bUrl + el.download = folder ? folder + '.zip' : 'download.zip' + el.click() + window.URL.revokeObjectURL(bUrl) + el.remove() } From 66edd3034f73b69d1f0f2eb66a5d87ee6bf35511 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sat, 27 Nov 2021 17:00:32 +0800 Subject: [PATCH 11/16] remove meaningless console log --- components/FileListing.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index ece6fb8..c401bc3 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -217,7 +217,6 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const genTotalSelected = (selected: { [key: string]: boolean }) => { const selectInfo = getFiles().map((c: any) => Boolean(selected[c.id])) const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] - console.log(hasT, hasF) return hasT && hasF ? 1 : (!hasF ? 2 : 0) } const toggleItemSelected = (id: string) => { From e706051238423701e38df85dccfb12dc3864e255 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sat, 27 Nov 2021 19:04:21 +0800 Subject: [PATCH 12/16] fix duplicated click event for wrapped checkbox --- components/FileListing.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index c401bc3..b224e23 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -4,7 +4,7 @@ import emojiRegex from 'emoji-regex' import { useClipboard } from 'use-clipboard-copy' import { ParsedUrlQuery } from 'querystring' -import { FunctionComponent, useEffect, useRef, useState } from 'react' +import { FunctionComponent, MouseEventHandler, useEffect, useRef, useState } from 'react' import { ImageDecorator } from 'react-viewer/lib/ViewerProps' import { useRouter } from 'next/router' @@ -111,8 +111,14 @@ const Checkbox: FunctionComponent<{ } } }, [ref, checked, indeterminate]) - const handleClick = () => { - if (ref.current) ref.current.click() + const handleClick: MouseEventHandler = (e) => { + if (ref.current) { + if (e.target === ref.current) { + e.stopPropagation() + } else { + ref.current.click() + } + } } return ( From 1b74a378e96fc57fdb8e5a25d46b018b105ca4d3 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 28 Nov 2021 21:26:20 +0800 Subject: [PATCH 13/16] add selected download notification --- components/FileListing.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index b224e23..3fba704 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -262,7 +262,16 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = el.remove() } else if (files.length > 1) { setTotalGenerating(true) - saveFiles(files, folder).then(() => setTotalGenerating(false)) + const toastId = toast.loading('Downloading selected files. This may be slow...') + saveFiles(files, folder).then(() => { + setTotalGenerating(false) + toast.dismiss(toastId) + toast.success('Finished to download selected files.') + }).catch(() => { + setTotalGenerating(false) + toast.dismiss(toastId) + toast.error('Failed to download selected files.') + }) } } From 1c1ae5a2c17edb0ac80e6f6749d1c90ce13431ec Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Wed, 15 Dec 2021 17:01:31 +0800 Subject: [PATCH 14/16] wider file listing on large screens --- pages/[...path].tsx | 2 +- pages/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/[...path].tsx b/pages/[...path].tsx index 0976252..44810e9 100644 --- a/pages/[...path].tsx +++ b/pages/[...path].tsx @@ -18,7 +18,7 @@ export default function Folders() {
-
+
diff --git a/pages/index.tsx b/pages/index.tsx index 85eba46..e5b7d71 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -15,7 +15,7 @@ export default function Home() {
-
+
From fd869698f5fe09bafe448bf7ac396a749dccf45b Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Wed, 15 Dec 2021 17:02:06 +0800 Subject: [PATCH 15/16] fix a few style issues with toast and checkbox --- components/FileListing.tsx | 83 ++++++++++++++++++++------------------ components/Navbar.tsx | 2 +- utils/tools.ts | 13 +++++- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 33b5501..4a86a49 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -4,7 +4,7 @@ import emojiRegex from 'emoji-regex' import { useClipboard } from 'use-clipboard-copy' import { ParsedUrlQuery } from 'querystring' -import { FunctionComponent, MouseEventHandler, useEffect, useRef, useState } from 'react' +import { FunctionComponent, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from 'react' import { ImageDecorator } from 'react-viewer/lib/ViewerProps' import { useRouter } from 'next/router' @@ -12,7 +12,7 @@ import dynamic from 'next/dynamic' import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon' import { extensions, preview } from '../utils/getPreviewType' -import { getBaseUrl, saveFiles, useProtectedSWRInfinite } from '../utils/tools' +import { getBaseUrl, downloadMultipleFiles, useProtectedSWRInfinite } from '../utils/tools' import { VideoPreview } from './previews/VideoPreview' import { AudioPreview } from './previews/AudioPreview' @@ -100,9 +100,13 @@ const FileListItem: FunctionComponent<{ } const Checkbox: FunctionComponent<{ - checked: 0 | 1 | 2, onChange: () => void, title: string, indeterminate?: boolean + checked: 0 | 1 | 2 + onChange: () => void + title: string + indeterminate?: boolean }> = ({ checked, onChange, title, indeterminate }) => { const ref = useRef(null) + useEffect(() => { if (ref.current) { ref.current.checked = Boolean(checked) @@ -111,7 +115,8 @@ const Checkbox: FunctionComponent<{ } } }, [ref, checked, indeterminate]) - const handleClick: MouseEventHandler = (e) => { + + const handleClick: MouseEventHandler = e => { if (ref.current) { if (e.target === ref.current) { e.stopPropagation() @@ -223,10 +228,11 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = const genTotalSelected = (selected: { [key: string]: boolean }) => { const selectInfo = getFiles().map((c: any) => Boolean(selected[c.id])) const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] - return hasT && hasF ? 1 : (!hasF ? 2 : 0) + return hasT && hasF ? 1 : !hasF ? 2 : 0 } + const toggleItemSelected = (id: string) => { - let val + let val: SetStateAction<{ [key: string]: boolean }> if (selected[id]) { val = { ...selected } delete val[id] @@ -236,6 +242,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = setSelected(val) setTotalSelected(genTotalSelected(val)) } + const toggleTotalSelected = () => { if (genTotalSelected(selected) == 2) { setSelected({}) @@ -248,11 +255,12 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = // Selected file download const handleSelectedDownload = () => { - const folderName = path.substr(path.lastIndexOf('/') + 1) - const folder = folderName ? folderName : undefined + const folderName = path.substring(path.lastIndexOf('/') + 1) + const folder = folderName ? decodeURIComponent(folderName) : undefined const files = getFiles() .filter((c: any) => selected[c.id]) .map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) + if (files.length == 1) { const el = document.createElement('a') el.style.display = 'none' @@ -262,22 +270,24 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = el.remove() } else if (files.length > 1) { setTotalGenerating(true) - const toastId = toast.loading('Downloading selected files. This may be slow...') - saveFiles(files, folder).then(() => { - setTotalGenerating(false) - toast.dismiss(toastId) - toast.success('Finished to download selected files.') - }).catch(() => { - setTotalGenerating(false) - toast.dismiss(toastId) - toast.error('Failed to download selected files.') - }) + const toastId = toast.loading('Downloading selected files. Refresh to cancle, this may take some time...') + downloadMultipleFiles(files, folder) + .then(() => { + setTotalGenerating(false) + toast.dismiss(toastId) + toast.success('Finished downloading selected files.') + }) + .catch(() => { + setTotalGenerating(false) + toast.dismiss(toastId) + toast.error('Failed to download selected files.') + }) } } return (
-
+
Name
Last Modified
Size
@@ -288,14 +298,10 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = checked={totalSelected} onChange={toggleTotalSelected} indeterminate={true} - title={"Select files"} + title={'Select files'} /> {totalGenerating ? ( - + = ({ query }) = /> - ) : (totalSelected ? ( + ) : totalSelected ? ( = ({ query }) = > - ) : '')} + ) : ( + '' + )}
- + {imagesInFolder.length !== 0 && ( = ({ query }) = render: , onClick: i => { clipboard.copy(i.alt ? `${getBaseUrl()}/api?path=${path + '/' + i.alt}&raw=true` : '') - toast.success('Copied image permanent link to clipboard.') + toast('Copied image permanent link to clipboard.', { icon: '👌' }) }, }, ]) @@ -395,7 +396,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" onClick={() => { clipboard.copy(`${getBaseUrl()}${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`) - toast.success('Copied folder permalink.') + toast('Copied folder permalink.', { icon: '👌' }) }} > @@ -407,7 +408,9 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = title="Copy raw file permalink" className="hover:bg-gray-300 dark:hover:bg-gray-600 p-2 rounded cursor-pointer" onClick={() => { - clipboard.copy(`${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${encodeURIComponent(c.name)}&raw=true`) + clipboard.copy( + `${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${encodeURIComponent(c.name)}&raw=true` + ) toast.success('Copied raw file permalink.') }} > @@ -423,7 +426,9 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) =
)}
- {c.folder || c.name === '.password' ? '' : ( + {c.folder || c.name === '.password' ? ( + '' + ) : ( toggleItemSelected(c.id)} diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 5003b23..c5bef6e 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -42,7 +42,7 @@ const Navbar = () => { return (
-
+
diff --git a/utils/tools.ts b/utils/tools.ts index e134db5..b97de21 100644 --- a/utils/tools.ts +++ b/utils/tools.ts @@ -130,16 +130,25 @@ export const matchProtectedRoute = (route: string) => { * @param files Files to be downloaded * @param folder Optional folder name to hold files, otherwise flatten files in the zip */ -export const saveFiles = async (files: { name: string, url: string }[], folder?: string) => { +export const downloadMultipleFiles = async (files: { name: string; url: string }[], folder?: string) => { const zip = new JSZip() const dir = folder ? zip.folder(folder)! : zip + + // Add selected file blobs to zip files.forEach(({ name, url }) => { - dir.file(name, fetch(url).then(r => r.blob())) + dir.file( + name, + fetch(url).then(r => r.blob()) + ) }) + + // Create zip file and prepare for download const b = await dir.generateAsync({ type: 'blob' }) const el = document.createElement('a') el.style.display = 'none' document.body.appendChild(el) + + // Download zip file const bUrl = window.URL.createObjectURL(b) el.href = bUrl el.download = folder ? folder + '.zip' : 'download.zip' From e47745c29d60122a1dffbf8265fd8759ba1ed940 Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Thu, 16 Dec 2021 12:49:22 +0800 Subject: [PATCH 16/16] show button disabled when none of the files are selected --- components/FileListing.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index 4a86a49..59c10a7 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -270,7 +270,7 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = el.remove() } else if (files.length > 1) { setTotalGenerating(true) - const toastId = toast.loading('Downloading selected files. Refresh to cancle, this may take some time...') + const toastId = toast.loading('Downloading selected files. Refresh to cancel, this may take some time...') downloadMultipleFiles(files, folder) .then(() => { setTotalGenerating(false) @@ -318,16 +318,15 @@ const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) = /> - ) : totalSelected ? ( - - - ) : ( - '' + )}