diff --git a/web/apps/photos/src/components/UploadSelectorInputs.tsx b/web/apps/photos/src/components/UploadSelectorInputs.tsx index 13e33fc6d..e22e2f541 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 ba0d53d60..f90d1b837 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 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 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, 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 { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; -import { CustomError } from "@ente/shared/error"; -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"; + 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 ee6284d4a..ab35b23fa 100644 --- a/web/apps/photos/src/pages/shared-albums/index.tsx +++ b/web/apps/photos/src/pages/shared-albums/index.tsx @@ -1,15 +1,51 @@ 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 EnteSpinner from "@ente/shared/components/EnteSpinner"; +import FormPaper from "@ente/shared/components/Form/FormPaper"; +import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title"; +import OverflowMenu from "@ente/shared/components/OverflowMenu/menu"; +import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option"; +import SingleInputForm, { + SingleInputFormProps, +} from "@ente/shared/components/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 { 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"; +import Typography from "@mui/material/Typography"; +import bs58 from "bs58"; +import { CollectionInfo } from "components/Collections/CollectionInfo"; +import { CollectionInfoBarWrapper } from "components/Collections/styledComponents"; +import { + FilesDownloadProgress, + FilesDownloadProgressAttributes, +} 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 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, @@ -25,50 +61,6 @@ import { } 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"; -import OverflowMenu from "@ente/shared/components/OverflowMenu/menu"; -import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option"; -import SingleInputForm, { - SingleInputFormProps, -} from "@ente/shared/components/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 AddPhotoAlternateOutlined from "@mui/icons-material/AddPhotoAlternateOutlined"; -import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; -import MoreHoriz from "@mui/icons-material/MoreHoriz"; -import Typography from "@mui/material/Typography"; -import bs58 from "bs58"; -import { CollectionInfo } from "components/Collections/CollectionInfo"; -import { CollectionInfoBarWrapper } from "components/Collections/styledComponents"; -import { - FilesDownloadProgress, - FilesDownloadProgressAttributes, -} from "components/FilesDownloadProgress"; -import FullScreenDropZone from "components/FullScreenDropZone"; -import { LoadingOverlay } from "components/LoadingOverlay"; -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 SharedAlbumNavbar from "components/pages/sharedAlbum/Navbar"; -import SelectedFileOptions from "components/pages/sharedAlbum/SelectedFileOptions"; -import { useRouter } from "next/router"; -import { useDropzone } from "react-dropzone"; -import downloadManager from "services/download"; 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: + * Wrap a open file selector into an easy to use package. * - * - A function that can be called to trigger the showing of the select file / - * directory dialog. + * 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. * - * - 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). + * 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 }; +};