diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index 4eaad44e91..9406779c0b 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -20,7 +20,6 @@ import { } from "ente-new/photos/components/Tiles"; import type { CollectionSummary } from "ente-new/photos/services/collection/ui"; import { CollectionsSortBy } from "ente-new/photos/services/collection/ui"; -import { FlexWrapper } from "ente-shared/components/Container"; import { t } from "i18next"; import memoize from "memoize-one"; import React, { useEffect, useRef, useState } from "react"; @@ -168,7 +167,7 @@ const AlbumsRow = React.memo( const collectionRow = collectionRowList[index]; return (
- + {collectionRow.map((item: any) => ( ))} - +
); }, diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 30320038db..3d3fe1ed87 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -12,18 +12,10 @@ import Photo, { default as PhotoIcon } from "@mui/icons-material/Photo"; import PublicIcon from "@mui/icons-material/Public"; import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline"; import WorkspacesIcon from "@mui/icons-material/Workspaces"; -import { - Dialog, - DialogProps, - FormHelperText, - Stack, - styled, - Typography, -} from "@mui/material"; +import { Dialog, DialogProps, Stack, styled, Typography } from "@mui/material"; import NumberAvatar from "@mui/material/Avatar"; import TextField from "@mui/material/TextField"; import Avatar from "components/pages/gallery/Avatar"; -import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { NestedSidebarDrawer, @@ -43,6 +35,7 @@ import { useClipboardCopy } from "ente-base/components/utils/hooks"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; import { sharedCryptoWorker } from "ente-base/crypto"; +import { isHTTP4xxError } from "ente-base/http"; import { formattedDateTime } from "ente-base/i18n-date"; import log from "ente-base/log"; import { appendCollectionKeyToShareURL } from "ente-gallery/services/share"; @@ -522,6 +515,9 @@ const AddParticipant: React.FC = ({ await shareCollection(collection, email, type); await syncWithRemote(false, true); } catch (e) { + if (isHTTP4xxError(e)) { + throw new Error(t("sharing_user_does_not_exist")); + } const errorMessage = handleSharingErrors(e); throw new Error(errorMessage); } @@ -549,15 +545,11 @@ const AddParticipant: React.FC = ({ onClose={onClose} callback={collectionShare} optionsList={nonSharedEmails} - placeholder={t("enter_email")} - fieldType="email" buttonText={ type == "VIEWER" ? t("add_viewers") : t("add_collaborators") } - submitButtonProps={{ size: "large", sx: { mt: 1, mb: 2 } }} - disableAutoFocus /> @@ -571,27 +563,12 @@ interface AddParticipantFormValues { interface AddParticipantFormProps { callback: (props: { email?: string; emails?: string[] }) => Promise; - fieldType: "text" | "email" | "password"; - placeholder: string; buttonText: string; - submitButtonProps?: any; - initialValue?: string; - secondaryButtonAction?: () => void; - disableAutoFocus?: boolean; - hiddenPreInput?: any; - caption?: any; - hiddenPostInput?: any; - autoComplete?: string; - blockButton?: boolean; - hiddenLabel?: boolean; onClose?: () => void; optionsList?: string[]; } const AddParticipantForm: React.FC = (props) => { - const { submitButtonProps } = props; - const { sx: buttonSx, ...restSubmitButtonProps } = submitButtonProps ?? {}; - const [loading, SetLoading] = useState(false); const submitForm = async ( @@ -615,17 +592,10 @@ const AddParticipantForm: React.FC = (props) => { }; const validationSchema = useMemo(() => { - switch (props.fieldType) { - case "text": - return Yup.object().shape({ - inputValue: Yup.string().required(t("required")), - }); - case "email": - return Yup.object().shape({ - inputValue: Yup.string().email(t("invalid_email_error")), - }); - } - }, [props.fieldType]); + return Yup.object().shape({ + inputValue: Yup.string().email(t("invalid_email_error")), + }); + }, []); const handleInputFieldClick = (setFieldValue) => { setFieldValue("selectedOptions", []); @@ -633,10 +603,7 @@ const AddParticipantForm: React.FC = (props) => { return ( - initialValues={{ - inputValue: props.initialValue ?? "", - selectedOptions: [], - }} + initialValues={{ inputValue: "", selectedOptions: [] }} onSubmit={submitForm} validationSchema={validationSchema} validateOnChange={false} @@ -651,31 +618,25 @@ const AddParticipantForm: React.FC = (props) => { }) => (
- {props.hiddenPreInput} {t("add_new_email")} handleInputFieldClick(setFieldValue) } - name={props.fieldType} - {...(props.hiddenLabel - ? { placeholder: props.placeholder } - : { label: props.placeholder })} error={Boolean(errors.inputValue)} helperText={errors.inputValue} value={values.inputValue} - disabled={loading} - autoFocus={!props.disableAutoFocus} - autoComplete={props.autoComplete} /> @@ -735,51 +696,19 @@ const AddParticipantForm: React.FC = (props) => { )} - - - {props.caption} - - {props.hiddenPostInput} - {props.secondaryButtonAction && ( - - {t("cancel")} - - )} - {props.buttonText} diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 0fad18bef2..ec828e1d1a 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -27,6 +27,7 @@ import { useBaseContext } from "ente-base/context"; import { basename, dirname, joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import type { CollectionMapping, Electron, ZipItem } from "ente-base/types/ipc"; +import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; import { useFileInput } from "ente-gallery/components/utils/use-file-input"; import { groupItemsBasedOnParentFolder, @@ -77,8 +78,6 @@ import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; import { SetCollectionNamerAttributes } from "./Collections/CollectionNamer"; import { UploadProgress } from "./UploadProgress"; -export type UploadTypeSelectorIntent = "upload" | "import" | "collect"; - interface UploadProps { syncWithRemote: (force?: boolean, silent?: boolean) => Promise; closeUploadTypeSelector: () => void; @@ -551,7 +550,7 @@ export const Upload: React.FC = ({ const preCollectionCreationAction = async () => { props.onCloseCollectionSelector?.(); - props.setShouldDisableDropzone(!uploadManager.shouldAllowNewUpload()); + props.setShouldDisableDropzone(uploadManager.isUploadInProgress()); setUploadPhase("preparing"); setUploadProgressView(true); }; diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index e08b346569..f777aaea92 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -15,7 +15,7 @@ import { } from "components/FilesDownloadProgress"; import { FixCreationTime } from "components/FixCreationTime"; import { Sidebar } from "components/Sidebar"; -import { Upload, type UploadTypeSelectorIntent } from "components/Upload"; +import { Upload } from "components/Upload"; import SelectedFileOptions from "components/pages/gallery/SelectedFileOptions"; import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/dialog"; import { stashRedirect } from "ente-accounts/services/redirect"; @@ -37,6 +37,7 @@ import { masterKeyFromSessionIfLoggedIn, } from "ente-base/session"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; +import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; import { type Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; import { @@ -785,9 +786,7 @@ const Page: React.FC = () => { }; const openUploader = (intent?: UploadTypeSelectorIntent) => { - if (!uploadManager.shouldAllowNewUpload()) { - return; - } + if (uploadManager.isUploadInProgress()) return; setUploadTypeSelectorView(true); setUploadTypeSelectorIntent(intent ?? "upload"); }; @@ -1095,8 +1094,8 @@ const Page: React.FC = () => { !hiddenFiles?.length && activeCollectionID === ALL_SECTION ? ( ) : !isInSearchMode && !isFirstLoad && @@ -1218,7 +1217,7 @@ const SidebarButton: React.FC = ({ onClick }) => ( ); const UploadButton: React.FC = ({ onClick }) => { - const disabled = !uploadManager.shouldAllowNewUpload(); + const disabled = uploadManager.isUploadInProgress(); const isSmallWidth = useIsSmallWidth(); const icon = ; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 61c3bca479..6452c1cc12 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -558,7 +558,7 @@ const EnteLogoLink = styled("a")(({ theme }) => ({ })); const AddPhotosButton: React.FC = ({ onClick }) => { - const disabled = !uploadManager.shouldAllowNewUpload(); + const disabled = uploadManager.isUploadInProgress(); const isSmallWidth = useIsSmallWidth(); const icon = ; @@ -585,7 +585,7 @@ const AddPhotosButton: React.FC = ({ onClick }) => { * shrink on mobile sized screens. */ const AddMorePhotosButton: React.FC = ({ onClick }) => { - const disabled = !uploadManager.shouldAllowNewUpload(); + const disabled = uploadManager.isUploadInProgress(); return ( { - return !this.uploadInProgress || watcher.isUploadRunning(); + /** + * `true` if an upload is currently in-progress (either a bunch of files + * directly uploaded by the user, or files being uploaded by the folder + * watch functionality). + */ + public isUploadInProgress = () => { + return this.uploadInProgress || watcher.isUploadRunning(); }; } diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index ee68eddcd9..c9611ee3d4 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -22,7 +22,6 @@ import { ShowHidePasswordInputAdornment } from "ente-base/components/mui/Passwor import { isMuseumHTTPError } from "ente-base/http"; import log from "ente-base/log"; import { setLSUser } from "ente-shared//storage/localStorage"; -import { VerticallyCentered } from "ente-shared/components/Container"; import { generateAndSaveIntermediateKeyAttributes, saveKeyInSessionStore, @@ -149,7 +148,7 @@ export const SignUpContents: React.FC = ({ handleSubmit, }): React.JSX.Element => ( - + = ({ } /> - + { )} - + { > {!ottInputVisible ? t("send_otp") : t("verify")} - + diff --git a/web/packages/gallery/components/Upload.tsx b/web/packages/gallery/components/Upload.tsx new file mode 100644 index 0000000000..7f723875cf --- /dev/null +++ b/web/packages/gallery/components/Upload.tsx @@ -0,0 +1,7 @@ +/** + * The upload can be triggered by different buttons and flows in the UI, each of + * which is referred to as an "intent". + * + * The "intent" does not change the eventual upload outcome, only the UX flow. + */ +export type UploadTypeSelectorIntent = "upload" | "import" | "collect"; diff --git a/web/packages/new/photos/components/gallery/index.tsx b/web/packages/new/photos/components/gallery/index.tsx index ee8aad7040..66edbedb6b 100644 --- a/web/packages/new/photos/components/gallery/index.tsx +++ b/web/packages/new/photos/components/gallery/index.tsx @@ -7,11 +7,17 @@ * there. */ -import { Paper, Stack, Typography } from "@mui/material"; +import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; +import FolderIcon from "@mui/icons-material/FolderOutlined"; +import { Paper, Stack, styled, Typography } from "@mui/material"; import { CenteredFill } from "ente-base/components/containers"; +import { EnteLogo } from "ente-base/components/EnteLogo"; +import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; +import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; import type { SearchSuggestion } from "ente-new/photos/services/search/types"; import { t } from "i18next"; import React, { useState } from "react"; +import { Trans } from "react-i18next"; import { enableML } from "../../services/ml"; import { EnableML, FaceConsent } from "../sidebar/MLSettings"; import { useMLStatusSnapshot } from "../utils/use-snapshot"; @@ -51,93 +57,84 @@ export const SearchResultsHeader: React.FC = ({ ); -import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; -import FolderIcon from "@mui/icons-material/FolderOutlined"; -import { Button, styled } from "@mui/material"; -import { EnteLogo } from "ente-base/components/EnteLogo"; -import { - FlexWrapper, - VerticallyCentered, -} from "ente-shared/components/Container"; -import { Trans } from "react-i18next"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export function GalleryEmptyState({ openUploader, shouldAllowNewUpload }) { - return ( - - - - - }} - /> - - - {t("welcome_to_ente_subtitle")} - - - - - - - - - - ); +interface GalleryEmptyStateProps { + /** + * If `true`, then an upload is already in progress (the empty state will + * then disable the prompts for uploads). + */ + isUploadInProgress: boolean; + /** + * Called when the user selects one of the upload buttons. It is passed the + * "intent" of the user. + */ + onUpload: (intent: UploadTypeSelectorIntent) => void; } -const Wrapper = styled("div")` - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -`; +export const GalleryEmptyState: React.FC = ({ + isUploadInProgress, + onUpload, +}) => ( + + + + }} + /> + + + {t("welcome_to_ente_subtitle")} + + + + + onUpload("upload")} + disabled={isUploadInProgress} + sx={{ p: 1 }} + > + + + {t("upload_first_photo")} + + + onUpload("import")} + disabled={isUploadInProgress} + sx={{ p: 1 }} + > + + + {t("import_your_folders")} + + + + +); /** * Prevent the image from being selected _and_ dragged, since dragging it diff --git a/web/packages/shared/components/Container.tsx b/web/packages/shared/components/Container.tsx index 63b9bf82b5..6d6f935d7e 100644 --- a/web/packages/shared/components/Container.tsx +++ b/web/packages/shared/components/Container.tsx @@ -1,25 +1,7 @@ import { Box, styled } from "@mui/material"; -export const VerticallyCentered = styled(Box)` - flex: 1; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - text-align: center; - overflow: auto; -`; - export const FlexWrapper = styled(Box)` display: flex; width: 100%; align-items: center; `; - -/** - * Deprecated, use {@link SpacedRow} from ente-base/components/mui/container - * instead - */ -export const SpaceBetweenFlex = styled(FlexWrapper)` - justify-content: space-between; -`;