diff --git a/web/apps/photos/src/components/UploadSelectorInputs.tsx b/web/apps/photos/src/components/UploadSelectorInputs.tsx index 13e33fc6d33ff576f4006085164469f79bc6a085..e22e2f541a3ec4ad29c5b1be6486e579979cbe29 100644 --- a/web/apps/photos/src/components/UploadSelectorInputs.tsx +++ b/web/apps/photos/src/components/UploadSelectorInputs.tsx @@ -1,9 +1,24 @@ -export default function UploadSelectorInputs({ +type GetInputProps = () => React.HTMLAttributes; + +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 = ({ getDragAndDropInputProps, getFileSelectorInputProps, getFolderSelectorInputProps, getZipFileSelectorInputProps, -}) { +}) => { return ( <> @@ -14,4 +29,4 @@ export default function UploadSelectorInputs({ )} ); -} +}; diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index ba0d53d604290ef5aec61990b9011499f7131f8b..f90d1b83717de6f342a11ebac649b93c207f7131 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/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 { APPS } from "@ente/shared/apps/constants"; 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 { 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 InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; 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 { isPromise } from "@ente/shared/utils"; +import { Typography, styled } from "@mui/material"; import AuthenticateUserModal from "components/AuthenticateUserModal"; import Collections from "components/Collections"; import CollectionNamer, { CollectionNamerAttributes, } from "components/Collections/CollectionNamer"; +import CollectionSelector, { + CollectionSelectorAttributes, +} from "components/Collections/CollectionSelector"; import ExportModal from "components/ExportModal"; import { FilesDownloadProgress, @@ -85,13 +39,18 @@ import { import FixCreationTime, { FixCreationTimeAttributes, } from "components/FixCreationTime"; +import FullScreenDropZone from "components/FullScreenDropZone"; import GalleryEmptyState from "components/GalleryEmptyState"; +import { LoadingOverlay } from "components/LoadingOverlay"; +import PhotoFrame from "components/PhotoFrame"; import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList"; import SearchResultInfo from "components/Search/SearchResultInfo"; +import Sidebar from "components/Sidebar"; import Uploader from "components/Upload/Uploader"; -import UploadInputs from "components/UploadSelectorInputs"; +import { UploadSelectorInputs } from "components/UploadSelectorInputs"; import { GalleryNavbar } from "components/pages/gallery/Navbar"; import PlanSelector from "components/pages/gallery/PlanSelector"; +import SelectedFileOptions from "components/pages/gallery/SelectedFileOptions"; import { ALL_SECTION, ARCHIVE_SECTION, @@ -100,15 +59,42 @@ import { TRASH_SECTION, } from "constants/collection"; import { SYNC_INTERVAL_IN_MICROSECONDS } from "constants/gallery"; +import { t } from "i18next"; +import { useRouter } from "next/router"; 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 { constructUserIDToEmailMap } from "services/collectionService"; +import { + constructEmailList, + constructUserIDToEmailMap, + createAlbum, + getAllLatestCollections, + getAllLocalCollections, + getCollectionSummaries, + getFavItemIds, + getHiddenItemsSummary, + getSectionSummaries, +} from "services/collectionService"; import downloadManager from "services/download"; import { syncEmbeddings, syncFileEmbeddings } from "services/embeddingService"; import { syncEntities } from "services/entityService"; +import { getLocalFiles, syncFiles } from "services/fileService"; import locationSearchService from "services/locationSearchService"; import { getLocalTrashedFiles, syncTrash } from "services/trashService"; import uploadManager from "services/upload/uploadManager"; +import { + isTokenValid, + syncMapEnabled, + validateKey, +} from "services/userService"; import { Collection, CollectionSummaries } from "types/collection"; import { EnteFile } from "types/file"; import { @@ -120,6 +106,7 @@ import { } from "types/gallery"; import { Search, SearchResultSummary, UpdateSearch } from "types/search"; import { FamilyData } from "types/user"; +import { checkSubscriptionPurchase } from "utils/billing"; import { COLLECTION_OPS_TYPE, constructCollectionNameMap, @@ -131,6 +118,16 @@ import { splitNormalAndHiddenCollections, } from "utils/collection"; 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 { getSessionExpiredMessage } from "utils/ui"; import { getLocalFamilyData } from "utils/user/family"; @@ -201,8 +198,11 @@ export default function Gallery() { const [isPhotoSwipeOpen, setIsPhotoSwipeOpen] = useState(false); const { + // A function to call to get the props we should apply to the container, getRootProps: getDragAndDropRootProps, + // ... the props we should apply to the element, getInputProps: getDragAndDropInputProps, + // ... and the files that we got. acceptedFiles: dragAndDropFiles, } = useDropzone({ noClick: true, @@ -210,23 +210,23 @@ export default function Gallery() { disabled: shouldDisableDropzone, }); const { - selectedFiles: fileSelectorFiles, - open: openFileSelector, getInputProps: getFileSelectorInputProps, + openSelector: openFileSelector, + selectedFiles: fileSelectorFiles, } = useFileInput({ directory: false, }); const { - selectedFiles: folderSelectorFiles, - open: openFolderSelector, getInputProps: getFolderSelectorInputProps, + openSelector: openFolderSelector, + selectedFiles: folderSelectorFiles, } = useFileInput({ directory: true, }); const { - selectedFiles: fileSelectorZipFiles, - open: openZipFileSelector, getInputProps: getZipFileSelectorInputProps, + openSelector: openZipFileSelector, + selectedFiles: fileSelectorZipFiles, } = useFileInput({ directory: false, accept: ".zip", @@ -1013,14 +1013,14 @@ export default function Gallery() { setSelectedFiles: setSelected, }} > - - + {blockingLoad && ( diff --git a/web/apps/photos/src/pages/shared-albums/index.tsx b/web/apps/photos/src/pages/shared-albums/index.tsx index ee6284d4a2fd1c4f13f52ba7a3dbe77002e859b8..ab35b23facf1e8ebc58329ceaab7c24a754105e5 100644 --- a/web/apps/photos/src/pages/shared-albums/index.tsx +++ b/web/apps/photos/src/pages/shared-albums/index.tsx @@ -1,40 +1,11 @@ import log from "@/next/log"; +import { logoutUser } from "@ente/accounts/services/user"; +import { APPS } from "@ente/shared/apps/constants"; import { CenteredFlex, SpaceBetweenFlex, VerticallyCentered, } 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 FormPaper from "@ente/shared/components/Form/FormPaper"; 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 { ENTE_WEBSITE_LINK } from "@ente/shared/constants/urls"; 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 FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import MoreHoriz from "@mui/icons-material/MoreHoriz"; @@ -60,15 +32,35 @@ import { } from "components/FilesDownloadProgress"; import FullScreenDropZone from "components/FullScreenDropZone"; import { LoadingOverlay } from "components/LoadingOverlay"; +import PhotoFrame from "components/PhotoFrame"; import { ITEM_TYPE, TimeStampListItem } from "components/PhotoList"; import UploadButton from "components/Upload/UploadButton"; import Uploader from "components/Upload/Uploader"; -import UploadSelectorInputs from "components/UploadSelectorInputs"; +import { UploadSelectorInputs } from "components/UploadSelectorInputs"; import SharedAlbumNavbar from "components/pages/sharedAlbum/Navbar"; import SelectedFileOptions from "components/pages/sharedAlbum/SelectedFileOptions"; +import { ALL_SECTION } from "constants/collection"; +import { t } from "i18next"; import { useRouter } from "next/router"; +import { AppContext } from "pages/_app"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useDropzone } from "react-dropzone"; 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 { SelectedState, SetFilesDownloadProgressAttributes, @@ -76,6 +68,13 @@ import { UploadTypeSelectorIntent, } from "types/gallery"; import { downloadCollectionFiles, isHiddenCollection } from "utils/collection"; +import { + downloadSelectedFiles, + getSelectedFiles, + mergeMetadata, + sortFiles, +} from "utils/file"; +import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; export default function PublicCollectionGallery() { const token = useRef(null); @@ -118,16 +117,16 @@ export default function PublicCollectionGallery() { disabled: shouldDisableDropzone, }); const { - selectedFiles: fileSelectorFiles, - open: openFileSelector, getInputProps: getFileSelectorInputProps, + openSelector: openFileSelector, + selectedFiles: fileSelectorFiles, } = useFileInput({ directory: false, }); const { - selectedFiles: folderSelectorFiles, - open: openFolderSelector, getInputProps: getFolderSelectorInputProps, + openSelector: openFolderSelector, + selectedFiles: folderSelectorFiles, } = useFileInput({ directory: true, }); @@ -543,14 +542,13 @@ export default function PublicCollectionGallery() { photoListFooter, }} > - + React.HTMLAttributes; + /** + * 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, accept, -}: UseFileInputParams) { +}: UseFileInputParams): UseFileInputResult => { const [selectedFiles, setSelectedFiles] = useState([]); const inputRef = useRef(); - const openSelectorDialog = useCallback(() => { + const openSelector = useCallback(() => { if (inputRef.current) { inputRef.current.value = null; inputRef.current.click(); } }, []); - const handleChange: React.ChangeEventHandler = async ( + const handleChange: React.ChangeEventHandler = ( event, ) => { - if (!!event.target && !!event.target.files) { - setSelectedFiles([...event.target.files]); - } + const files = event.target?.files; + if (files) setSelectedFiles([...files]); }; // [Note: webkitRelativePath] @@ -78,12 +93,8 @@ export default function useFileInput({ onChange: handleChange, ...(accept ? { accept } : {}), }), - [], + [directoryOpts, accept], ); - return { - getInputProps, - open: openSelectorDialog, - selectedFiles: selectedFiles, - }; -} + return { getInputProps, openSelector, selectedFiles }; +};