Also mark selection

This commit is contained in:
Manav Rathi 2025-02-20 12:46:54 +05:30
parent 957c333cf3
commit 77d16e275d
No known key found for this signature in database
2 changed files with 52 additions and 28 deletions

View File

@ -228,35 +228,61 @@ export const Upload: React.FC<UploadProps> = ({
const uploaderNameRef = useRef<string>(null); const uploaderNameRef = useRef<string>(null);
const isDragAndDrop = useRef(false); const isDragAndDrop = useRef(false);
/**
* `true` if we've activated one hidden {@link Inputs} that allow the user
* to select items, and haven't heard back from the browser as to the
* selection (or cancellation).
*
* [Note: Showing an activity indicator during upload item selection]
*
* When selecting a large number of items (100K+), the browser can take
* significant time (10s+) before it hands back control to us. The
* {@link isInputPending} state tracks this intermediate state, and we use
* it to show an activity indicator to let that the user know that their
* selection is still being processed.
*/
const [isInputPending, setIsInputPending] = useState(false);
/**
* Files that were selected by the user in the last activation of one of the
* hidden {@link Inputs}.
*/
const [selectedInputFiles, setSelectedInputFiles] = useState<File[]>([]);
const handleInputSelect = useCallback((files: File[]) => {
setIsInputPending(false);
setSelectedInputFiles(files);
}, []);
const handleInputCancel = useCallback(() => { const handleInputCancel = useCallback(() => {
console.log("cancel"); setIsInputPending(false);
}, []); }, []);
const { const {
getInputProps: getFileSelectorInputProps, getInputProps: getFileSelectorInputProps,
openSelector: openFileSelector, openSelector: openFileSelector,
selectedFiles: fileSelectorFiles,
} = useFileInput({ } = useFileInput({
directory: false, directory: false,
onSelect: handleInputSelect,
onCancel: handleInputCancel, onCancel: handleInputCancel,
}); });
const { const {
getInputProps: getFolderSelectorInputProps, getInputProps: getFolderSelectorInputProps,
openSelector: openFolderSelector, openSelector: openFolderSelector,
selectedFiles: folderSelectorFiles,
} = useFileInput({ } = useFileInput({
directory: true, directory: true,
onSelect: handleInputSelect,
onCancel: handleInputCancel, onCancel: handleInputCancel,
}); });
const { const {
getInputProps: getZipFileSelectorInputProps, getInputProps: getZipFileSelectorInputProps,
openSelector: openZipFileSelector, openSelector: openZipFileSelector,
selectedFiles: fileSelectorZipFiles,
} = useFileInput({ } = useFileInput({
directory: false, directory: false,
accept: ".zip", accept: ".zip",
onSelect: handleInputSelect,
onCancel: handleInputCancel, onCancel: handleInputCancel,
}); });
@ -346,15 +372,9 @@ export const Upload: React.FC<UploadProps> = ({
switch (selectedUploadType.current) { switch (selectedUploadType.current) {
case "files": case "files":
files = fileSelectorFiles;
break;
case "folders": case "folders":
files = folderSelectorFiles;
break;
case "zips": case "zips":
files = fileSelectorZipFiles; files = selectedInputFiles;
break; break;
default: default:
@ -373,12 +393,7 @@ export const Upload: React.FC<UploadProps> = ({
} else { } else {
setWebFiles(files); setWebFiles(files);
} }
}, [ }, [selectedInputFiles, dragAndDropFiles]);
dragAndDropFiles,
fileSelectorFiles,
folderSelectorFiles,
fileSelectorZipFiles,
]);
// Trigger an upload when any of the dependencies change. // Trigger an upload when any of the dependencies change.
useEffect(() => { useEffect(() => {
@ -739,6 +754,7 @@ export const Upload: React.FC<UploadProps> = ({
const handleUploadTypeSelect = (type: UploadType) => { const handleUploadTypeSelect = (type: UploadType) => {
selectedUploadType.current = type; selectedUploadType.current = type;
setIsInputPending(true);
switch (type) { switch (type) {
case "files": case "files":
openFileSelector(); openFileSelector();

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef } from "react";
interface UseFileInputParams { interface UseFileInputParams {
/** /**
@ -14,6 +14,17 @@ interface UseFileInputParams {
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept). * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept).
*/ */
accept?: string; accept?: string;
/**
* A callback that is invoked when the user selects files.
*
* It will be passed the list of {@link File}s that the user selected.
*
* This will be a list even if the user selected directories - in that case,
* it will be the recursive list of files within this directory.
*
* If the user selected no items, then {@link onCancel} will be invoked.
*/
onSelect: (selectedFiles: File[]) => void;
/** /**
* A callback that is invoked when the user cancels on the file / directory * A callback that is invoked when the user cancels on the file / directory
* dialog. * dialog.
@ -33,13 +44,6 @@ interface UseFileInputResult {
* A function that can be called to open the select file / directory dialog. * A function that can be called to open the select file / directory dialog.
*/ */
openSelector: () => void; openSelector: () => void;
/**
* The list of {@link File}s that the user selected.
*
* This will be a list even if the user selected directories - in that case,
* it will be the recursive list of files within this directory.
*/
selectedFiles: File[];
} }
/** /**
@ -55,9 +59,9 @@ interface UseFileInputResult {
export const useFileInput = ({ export const useFileInput = ({
directory, directory,
accept, accept,
onSelect,
onCancel, onCancel,
}: UseFileInputParams): UseFileInputResult => { }: UseFileInputParams): UseFileInputResult => {
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const inputRef = useRef<HTMLInputElement | undefined>(undefined); const inputRef = useRef<HTMLInputElement | undefined>(undefined);
useEffect(() => { useEffect(() => {
@ -80,7 +84,11 @@ export const useFileInput = ({
event, event,
) => { ) => {
const files = event.target.files; const files = event.target.files;
if (files) setSelectedFiles([...files]); if (files?.length) {
onSelect([...files]);
} else {
onCancel();
}
}; };
// [Note: webkitRelativePath] // [Note: webkitRelativePath]
@ -111,5 +119,5 @@ export const useFileInput = ({
[directoryOpts, accept, handleChange], [directoryOpts, accept, handleChange],
); );
return { getInputProps, openSelector, selectedFiles }; return { getInputProps, openSelector };
}; };