Manav Rathi 1 tahun lalu
induk
melakukan
bedb515dc2

+ 18 - 3
web/apps/photos/src/components/UploadSelectorInputs.tsx

@@ -1,9 +1,24 @@
-export default function UploadSelectorInputs({
+type GetInputProps = () => React.HTMLAttributes<HTMLInputElement>;
+
+interface UploadSelectorInputsProps {
+    getDragAndDropInputProps: GetInputProps;
+    getFileSelectorInputProps: GetInputProps;
+    getFolderSelectorInputProps: GetInputProps;
+    getZipFileSelectorInputProps?: GetInputProps;
+}
+
+/**
+ * Create a bunch of HTML inputs elements, one each for the given props.
+ *
+ * These hidden input element serve as the way for us to show various file /
+ * folder Selector dialogs and handle drag and drop inputs.
+ */
+export const UploadSelectorInputs: React.FC<UploadSelectorInputsProps> = ({
     getDragAndDropInputProps,
     getDragAndDropInputProps,
     getFileSelectorInputProps,
     getFileSelectorInputProps,
     getFolderSelectorInputProps,
     getFolderSelectorInputProps,
     getZipFileSelectorInputProps,
     getZipFileSelectorInputProps,
-}) {
+}) => {
     return (
     return (
         <>
         <>
             <input {...getDragAndDropInputProps()} />
             <input {...getDragAndDropInputProps()} />
@@ -14,4 +29,4 @@ export default function UploadSelectorInputs({
             )}
             )}
         </>
         </>
     );
     );
-}
+};

+ 80 - 80
web/apps/photos/src/pages/gallery/index.tsx

@@ -1,82 +1,36 @@
-import {
-    SESSION_KEYS,
-    clearKeys,
-    getKey,
-} from "@ente/shared/storage/sessionStorage";
-import { Typography, styled } from "@mui/material";
-import { t } from "i18next";
-import { useRouter } from "next/router";
-import {
-    createContext,
-    useContext,
-    useEffect,
-    useMemo,
-    useRef,
-    useState,
-} from "react";
-import {
-    constructEmailList,
-    createAlbum,
-    getAllLatestCollections,
-    getAllLocalCollections,
-    getCollectionSummaries,
-    getFavItemIds,
-    getHiddenItemsSummary,
-    getSectionSummaries,
-} from "services/collectionService";
-import { getLocalFiles, syncFiles } from "services/fileService";
-
-import { checkSubscriptionPurchase } from "utils/billing";
-
-import EnteSpinner from "@ente/shared/components/EnteSpinner";
-import {
-    isFirstLogin,
-    justSignedUp,
-    setIsFirstLogin,
-    setJustSignedUp,
-} from "@ente/shared/storage/localStorage/helpers";
-import CollectionSelector, {
-    CollectionSelectorAttributes,
-} from "components/Collections/CollectionSelector";
-import FullScreenDropZone from "components/FullScreenDropZone";
-import { LoadingOverlay } from "components/LoadingOverlay";
-import PhotoFrame from "components/PhotoFrame";
-import Sidebar from "components/Sidebar";
-import SelectedFileOptions from "components/pages/gallery/SelectedFileOptions";
-import { useDropzone } from "react-dropzone";
-import {
-    isTokenValid,
-    syncMapEnabled,
-    validateKey,
-} from "services/userService";
-import { preloadImage } from "utils/common";
-import {
-    FILE_OPS_TYPE,
-    constructFileToCollectionMap,
-    getSelectedFiles,
-    getUniqueFiles,
-    handleFileOps,
-    mergeMetadata,
-    sortFiles,
-} from "utils/file";
-
 import log from "@/next/log";
 import log from "@/next/log";
 import { APPS } from "@ente/shared/apps/constants";
 import { APPS } from "@ente/shared/apps/constants";
 import { CenteredFlex } from "@ente/shared/components/Container";
 import { CenteredFlex } from "@ente/shared/components/Container";
+import EnteSpinner from "@ente/shared/components/EnteSpinner";
 import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
 import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
 import { CustomError } from "@ente/shared/error";
 import { CustomError } from "@ente/shared/error";
-import useFileInput from "@ente/shared/hooks/useFileInput";
+import { useFileInput } from "@ente/shared/hooks/useFileInput";
 import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
 import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
 import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
 import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
 import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
 import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
