ente/web/packages/shared/hooks/useFileInput.tsx
2024-04-30 13:01:29 +05:30

109 lines
3.6 KiB
TypeScript

import { useCallback, useRef, useState } from "react";
/**
* [Note: File paths when running under Electron]
*
* We have access to the absolute path of the web {@link File} object when we
* are running in the context of our desktop app.
*
* https://www.electronjs.org/docs/latest/api/file-object
*
* This is in contrast to the `webkitRelativePath` that we get when we're
* running in the browser, which is the relative path to the directory that the
* user selected (or just the name of the file if the user selected or
* drag/dropped a single one).
*
* Note that this is a deprecated approach. From Electron docs:
*
* > Warning: The path property that Electron adds to the File interface is
* > deprecated and will be removed in a future Electron release. We recommend
* > you use `webUtils.getPathForFile` instead.
*/
export interface FileWithPath extends File {
readonly path?: string;
}
interface UseFileInputParams {
directory?: boolean;
accept?: string;
}
/**
* Return three things:
*
* - A function that can be called to trigger the showing of the select file /
* directory dialog.
*
* - The list of properties that should be passed to a dummy `input` element
* that needs to be created to anchor the select file dialog. This input HTML
* element is not going to be visible, but it needs to be part of the DOM fro
* the open trigger to have effect.
*
* - The list of files 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.
*
* @param param0
*
* - If {@link directory} is true, the file open dialog will ask the user to
* select directories. Otherwise it'll ask the user to select files.
*
* - If {@link accept} is specified, it'll restrict the type of files that the
* user can select by setting the "accept" attribute of the underlying HTML
* input element we use to surface the file selector dialog. For value of
* accept can be an extension or a MIME type (See
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept).
*/
export default function useFileInput({
directory,
accept,
}: UseFileInputParams) {
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const inputRef = useRef<HTMLInputElement>();
const openSelectorDialog = useCallback(() => {
if (inputRef.current) {
inputRef.current.value = null;
inputRef.current.click();
}
}, []);
const handleChange: React.ChangeEventHandler<HTMLInputElement> = async (
event,
) => {
if (!!event.target && !!event.target.files) {
setSelectedFiles([...event.target.files]);
}
};
// [Note: webkitRelativePath]
//
// If the webkitdirectory attribute of an <input> HTML element is set then
// the File objects that we get will have `webkitRelativePath` property
// containing the relative path to the selected directory.
//
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
const directoryOpts = directory
? { directory: "", webkitdirectory: "" }
: {};
const getInputProps = useCallback(
() => ({
type: "file",
multiple: true,
style: { display: "none" },
...directoryOpts,
ref: inputRef,
onChange: handleChange,
...(accept ? { accept } : {}),
}),
[],
);
return {
getInputProps,
open: openSelectorDialog,
selectedFiles: selectedFiles,
};
}