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([]); const inputRef = useRef(); const openSelectorDialog = useCallback(() => { if (inputRef.current) { inputRef.current.value = null; inputRef.current.click(); } }, []); const handleChange: React.ChangeEventHandler = async ( event, ) => { if (!!event.target && !!event.target.files) { setSelectedFiles([...event.target.files]); } }; // [Note: webkitRelativePath] // // If the webkitdirectory attribute of an 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, }; }