-import { getToken } from "@ente/shared/storage/localStorage/helpers";
+import {
+    getToken,
+    isFirstLogin,
+    justSignedUp,
+    setIsFirstLogin,
+    setJustSignedUp,
+} from "@ente/shared/storage/localStorage/helpers";
+import {
+    SESSION_KEYS,
+    clearKeys,
+    getKey,
+} from "@ente/shared/storage/sessionStorage";
 import { User } from "@ente/shared/user/types";
 import { User } from "@ente/shared/user/types";
 import { isPromise } from "@ente/shared/utils";
 import { isPromise } from "@ente/shared/utils";
+import { Typography, styled } from "@mui/material";
 import AuthenticateUserModal from "components/AuthenticateUserModal";
 import AuthenticateUserModal from "components/AuthenticateUserModal";
 import Collections from "components/Collections";
 import Collections from "components/Collections";
 import CollectionNamer, {
 import CollectionNamer, {
     CollectionNamerAttributes,
     CollectionNamerAttributes,
 } from "components/Collections/CollectionNamer";
 } from "components/Collections/CollectionNamer";
+import CollectionSelector, {
+    CollectionSelectorAttributes,
+} from "components/Collections/CollectionSelector";
 import ExportModal from "components/ExportModal";
 import ExportModal from "components/ExportModal";
 import {
 import {
     FilesDownloadProgress,
     FilesDownloadProgress,
@@ -85,13 +39,18 @@ import {
 import FixCreationTime, {
 import FixCreationTime, {
     FixCreationTimeAttributes,
     FixCreationTimeAttributes,
 } from "components/FixCreationTime";
 } from "components/FixCreationTime";
+import FullScreenDropZone from "components/FullScreenDropZone";
 import GalleryEmptyState from "components/GalleryEmptyState";
 import GalleryEmptyState from "components/GalleryEmptyState";
+import { LoadingOverlay } from "components/LoadingOverlay";
+import PhotoFrame from "components/PhotoFrame";
 import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList";
 import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList";
 import SearchResultInfo from "components/Search/SearchResultInfo";
 import SearchResultInfo from "components/Search/SearchResultInfo";
+import Sidebar from "components/Sidebar";
 import Uploader from "components/Upload/Uploader";
 import Uploader from "components/Upload/Uploader";
-import UploadInputs from "components/UploadSelectorInputs";
+import { UploadSelectorInputs } from "components/UploadSelectorInputs";
 import { GalleryNavbar } from "components/pages/gallery/Navbar";
 import { GalleryNavbar } from "components/pages/gallery/Navbar";
 import PlanSelector from "components/pages/gallery/PlanSelector";
 import PlanSelector from "components/pages/gallery/PlanSelector";
+import SelectedFileOptions from "components/pages/gallery/SelectedFileOptions";
 import {
 import {
     ALL_SECTION,
     ALL_SECTION,
     ARCHIVE_SECTION,
     ARCHIVE_SECTION,
@@ -100,15 +59,42 @@ import {
     TRASH_SECTION,
     TRASH_SECTION,
 } from "constants/collection";
 } from "constants/collection";
 import { SYNC_INTERVAL_IN_MICROSECONDS } from "constants/gallery";
 import { SYNC_INTERVAL_IN_MICROSECONDS } from "constants/gallery";
+import { t } from "i18next";
+import { useRouter } from "next/router";
 import { AppContext } from "pages/_app";
 import { AppContext } from "pages/_app";
+import {
+    createContext,
+    useContext,
+    useEffect,
+    useMemo,
+    useRef,
+    useState,
+} from "react";
+import { useDropzone } from "react-dropzone";
 import { clipService } from "services/clip-service";
 import { clipService } from "services/clip-service";
-import { constructUserIDToEmailMap } from "services/collectionService";
+import {
+    constructEmailList,
+    constructUserIDToEmailMap,
+    createAlbum,
+    getAllLatestCollections,
+    getAllLocalCollections,
+    getCollectionSummaries,
+    getFavItemIds,
+    getHiddenItemsSummary,
+    getSectionSummaries,
+} from "services/collectionService";
 import downloadManager from "services/download";
 import downloadManager from "services/download";
 import { syncEmbeddings, syncFileEmbeddings } from "services/embeddingService";
 import { syncEmbeddings, syncFileEmbeddings } from "services/embeddingService";
 import { syncEntities } from "services/entityService";
 import { syncEntities } from "services/entityService";
+import { getLocalFiles, syncFiles } from "services/fileService";
 import locationSearchService from "services/locationSearchService";
 import locationSearchService from "services/locationSearchService";
 import { getLocalTrashedFiles, syncTrash } from "services/trashService";
 import { getLocalTrashedFiles, syncTrash } from "services/trashService";
 import uploadManager from "services/upload/uploadManager";
 import uploadManager from "services/upload/uploadManager";
+import {
+    isTokenValid,
+    syncMapEnabled,
+    validateKey,
+} from "services/userService";
 import { Collection, CollectionSummaries } from "types/collection";
 import { Collection, CollectionSummaries } from "types/collection";
 import { EnteFile } from "types/file";
 import { EnteFile } from "types/file";
 import {
 import {
@@ -120,6 +106,7 @@ import {
 } from "types/gallery";
 } from "types/gallery";
 import { Search, SearchResultSummary, UpdateSearch } from "types/search";
 import { Search, SearchResultSummary, UpdateSearch } from "types/search";
 import { FamilyData } from "types/user";
 import { FamilyData } from "types/user";
+import { checkSubscriptionPurchase } from "utils/billing";
 import {
 import {
     COLLECTION_OPS_TYPE,
     COLLECTION_OPS_TYPE,
     constructCollectionNameMap,
     constructCollectionNameMap,
@@ -131,6 +118,16 @@ import {
     splitNormalAndHiddenCollections,
     splitNormalAndHiddenCollections,
 } from "utils/collection";
 } from "utils/collection";
 import ComlinkSearchWorker from "utils/comlink/ComlinkSearchWorker";
 import ComlinkSearchWorker from "utils/comlink/ComlinkSearchWorker";
+import { preloadImage } from "utils/common";
+import {
+    FILE_OPS_TYPE,
+    constructFileToCollectionMap,
+    getSelectedFiles,
+    getUniqueFiles,
+    handleFileOps,
+    mergeMetadata,
+    sortFiles,
+} from "utils/file";
 import { isArchivedFile } from "utils/magicMetadata";
 import { isArchivedFile } from "utils/magicMetadata";
 import { getSessionExpiredMessage } from "utils/ui";
 import { getSessionExpiredMessage } from "utils/ui";
 import { getLocalFamilyData } from "utils/user/family";
 import { getLocalFamilyData } from "utils/user/family";
@@ -201,8 +198,11 @@ export default function Gallery() {
     const [isPhotoSwipeOpen, setIsPhotoSwipeOpen] = useState(false);
     const [isPhotoSwipeOpen, setIsPhotoSwipeOpen] = useState(false);
 
 
     const {
     const {
+        // A function to call to get the props we should apply to the container,
         getRootProps: getDragAndDropRootProps,
         getRootProps: getDragAndDropRootProps,
+        // ... the props we should apply to the <input> element,
         getInputProps: getDragAndDropInputProps,
         getInputProps: getDragAndDropInputProps,
+        // ... and the files that we got.
         acceptedFiles: dragAndDropFiles,
         acceptedFiles: dragAndDropFiles,
     } = useDropzone({
     } = useDropzone({
         noClick: true,
         noClick: true,
@@ -210,23 +210,23 @@ export default function Gallery() {
         disabled: shouldDisableDropzone,
         disabled: shouldDisableDropzone,
     });
     });
     const {
     const {
-        selectedFiles: fileSelectorFiles,
-        open: openFileSelector,
         getInputProps: getFileSelectorInputProps,
         getInputProps: getFileSelectorInputProps,
+        openSelector: openFileSelector,
+        selectedFiles: fileSelectorFiles,
     } = useFileInput({
     } = useFileInput({
         directory: false,
         directory: false,
     });
     });
     const {
     const {
-        selectedFiles: folderSelectorFiles,
-        open: openFolderSelector,
         getInputProps: getFolderSelectorInputProps,
         getInputProps: getFolderSelectorInputProps,
+        openSelector: openFolderSelector,
+        selectedFiles: folderSelectorFiles,
     } = useFileInput({
     } = useFileInput({
         directory: true,
         directory: true,
     });
     });
     const {
     const {
-        selectedFiles: fileSelectorZipFiles,
-        open: openZipFileSelector,
         getInputProps: getZipFileSelectorInputProps,
         getInputProps: getZipFileSelectorInputProps,
+        openSelector: openZipFileSelector,
+        selectedFiles: fileSelectorZipFiles,
     } = useFileInput({
     } = useFileInput({
         directory: false,
         directory: false,
         accept: ".zip",
         accept: ".zip",
@@ -1013,14 +1013,14 @@ export default function Gallery() {
                 setSelectedFiles: setSelected,
                 setSelectedFiles: setSelected,
             }}
             }}
         >
         >
-            <FullScreenDropZone
-                getDragAndDropRootProps={getDragAndDropRootProps}
-            >
-                <UploadInputs
-                    getDragAndDropInputProps={getDragAndDropInputProps}
-                    getFileSelectorInputProps={getFileSelectorInputProps}
-                    getFolderSelectorInputProps={getFolderSelectorInputProps}
-                    getZipFileSelectorInputProps={getZipFileSelectorInputProps}
+            <FullScreenDropZone {...{ getDragAndDropRootProps }}>
+                <UploadSelectorInputs
+                    {...{
+                        getDragAndDropInputProps,
+                        getFileSelectorInputProps,
+                        getFolderSelectorInputProps,
+                        getZipFileSelectorInputProps,
+                    }}
                 />
                 />
                 {blockingLoad && (
                 {blockingLoad && (
                     <LoadingOverlay>
                     <LoadingOverlay>

+ 42 - 44
web/apps/photos/src/pages/shared-albums/index.tsx

@@ -1,40 +1,11 @@
 import log from "@/next/log";
 import log from "@/next/log";
+import { logoutUser } from "@ente/accounts/services/user";
+import { APPS } from "@ente/shared/apps/constants";
 import {
 import {
     CenteredFlex,
     CenteredFlex,
     SpaceBetweenFlex,
     SpaceBetweenFlex,
     VerticallyCentered,
     VerticallyCentered,
 } from "@ente/shared/components/Container";
 } from "@ente/shared/components/Container";
-import { CustomError, parseSharingErrorCodes } from "@ente/shared/error";
-import PhotoFrame from "components/PhotoFrame";
-import { ALL_SECTION } from "constants/collection";
-import { t } from "i18next";
-import { AppContext } from "pages/_app";
-import { useContext, useEffect, useMemo, useRef, useState } from "react";
-import {
-    getLocalPublicCollection,
-    getLocalPublicCollectionPassword,
-    getLocalPublicFiles,
-    getPublicCollection,
-    getPublicCollectionUID,
-    getReferralCode,
-    removePublicCollectionWithFiles,
-    removePublicFiles,
-    savePublicCollectionPassword,
-    syncPublicFiles,
-    verifyPublicCollectionPassword,
-} from "services/publicCollectionService";
-import { Collection } from "types/collection";
-import { EnteFile } from "types/file";
-import {
-    downloadSelectedFiles,
-    getSelectedFiles,
-    mergeMetadata,
-    sortFiles,
-} from "utils/file";
-import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
-
-import { logoutUser } from "@ente/accounts/services/user";
-import { APPS } from "@ente/shared/apps/constants";
 import EnteSpinner from "@ente/shared/components/EnteSpinner";
 import EnteSpinner from "@ente/shared/components/EnteSpinner";
 import FormPaper from "@ente/shared/components/Form/FormPaper";
 import FormPaper from "@ente/shared/components/Form/FormPaper";
 import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
 import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
@@ -46,7 +17,8 @@ import SingleInputForm, {
 import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
 import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
 import { ENTE_WEBSITE_LINK } from "@ente/shared/constants/urls";
 import { ENTE_WEBSITE_LINK } from "@ente/shared/constants/urls";
 import ComlinkCryptoWorker from "@ente/shared/crypto";
 import ComlinkCryptoWorker from "@ente/shared/crypto";
-import useFileInput from "@ente/shared/hooks/useFileInput";
+import { CustomError, parseSharingErrorCodes } from "@ente/shared/error";
+import { useFileInput } from "@ente/shared/hooks/useFileInput";
 import AddPhotoAlternateOutlined from "@mui/icons-material/AddPhotoAlternateOutlined";
 import AddPhotoAlternateOutlined from "@mui/icons-material/AddPhotoAlternateOutlined";
 import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
 import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
 import MoreHoriz from "@mui/icons-material/MoreHoriz";
 import MoreHoriz from "@mui/icons-material/MoreHoriz";
@@ -60,15 +32,35 @@ import {
 } from "components/FilesDownloadProgress";
 } from "components/FilesDownloadProgress";
 import FullScreenDropZone from "components/FullScreenDropZone";
 import FullScreenDropZone from "components/FullScreenDropZone";
 import { LoadingOverlay } from "components/LoadingOverlay";
 import { LoadingOverlay } from "components/LoadingOverlay";
+import PhotoFrame from "components/PhotoFrame";
 import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList";
 import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList";
 import UploadButton from "components/Upload/UploadButton";
 import UploadButton from "components/Upload/UploadButton";
 import Uploader from "components/Upload/Uploader";
 import Uploader from "components/Upload/Uploader";
-import UploadSelectorInputs from "components/UploadSelectorInputs";
+import { UploadSelectorInputs } from "components/UploadSelectorInputs";
 import SharedAlbumNavbar from "components/pages/sharedAlbum/Navbar";
 import SharedAlbumNavbar from "components/pages/sharedAlbum/Navbar";
 import SelectedFileOptions from "components/pages/sharedAlbum/SelectedFileOptions";
 import SelectedFileOptions from "components/pages/sharedAlbum/SelectedFileOptions";
+import { ALL_SECTION } from "constants/collection";
+import { t } from "i18next";
 import { useRouter } from "next/router";
 import { useRouter } from "next/router";
+import { AppContext } from "pages/_app";
+import { useContext, useEffect, useMemo, useRef, useState } from "react";
 import { useDropzone } from "react-dropzone";
 import { useDropzone } from "react-dropzone";
 import downloadManager from "services/download";
 import downloadManager from "services/download";
+import {
+    getLocalPublicCollection,
+    getLocalPublicCollectionPassword,
+    getLocalPublicFiles,
+    getPublicCollection,
+    getPublicCollectionUID,
+    getReferralCode,
+    removePublicCollectionWithFiles,
+    removePublicFiles,
+    savePublicCollectionPassword,
+    syncPublicFiles,
+    verifyPublicCollectionPassword,
+} from "services/publicCollectionService";
+import { Collection } from "types/collection";
+import { EnteFile } from "types/file";
 import {
 import {
     SelectedState,
     SelectedState,
     SetFilesDownloadProgressAttributes,
     SetFilesDownloadProgressAttributes,
@@ -76,6 +68,13 @@ import {
     UploadTypeSelectorIntent,
     UploadTypeSelectorIntent,
 } from "types/gallery";
 } from "types/gallery";
 import { downloadCollectionFiles, isHiddenCollection } from "utils/collection";
 import { downloadCollectionFiles, isHiddenCollection } from "utils/collection";
+import {
+    downloadSelectedFiles,
+    getSelectedFiles,
+    mergeMetadata,
+    sortFiles,
+} from "utils/file";
+import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
 
 
 export default function PublicCollectionGallery() {
 export default function PublicCollectionGallery() {
     const token = useRef<string>(null);
     const token = useRef<string>(null);
@@ -118,16 +117,16 @@ export default function PublicCollectionGallery() {
         disabled: shouldDisableDropzone,
         disabled: shouldDisableDropzone,
     });
     });
     const {
     const {
-        selectedFiles: fileSelectorFiles,
-        open: openFileSelector,
         getInputProps: getFileSelectorInputProps,
         getInputProps: getFileSelectorInputProps,
+        openSelector: openFileSelector,
+        selectedFiles: fileSelectorFiles,
     } = useFileInput({
     } = useFileInput({
         directory: false,
         directory: false,
     });
     });
     const {
     const {
-        selectedFiles: folderSelectorFiles,
-        open: openFolderSelector,
         getInputProps: getFolderSelectorInputProps,
         getInputProps: getFolderSelectorInputProps,
+        openSelector: openFolderSelector,
+        selectedFiles: folderSelectorFiles,
     } = useFileInput({
     } = useFileInput({
         directory: true,
         directory: true,
     });
     });
@@ -543,14 +542,13 @@ export default function PublicCollectionGallery() {
                 photoListFooter,
                 photoListFooter,
             }}
             }}
         >
         >
-            <FullScreenDropZone
-                getDragAndDropRootProps={getDragAndDropRootProps}
-            >
+            <FullScreenDropZone {...{ getDragAndDropRootProps }}>
                 <UploadSelectorInputs
                 <UploadSelectorInputs
-                    getDragAndDropInputProps={getDragAndDropInputProps}
-                    getFileSelectorInputProps={getFileSelectorInputProps}
-                    getFolderSelectorInputProps={getFolderSelectorInputProps}
-                    getZipFileSelectorInputProps={undefined}
+                    {...{
+                        getDragAndDropInputProps,
+                        getFileSelectorInputProps,
+                        getFolderSelectorInputProps,
+                    }}
                 />
                 />
                 <SharedAlbumNavbar
                 <SharedAlbumNavbar
                     showUploadButton={
                     showUploadButton={

+ 47 - 36
web/packages/shared/hooks/useFileInput.tsx

@@ -1,56 +1,71 @@
 import { useCallback, useRef, useState } from "react";
 import { useCallback, useRef, useState } from "react";
 
 
 interface UseFileInputParams {
 interface UseFileInputParams {
+    /**
+     * If `true`, the file open dialog will ask the user to select directories.
+     * Otherwise it'll ask the user to select files (default).
+     */
     directory?: boolean;
     directory?: boolean;
+    /**
+     * If 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).
+     */
     accept?: string;
     accept?: string;
 }
 }
 
 
+interface UseFileInputResult {
+    /**
+     * A function to call to get the 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 for {@link openSelector} to have effect.
+     */
+    getInputProps: () => React.HTMLAttributes<HTMLInputElement>;
+    /**
+     * A function that can be called to open the select file / directory dialog.
+     */
+    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[];
+}
+
 /**
 /**
- * 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.
+ * Wrap a open file selector into an easy to use package.
  *
  *
- * @param param0
+ * Returns a {@link UseFileInputResult} which contains a function to get the
+ * props for an input element, a function to open the file selector, and the
+ * list of selected files.
  *
  *
- * - 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).
+ * See the documentation of {@link UseFileInputParams} and
+ * {@link UseFileInputResult} for more details.
  */
  */
-export default function useFileInput({
+export const useFileInput = ({
     directory,
     directory,
     accept,
     accept,
-}: UseFileInputParams) {
+}: UseFileInputParams): UseFileInputResult => {
     const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
     const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
     const inputRef = useRef<HTMLInputElement>();
     const inputRef = useRef<HTMLInputElement>();
 
 
-    const openSelectorDialog = useCallback(() => {
+    const openSelector = useCallback(() => {
         if (inputRef.current) {
         if (inputRef.current) {
             inputRef.current.value = null;
             inputRef.current.value = null;
             inputRef.current.click();
             inputRef.current.click();
         }
         }
     }, []);
     }, []);
 
 
-    const handleChange: React.ChangeEventHandler<HTMLInputElement> = async (
+    const handleChange: React.ChangeEventHandler<HTMLInputElement> = (
         event,
         event,
     ) => {
     ) => {
-        if (!!event.target && !!event.target.files) {
-            setSelectedFiles([...event.target.files]);
-        }
+        const files = event.target?.files;
+        if (files) setSelectedFiles([...files]);
     };
     };
 
 
     // [Note: webkitRelativePath]
     // [Note: webkitRelativePath]
@@ -78,12 +93,8 @@ export default function useFileInput({
             onChange: handleChange,
             onChange: handleChange,
             ...(accept ? { accept } : {}),
             ...(accept ? { accept } : {}),
         }),
         }),
-        [],
+        [directoryOpts, accept],
     );
     );
 
 
-    return {
-        getInputProps,
-        open: openSelectorDialog,
-        selectedFiles: selectedFiles,
-    };
-}
+    return { getInputProps, openSelector, selectedFiles };
+};