[web] General refactoring - Uploads (#4017)

This commit is contained in:
Manav Rathi
2024-11-12 19:15:21 +05:30
committed by GitHub
20 changed files with 226 additions and 307 deletions

View File

@@ -86,7 +86,7 @@ export const CollectionShare: React.FC<CollectionShareProps> = ({
props.onClose();
};
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
handleRootClose();
} else {
props.onClose();
@@ -539,7 +539,7 @@ const AddParticipant: React.FC<AddParticipantProps> = ({
};
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
handleRootClose();
} else {
onClose();
@@ -853,7 +853,7 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
onRootClose();
};
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
handleRootClose();
} else {
onClose();
@@ -1037,7 +1037,7 @@ const ManageParticipant: React.FC<ManageParticipantProps> = ({
const galleryContext = useContext(GalleryContext);
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
onRootClose();
} else {
onClose();
@@ -1356,7 +1356,7 @@ const ManagePublicShareOptions: React.FC<ManagePublicShareOptionsProps> = ({
publicShareUrl,
}) => {
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
onRootClose();
} else {
onClose();
@@ -1552,7 +1552,7 @@ const ManageLinkExpiry: React.FC<ManageLinkExpiryProps> = ({
};
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
onRootClose();
} else {
closeShareExpiryOptionsModalView();
@@ -1684,7 +1684,7 @@ const ManageDeviceLimit: React.FC<ManageDeviceLimitProps> = ({
};
const handleDrawerClose: DialogProps["onClose"] = (_, reason) => {
if (reason === "backdropClick") {
if (reason == "backdropClick") {
onRootClose();
} else {
closeDeviceLimitChangeModal();
@@ -1897,7 +1897,7 @@ function PublicLinkSetPassword({
fullWidth
>
<Stack spacing={3} p={1.5}>
<Typography variant="h3" px={1} py={0.5} fontWeight={"bold"}>
<Typography variant="h3" fontWeight={"bold"} px={1} py={0.5}>
{t("password_lock")}
</Typography>
<SingleInputForm

View File

@@ -1,7 +1,9 @@
import { EnteSwitch } from "@/base/components/EnteSwitch";
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
import { ensureElectron } from "@/base/electron";
import log from "@/base/log";
import { EnteFile } from "@/media/file";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { useAppContext } from "@/new/photos/types/context";
import ChangeDirectoryOption from "@ente/shared/components/ChangeDirectoryOption";
import {
@@ -9,13 +11,13 @@ import {
VerticallyCenteredFlex,
} from "@ente/shared/components/Container";
import LinkButton from "@ente/shared/components/LinkButton";
import DialogTitleWithCloseButton from "@ente/shared/components/TitleWithCloseButton";
import { CustomError } from "@ente/shared/error";
import {
Box,
Button,
Dialog,
DialogContent,
DialogTitle,
Divider,
Tooltip,
Typography,
@@ -35,13 +37,15 @@ import ExportFinished from "./ExportFinished";
import ExportInProgress from "./ExportInProgress";
import ExportInit from "./ExportInit";
interface ExportModalProps {
show: boolean;
onHide: () => void;
type ExportProps = ModalVisibilityProps & {
collectionNameMap: Map<number, string>;
}
};
export default function ExportModal(props: ExportModalProps) {
export const Export: React.FC<ExportProps> = ({
open,
onClose,
collectionNameMap,
}) => {
const { showMiniDialog } = useAppContext();
const [exportStage, setExportStage] = useState(ExportStage.INIT);
const [exportFolder, setExportFolder] = useState("");
@@ -79,11 +83,11 @@ export default function ExportModal(props: ExportModalProps) {
}, []);
useEffect(() => {
if (!props.show) {
if (!open) {
return;
}
void syncExportRecord(exportFolder);
}, [props.show]);
}, [open]);
// ======================
// HELPER FUNCTIONS
@@ -166,15 +170,14 @@ export default function ExportModal(props: ExportModalProps) {
};
return (
<Dialog
open={props.show}
onClose={props.onHide}
maxWidth="xs"
fullWidth
>
<DialogTitleWithCloseButton onClose={props.onHide}>
{t("export_data")}
</DialogTitleWithCloseButton>
<Dialog {...{ open, onClose }} maxWidth="xs" fullWidth>
<SpaceBetweenFlex sx={{ p: "12px 4px 0px 0px" }}>
<DialogTitle variant="h3" fontWeight={"bold"}>
{t("export_data")}
</DialogTitle>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<DialogContent>
<ExportDirectory
exportFolder={exportFolder}
@@ -191,15 +194,15 @@ export default function ExportModal(props: ExportModalProps) {
exportStage={exportStage}
startExport={startExport}
stopExport={stopExport}
onHide={props.onHide}
onHide={onClose}
lastExportTime={lastExportTime}
exportProgress={exportProgress}
pendingExports={pendingExports}
collectionNameMap={props.collectionNameMap}
collectionNameMap={collectionNameMap}
/>
</Dialog>
);
}
};
function ExportDirectory({ exportFolder, changeExportDirectory, exportStage }) {
return (

View File

@@ -84,10 +84,11 @@ export const FixCreationTime: React.FC<FixCreationTimeProps> = ({
onClose();
}}
>
<DialogTitle>{title}</DialogTitle>
<DialogTitle sx={{ marginBlockStart: "4px" }}>{title}</DialogTitle>
<DialogContent
style={{
minWidth: "310px",
paddingBlockStart: "6px",
display: "flex",
flexDirection: "column",
...(step == "running" ? { alignItems: "center" } : {}),
@@ -235,7 +236,7 @@ const Footer: React.FC<FooterProps> = ({ step, onSubmit, onClose }) =>
style={{
width: "100%",
display: "flex",
marginTop: "30px",
marginTop: "24px",
justifyContent: "space-around",
}}
>

View File

@@ -3,6 +3,7 @@ import { openAccountsManagePasskeysPage } from "@/accounts/services/passkey";
import { isDesktop } from "@/base/app";
import { EnteLogo } from "@/base/components/EnteLogo";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { SpaceBetweenFlex } from "@/base/components/mui/Container";
import { SidebarDrawer } from "@/base/components/mui/SidebarDrawer";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useIsSmallWidth } from "@/base/hooks";
@@ -10,6 +11,7 @@ import log from "@/base/log";
import { savedLogs } from "@/base/log-web";
import { customAPIHost } from "@/base/origins";
import { downloadString } from "@/base/utils/web";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { TwoFactorSettings } from "@/new/photos/components/sidebar/TwoFactorSettings";
import { downloadAppDialogAttributes } from "@/new/photos/components/utils/download";
import { useUserDetailsSnapshot } from "@/new/photos/components/utils/use-snapshot";
@@ -41,11 +43,9 @@ import { AppContext, useAppContext } from "@/new/photos/types/context";
import { initiateEmail, openURL } from "@/new/photos/utils/web";
import {
FlexWrapper,
SpaceBetweenFlex,
VerticallyCentered,
} from "@ente/shared/components/Container";
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
import DialogTitleWithCloseButton from "@ente/shared/components/TitleWithCloseButton";
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
import { THEME_COLOR } from "@ente/shared/themes/constants";
import ArchiveOutlined from "@mui/icons-material/ArchiveOutlined";
@@ -136,10 +136,12 @@ interface HeaderSectionProps {
const HeaderSection: React.FC<HeaderSectionProps> = ({ closeSidebar }) => {
return (
<SpaceBetweenFlex mt={0.5} mb={1} pl={1.5}>
<SpaceBetweenFlex
sx={{ marginBlock: "4px 4px", paddingInlineStart: "12px" }}
>
<EnteLogo />
<IconButton
aria-label="close"
aria-label={t("close")}
onClick={closeSidebar}
color="secondary"
>
@@ -344,12 +346,17 @@ function MemberSubscriptionManage({ open, userDetails, onClose }) {
return (
<Dialog {...{ open, onClose, fullScreen }} maxWidth="xs" fullWidth>
<DialogTitleWithCloseButton onClose={onClose}>
<Typography variant="h3" fontWeight={"bold"}>
{t("subscription")}
</Typography>
<Typography color={"text.muted"}>{t("family_plan")}</Typography>
</DialogTitleWithCloseButton>
<SpaceBetweenFlex sx={{ p: "20px 8px 12px 16px" }}>
<Stack>
<Typography variant="h3" fontWeight={"bold"}>
{t("subscription")}
</Typography>
<Typography color={"text.muted"}>
{t("family_plan")}
</Typography>
</Stack>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<DialogContent>
<VerticallyCentered>
<Box mb={4}>

View File

@@ -1,4 +1,4 @@
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { type UploadPhase } from "@/new/photos/services/upload/types";
import { createContext } from "react";
import type {
InProgressUpload,
@@ -11,7 +11,7 @@ interface UploadProgressContextType {
open: boolean;
onClose: () => void;
uploadCounter: UploadCounter;
uploadStage: UPLOAD_STAGES;
uploadPhase: UploadPhase;
percentComplete: number;
retryFailed: () => void;
inProgressUploads: InProgressUpload[];
@@ -25,7 +25,7 @@ const defaultUploadProgressContext: UploadProgressContextType = {
open: null,
onClose: () => null,
uploadCounter: null,
uploadStage: null,
uploadPhase: undefined,
percentComplete: null,
retryFailed: () => null,
inProgressUploads: null,

View File

@@ -1,9 +1,5 @@
import {
UPLOAD_RESULT,
UPLOAD_STAGES,
} from "@/new/photos/services/upload/types";
import { dialogCloseHandler } from "@ente/shared/components/TitleWithCloseButton";
import { Dialog, DialogContent } from "@mui/material";
import { UPLOAD_RESULT } from "@/new/photos/services/upload/types";
import { Dialog, DialogContent, type DialogProps } from "@mui/material";
import { t } from "i18next";
import { useContext, useEffect, useState } from "react";
import { Trans } from "react-i18next";
@@ -15,7 +11,7 @@ import { ResultSection } from "./resultSection";
import { NotUploadSectionHeader } from "./styledComponents";
export function UploadProgressDialog() {
const { open, onClose, uploadStage, finishedUploads } = useContext(
const { open, onClose, uploadPhase, finishedUploads } = useContext(
UploadProgressContext,
);
@@ -37,90 +33,73 @@ export function UploadProgressDialog() {
}
}, [finishedUploads]);
const handleClose = dialogCloseHandler({ staticBackdrop: true, onClose });
const handleClose: DialogProps["onClose"] = (_, reason) => {
if (reason != "backdropClick") onClose();
};
return (
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
<UploadProgressHeader />
{(uploadStage === UPLOAD_STAGES.UPLOADING ||
uploadStage === UPLOAD_STAGES.FINISH ||
uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA) && (
{(uploadPhase == "uploading" || uploadPhase == "done") && (
<DialogContent sx={{ "&&&": { px: 0 } }}>
{(uploadStage === UPLOAD_STAGES.UPLOADING ||
uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA) && (
<InProgressSection />
)}
{(uploadStage === UPLOAD_STAGES.UPLOADING ||
uploadStage === UPLOAD_STAGES.FINISH) && (
<>
<ResultSection
uploadResult={UPLOAD_RESULT.UPLOADED}
sectionTitle={t("SUCCESSFUL_UPLOADS")}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL
}
sectionTitle={t(
"THUMBNAIL_GENERATION_FAILED_UPLOADS",
)}
sectionInfo={t(
"THUMBNAIL_GENERATION_FAILED_INFO",
)}
/>
{uploadStage === UPLOAD_STAGES.FINISH &&
hasUnUploadedFiles && (
<NotUploadSectionHeader>
{t("FILE_NOT_UPLOADED_LIST")}
</NotUploadSectionHeader>
)}
<ResultSection
uploadResult={UPLOAD_RESULT.BLOCKED}
sectionTitle={t("BLOCKED_UPLOADS")}
sectionInfo={
<Trans i18nKey={"ETAGS_BLOCKED"} />
}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.FAILED}
sectionTitle={t("FAILED_UPLOADS")}
sectionInfo={
uploadStage === UPLOAD_STAGES.FINISH
? undefined
: t("failed_uploads_hint")
}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.ALREADY_UPLOADED}
sectionTitle={t("SKIPPED_FILES")}
sectionInfo={t("SKIPPED_INFO")}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE
}
sectionTitle={t(
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS",
)}
sectionInfo={t(
"LARGER_THAN_AVAILABLE_STORAGE_INFO",
)}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.UNSUPPORTED}
sectionTitle={t("UNSUPPORTED_FILES")}
sectionInfo={t("UNSUPPORTED_INFO")}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.TOO_LARGE}
sectionTitle={t("TOO_LARGE_UPLOADS")}
sectionInfo={t("TOO_LARGE_INFO")}
/>
</>
{uploadPhase === "uploading" && <InProgressSection />}
<ResultSection
uploadResult={UPLOAD_RESULT.UPLOADED}
sectionTitle={t("SUCCESSFUL_UPLOADS")}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL
}
sectionTitle={t("THUMBNAIL_GENERATION_FAILED_UPLOADS")}
sectionInfo={t("THUMBNAIL_GENERATION_FAILED_INFO")}
/>
{uploadPhase == "done" && hasUnUploadedFiles && (
<NotUploadSectionHeader>
{t("FILE_NOT_UPLOADED_LIST")}
</NotUploadSectionHeader>
)}
<ResultSection
uploadResult={UPLOAD_RESULT.BLOCKED}
sectionTitle={t("BLOCKED_UPLOADS")}
sectionInfo={<Trans i18nKey={"ETAGS_BLOCKED"} />}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.FAILED}
sectionTitle={t("FAILED_UPLOADS")}
sectionInfo={
uploadPhase == "done"
? undefined
: t("failed_uploads_hint")
}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.ALREADY_UPLOADED}
sectionTitle={t("SKIPPED_FILES")}
sectionInfo={t("SKIPPED_INFO")}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE
}
sectionTitle={t(
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS",
)}
sectionInfo={t("LARGER_THAN_AVAILABLE_STORAGE_INFO")}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.UNSUPPORTED}
sectionTitle={t("UNSUPPORTED_FILES")}
sectionInfo={t("UNSUPPORTED_INFO")}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.TOO_LARGE}
sectionTitle={t("TOO_LARGE_UPLOADS")}
sectionInfo={t("TOO_LARGE_INFO")}
/>
</DialogContent>
)}
{uploadStage === UPLOAD_STAGES.FINISH && <UploadProgressFooter />}
{uploadPhase == "done" && <UploadProgressFooter />}
</Dialog>
);
}

View File

@@ -1,20 +1,17 @@
import {
UPLOAD_RESULT,
UPLOAD_STAGES,
} from "@/new/photos/services/upload/types";
import { UPLOAD_RESULT } from "@/new/photos/services/upload/types";
import { Button, DialogActions } from "@mui/material";
import { t } from "i18next";
import { useContext } from "react";
import UploadProgressContext from "./context";
export function UploadProgressFooter() {
const { uploadStage, finishedUploads, retryFailed, onClose } = useContext(
const { uploadPhase, finishedUploads, retryFailed, onClose } = useContext(
UploadProgressContext,
);
return (
<DialogActions>
{uploadStage === UPLOAD_STAGES.FINISH &&
{uploadPhase == "done" &&
(finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? (
<Button variant="contained" fullWidth onClick={retryFailed}>

View File

@@ -11,11 +11,10 @@ import {
} from "./section";
import { InProgressItemContainer } from "./styledComponents";
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { CaptionedText } from "components/CaptionedText";
export const InProgressSection = () => {
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadStage } =
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } =
useContext(UploadProgressContext);
const fileList = inProgressUploads ?? [];
@@ -23,7 +22,7 @@ export const InProgressSection = () => {
return (
<InProgressItemContainer key={localFileID}>
<span>{uploadFileNames.get(localFileID)}</span>
{uploadStage === UPLOAD_STAGES.UPLOADING && (
{uploadPhase == "uploading" && (
<>
{" "}
<span className="separator">{`-`}</span>
@@ -46,11 +45,7 @@ export const InProgressSection = () => {
<UploadProgressSection>
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
<CaptionedText
mainText={
uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
? t("INPROGRESS_METADATA_EXTRACTION")
: t("INPROGRESS_UPLOADS")
}
mainText={t("INPROGRESS_UPLOADS")}
subText={String(inProgressUploads?.length ?? 0)}
/>
</UploadProgressSectionTitle>

View File

@@ -1,4 +1,4 @@
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { type UploadPhase } from "@/new/photos/services/upload/types";
import { useAppContext } from "@/new/photos/types/context";
import { t } from "i18next";
import { useEffect, useState } from "react";
@@ -16,7 +16,7 @@ interface Props {
open: boolean;
onClose: () => void;
uploadCounter: UploadCounter;
uploadStage: UPLOAD_STAGES;
uploadPhase: UploadPhase;
percentComplete: number;
retryFailed: () => void;
inProgressUploads: InProgressUpload[];
@@ -29,7 +29,7 @@ interface Props {
export default function UploadProgress({
open,
uploadCounter,
uploadStage,
uploadPhase,
percentComplete,
retryFailed,
uploadFileNames,
@@ -62,7 +62,7 @@ export default function UploadProgress({
}
function onClose() {
if (uploadStage !== UPLOAD_STAGES.FINISH) {
if (uploadPhase != "done") {
confirmCancelUpload();
} else {
props.onClose();
@@ -79,7 +79,7 @@ export default function UploadProgress({
open,
onClose,
uploadCounter,
uploadStage,
uploadPhase,
percentComplete,
retryFailed,
inProgressUploads,

View File

@@ -1,15 +1,13 @@
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { Box, Divider, LinearProgress } from "@mui/material";
import { useContext } from "react";
import UploadProgressContext from "./context";
export function UploadProgressBar() {
const { uploadStage, percentComplete } = useContext(UploadProgressContext);
const { uploadPhase, percentComplete } = useContext(UploadProgressContext);
return (
<Box>
{(uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA ||
uploadStage === UPLOAD_STAGES.UPLOADING) && (
{(uploadPhase == "readingMetadata" ||
uploadPhase == "uploading") && (
<>
<LinearProgress
sx={{

View File

@@ -1,5 +1,5 @@
import { FilledIconButton } from "@/new/photos/components/mui";
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { type UploadPhase } from "@/new/photos/services/upload/types";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import Close from "@mui/icons-material/Close";
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
@@ -7,6 +7,7 @@ import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
import { Box, DialogTitle, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import { useContext } from "react";
import type { UploadCounter } from "services/upload/uploadManager";
import UploadProgressContext from "./context";
const UploadProgressTitleText = ({ expanded }) => {
@@ -18,7 +19,7 @@ const UploadProgressTitleText = ({ expanded }) => {
};
function UploadProgressSubtitleText() {
const { uploadStage, uploadCounter } = useContext(UploadProgressContext);
const { uploadPhase, uploadCounter } = useContext(UploadProgressContext);
return (
<Typography
@@ -27,15 +28,29 @@ function UploadProgressSubtitleText() {
color="text.muted"
marginTop={"4px"}
>
{uploadStage === UPLOAD_STAGES.UPLOADING
? t(`UPLOAD_STAGE_MESSAGE.${uploadStage}`, { uploadCounter })
: uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
? t(`UPLOAD_STAGE_MESSAGE.${uploadStage}`, { uploadCounter })
: t(`UPLOAD_STAGE_MESSAGE.${uploadStage}`)}
{subtitleText(uploadPhase, uploadCounter)}
</Typography>
);
}
const subtitleText = (
uploadPhase: UploadPhase,
uploadCounter: UploadCounter,
) => {
switch (uploadPhase) {
case "preparing":
return t("UPLOAD_STAGE_MESSAGE.0");
case "readingMetadata":
return t("UPLOAD_STAGE_MESSAGE.1");
case "uploading":
return t("UPLOAD_STAGE_MESSAGE.3", { uploadCounter });
case "cancelling":
return t("UPLOAD_STAGE_MESSAGE.4");
case "done":
return t("UPLOAD_STAGE_MESSAGE.5");
}
};
export function UploadProgressTitle() {
const { setExpanded, onClose, expanded } = useContext(
UploadProgressContext,

View File

@@ -3,13 +3,10 @@ import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { useIsTouchscreen } from "@/base/hooks";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
import DialogTitleWithCloseButton, {
dialogCloseHandler,
} from "@ente/shared/components/TitleWithCloseButton";
import ChevronRight from "@mui/icons-material/ChevronRight";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import GoogleIcon from "@mui/icons-material/Google";
import { default as FileUploadIcon } from "@mui/icons-material/ImageOutlined";
import { default as FolderUploadIcon } from "@mui/icons-material/PermMediaOutlined";
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
import PermMediaOutlinedIcon from "@mui/icons-material/PermMediaOutlined";
import {
Box,
Dialog,
@@ -97,7 +94,7 @@ export const UploadTypeSelector: React.FC<UploadTypeSelectorProps> = ({
[theme.breakpoints.down(360)]: { p: 0 },
}),
}}
onClose={dialogCloseHandler({ onClose: onClose })}
onClose={onClose}
>
<Options
intent={intent}
@@ -128,15 +125,13 @@ export const Options: React.FC<OptionsProps> = ({
// Keep dialog content specific state here, in a separate component, so that
// this state is not tied to the lifetime of the dialog.
//
// If we don't do this, then the dialog retains whatever it was doing when
// If we don't do this, then a MUI dialog retains whatever it was doing when
// it was last closed. Sometimes that is desirable, but sometimes not, and
// in the latter cases moving the instance specific state to a child works.
const [showTakeoutOptions, setShowTakeoutOptions] = useState(false);
const handleTakeoutClose = () => {
setShowTakeoutOptions(false);
};
const handleTakeoutClose = () => setShowTakeoutOptions(false);
const handleSelect = (option: OptionType) => {
switch (option) {
@@ -168,44 +163,45 @@ const DefaultOptions: React.FC<OptionsProps> = ({
}) => {
return (
<>
<DialogTitleWithCloseButton onClose={onClose}>
{intent == "collect"
? t("select_photos")
: intent == "import"
? t("import")
: t("upload")}
</DialogTitleWithCloseButton>
<SpaceBetweenFlex>
<DialogTitle variant="h5">
{intent == "collect"
? t("select_photos")
: intent == "import"
? t("import")
: t("upload")}
</DialogTitle>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<Box p={1.5} pt={0.5}>
<Box sx={{ p: "12px", pt: "16px" }}>
<Stack spacing={0.5}>
{intent != "import" && (
<EnteMenuItem
onClick={() => onSelect("files")}
startIcon={<FileUploadIcon />}
endIcon={<ChevronRight />}
startIcon={<ImageOutlinedIcon />}
endIcon={<ChevronRightIcon />}
label={t("file")}
/>
)}
<EnteMenuItem
onClick={() => onSelect("folders")}
startIcon={<FolderUploadIcon />}
endIcon={<ChevronRight />}
startIcon={<PermMediaOutlinedIcon />}
endIcon={<ChevronRightIcon />}
label={t("folder")}
/>
{intent !== "collect" && (
<EnteMenuItem
onClick={() => onSelect("zips")}
startIcon={<GoogleIcon />}
endIcon={<ChevronRight />}
endIcon={<ChevronRightIcon />}
label={t("google_takeout")}
/>
)}
</Stack>
<Typography
p={1.5}
pt={4}
color="text.muted"
sx={{ textAlign: "center" }}
sx={{ p: "12px", pt: "24px", textAlign: "center" }}
>
{t("drag_and_drop_hint")}
</Typography>
@@ -220,13 +216,13 @@ const TakeoutOptions: React.FC<Omit<OptionsProps, "intent">> = ({
}) => {
return (
<>
<SpaceBetweenFlex sx={{ padding: "8px 8px 0px 0" }}>
<SpaceBetweenFlex>
<DialogTitle variant="h5">{t("google_takeout")}</DialogTitle>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<Stack sx={{ padding: "12px", gap: "20px" }}>
<Stack gap={1}>
<Stack sx={{ padding: "18px 12px 20px 12px", gap: "16px" }}>
<Stack sx={{ gap: "8px" }}>
<FocusVisibleButton
color="accent"
fullWidth
@@ -252,7 +248,7 @@ const TakeoutOptions: React.FC<Omit<OptionsProps, "intent">> = ({
</Link>
</Stack>
<Typography variant="small" color="text.muted" pb={1}>
<Typography variant="small" color="text.muted">
{t("takeout_hint")}
</Typography>
</Stack>

View File

@@ -12,8 +12,8 @@ import { exportMetadataDirectoryName } from "@/new/photos/services/export";
import type {
FileAndPath,
UploadItem,
UploadPhase,
} from "@/new/photos/services/upload/types";
import { UPLOAD_STAGES } from "@/new/photos/services/upload/types";
import { redirectToCustomerPortal } from "@/new/photos/services/user-details";
import { useAppContext } from "@/new/photos/types/context";
import { NotificationAttributes } from "@/new/photos/types/notification";
@@ -121,9 +121,7 @@ export default function Uploader({
);
const [uploadProgressView, setUploadProgressView] = useState(false);
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
UPLOAD_STAGES.START,
);
const [uploadPhase, setUploadPhase] = useState<UploadPhase>("preparing");
const [uploadFileNames, setUploadFileNames] = useState<UploadFileNames>();
const [uploadCounter, setUploadCounter] = useState<UploadCounter>({
finished: 0,
@@ -245,7 +243,7 @@ export default function Uploader({
setUploadCounter,
setInProgressUploads,
setFinishedUploads,
setUploadStage,
setUploadPhase,
setUploadFilenames: setUploadFileNames,
setHasLivePhotos,
setUploadProgressView,
@@ -486,7 +484,7 @@ export default function Uploader({
const preCollectionCreationAction = async () => {
props.onCloseCollectionSelector?.();
props.setShouldDisableDropzone(!uploadManager.shouldAllowNewUpload());
setUploadStage(UPLOAD_STAGES.START);
setUploadPhase("preparing");
setUploadProgressView(true);
};
@@ -802,7 +800,7 @@ export default function Uploader({
percentComplete={percentComplete}
uploadFileNames={uploadFileNames}
uploadCounter={uploadCounter}
uploadStage={uploadStage}
uploadPhase={uploadPhase}
inProgressUploads={inProgressUploads}
hasLivePhotos={hasLivePhotos}
retryFailed={retryFailed}

View File

@@ -7,6 +7,7 @@ import { ensureElectron } from "@/base/electron";
import { basename, dirname } from "@/base/file";
import type { CollectionMapping, FolderWatch } from "@/base/types/ipc";
import { CollectionMappingChoiceDialog } from "@/new/photos/components/CollectionMappingChoiceDialog";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { AppContext, useAppContext } from "@/new/photos/types/context";
import { ensure } from "@/utils/ensure";
import {
@@ -17,7 +18,6 @@ import {
} from "@ente/shared/components/Container";
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
import DialogTitleWithCloseButton from "@ente/shared/components/TitleWithCloseButton";
import CheckIcon from "@mui/icons-material/Check";
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
import FolderCopyOutlinedIcon from "@mui/icons-material/FolderCopyOutlined";
@@ -29,6 +29,7 @@ import {
CircularProgress,
Dialog,
DialogContent,
DialogTitle,
Stack,
Tooltip,
Typography,
@@ -119,11 +120,12 @@ export const WatchFolder: React.FC<ModalVisibilityProps> = ({
fullWidth
PaperProps={{ sx: { height: "448px", maxWidth: "414px" } }}
>
<Title_>
<DialogTitleWithCloseButton onClose={onClose}>
<SpaceBetweenFlex sx={{ p: "16px 8px 8px 8px" }}>
<DialogTitle variant="h3" fontWeight={"bold"}>
{t("WATCHED_FOLDERS")}
</DialogTitleWithCloseButton>
</Title_>
</DialogTitle>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<DialogContent sx={{ flex: 1 }}>
<Stack spacing={1} p={1.5} height={"100%"}>
<WatchList {...{ watches, removeWatch }} />
@@ -147,10 +149,6 @@ export const WatchFolder: React.FC<ModalVisibilityProps> = ({
);
};
const Title_ = styled("div")`
padding: 16px 12px 16px 16px;
`;
interface WatchList {
watches: FolderWatch[] | undefined;
removeWatch: (watch: FolderWatch) => void;

View File

@@ -90,7 +90,7 @@ import CollectionNamer, {
CollectionNamerAttributes,
} from "components/Collections/CollectionNamer";
import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader";
import ExportModal from "components/ExportModal";
import { Export } from "components/Export";
import {
FilesDownloadProgress,
FilesDownloadProgressAttributes,
@@ -249,8 +249,6 @@ export default function Gallery() {
const closeSidebar = () => setSidebarView(false);
const openSidebar = () => setSidebarView(true);
const [exportModalView, setExportModalView] = useState(false);
const [authenticateUserModalView, setAuthenticateUserModalView] =
useState(false);
@@ -297,6 +295,8 @@ export default function Gallery() {
useModalVisibility();
const { show: showFixCreationTime, props: fixCreationTimeVisibilityProps } =
useModalVisibility();
const { show: showExport, props: exportVisibilityProps } =
useModalVisibility();
// TODO: Temp
const user = state.user;
@@ -496,7 +496,7 @@ export default function Gallery() {
collectionNamerView ||
planSelectorVisibilityProps.open ||
fixCreationTimeVisibilityProps.open ||
exportModalView ||
exportVisibilityProps.open ||
authenticateUserModalView ||
isPhotoSwipeOpen ||
!filteredFiles?.length ||
@@ -810,14 +810,6 @@ export default function Gallery() {
setUploadTypeSelectorIntent(intent ?? "upload");
};
const openExportModal = () => {
setExportModalView(true);
};
const closeExportModal = () => {
setExportModalView(false);
};
const handleSetActiveCollectionID = (
collectionSummaryID: number | undefined,
) =>
@@ -876,7 +868,7 @@ export default function Gallery() {
syncWithRemote,
setBlockingLoad,
photoListHeader,
openExportModal,
openExportModal: showExport,
authenticateUser,
userIDToEmailMap,
user,
@@ -1115,9 +1107,8 @@ export default function Gallery() {
isInHiddenSection={barMode == "hidden-albums"}
/>
)}
<ExportModal
show={exportModalView}
onHide={closeExportModal}
<Export
{...exportVisibilityProps}
collectionNameMap={state.allCollectionNameByID}
/>
<AuthenticateUserModal

View File

@@ -15,7 +15,7 @@ import type { UploadItem } from "@/new/photos/services/upload/types";
import {
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
UPLOAD_RESULT,
UPLOAD_STAGES,
type UploadPhase,
} from "@/new/photos/services/upload/types";
import { ensure } from "@/utils/ensure";
import { wait } from "@/utils/promise";
@@ -70,7 +70,7 @@ export type SegregatedFinishedUploads = Map<UPLOAD_RESULT, FileID[]>;
export interface ProgressUpdater {
setPercentComplete: React.Dispatch<React.SetStateAction<number>>;
setUploadCounter: React.Dispatch<React.SetStateAction<UploadCounter>>;
setUploadStage: React.Dispatch<React.SetStateAction<UPLOAD_STAGES>>;
setUploadPhase: (phase: UploadPhase) => void;
setInProgressUploads: React.Dispatch<
React.SetStateAction<InProgressUpload[]>
>;
@@ -132,7 +132,7 @@ class UIService {
private progressUpdater: ProgressUpdater;
// UPLOAD LEVEL STATES
private uploadStage: UPLOAD_STAGES = UPLOAD_STAGES.START;
private uploadPhase: UploadPhase = "preparing";
private filenames: Map<number, string> = new Map();
private hasLivePhoto: boolean = false;
private uploadProgressView: boolean = false;
@@ -146,7 +146,7 @@ class UIService {
init(progressUpdater: ProgressUpdater) {
this.progressUpdater = progressUpdater;
this.progressUpdater.setUploadStage(this.uploadStage);
this.progressUpdater.setUploadPhase(this.uploadPhase);
this.progressUpdater.setUploadFilenames(this.filenames);
this.progressUpdater.setHasLivePhotos(this.hasLivePhoto);
this.progressUpdater.setUploadProgressView(this.uploadProgressView);
@@ -184,9 +184,9 @@ class UIService {
this.updateProgressBarUI();
}
setUploadStage(stage: UPLOAD_STAGES) {
this.uploadStage = stage;
this.progressUpdater.setUploadStage(stage);
setUploadPhase(phase: UploadPhase) {
this.uploadPhase = phase;
this.progressUpdater.setUploadPhase(phase);
}
setFiles(files: { localID: number; fileName: string }[]) {
@@ -363,7 +363,7 @@ class UploadManager {
this.resetState();
this.uiService.reset();
uploadCancelService.reset();
this.uiService.setUploadStage(UPLOAD_STAGES.START);
this.uiService.setUploadPhase("preparing");
}
showUploadProgressDialog() {
@@ -411,10 +411,7 @@ class UploadManager {
splitMetadataAndMediaItems(namedItems);
if (metadataItems.length) {
this.uiService.setUploadStage(
UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES,
);
this.uiService.setUploadPhase("readingMetadata");
await this.parseMetadataJSONFiles(metadataItems);
}
@@ -442,7 +439,7 @@ class UploadManager {
throw e;
}
} finally {
this.uiService.setUploadStage(UPLOAD_STAGES.FINISH);
this.uiService.setUploadPhase("done");
void globalThis.electron?.clearPendingUploads();
for (let i = 0; i < maxConcurrentUploads; i++) {
this.comlinkCryptoWorkers[i]?.terminate();
@@ -499,7 +496,7 @@ class UploadManager {
this.itemsToBeUploaded = [...this.itemsToBeUploaded, ...mediaItems];
this.uiService.reset(mediaItems.length);
await UploadService.setFileCount(mediaItems.length);
this.uiService.setUploadStage(UPLOAD_STAGES.UPLOADING);
this.uiService.setUploadPhase("uploading");
const uploadProcesses = [];
for (
@@ -653,7 +650,7 @@ class UploadManager {
public cancelRunningUpload() {
log.info("User cancelled running upload");
this.uiService.setUploadStage(UPLOAD_STAGES.CANCELLING);
this.uiService.setUploadPhase("cancelling");
uploadCancelService.requestUploadCancelation();
}

View File

@@ -1,19 +1,21 @@
import { type MiniDialogAttributes } from "@/base/components/MiniDialog";
import { SpaceBetweenFlex } from "@/base/components/mui/Container";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { errorDialogAttributes } from "@/base/components/utils/dialog";
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
import { useIsSmallWidth } from "@/base/hooks";
import log from "@/base/log";
import { downloadString } from "@/base/utils/web";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { ensure } from "@/utils/ensure";
import CodeBlock from "@ente/shared/components/CodeBlock";
import DialogTitleWithCloseButton from "@ente/shared/components/TitleWithCloseButton";
import { getRecoveryKey } from "@ente/shared/crypto/helpers";
import {
Box,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Typography,
styled,
} from "@mui/material";
@@ -74,9 +76,13 @@ export const RecoveryKey: React.FC<RecoveryKeyProps> = ({
maxWidth="xs"
fullWidth
>
<DialogTitleWithCloseButton onClose={onClose}>
{t("recovery_key")}
</DialogTitleWithCloseButton>
<SpaceBetweenFlex sx={{ p: "8px 4px 8px 0" }}>
<DialogTitle variant="h3" fontWeight={"bold"}>
{t("recovery_key")}
</DialogTitle>
<DialogCloseIconButton {...{ onClose }} />
</SpaceBetweenFlex>
<DialogContent>
<Typography mb={3}>{t("recovery_key_description")}</Typography>
<DashedBorderWrapper>

View File

@@ -59,7 +59,6 @@
"UPLOAD_STAGE_MESSAGE": {
"0": "Preparing to upload",
"1": "Reading google metadata files",
"2": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} files metadata extracted",
"3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} files processed",
"4": "Cancelling remaining uploads",
"5": "Backup complete"
@@ -307,7 +306,6 @@
"SKIPPED_INFO": "Skipped these as there are files with matching name and content in the same album",
"UNSUPPORTED_INFO": "Ente does not support these file formats yet",
"BLOCKED_UPLOADS": "Blocked uploads",
"INPROGRESS_METADATA_EXTRACTION": "In progress",
"INPROGRESS_UPLOADS": "Uploads in progress",
"TOO_LARGE_UPLOADS": "Large files",
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Insufficient storage",

View File

@@ -58,14 +58,12 @@ export const toDataOrPathOrZipEntry = (desktopUploadItem: DesktopUploadItem) =>
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
export enum UPLOAD_STAGES {
START,
READING_GOOGLE_METADATA_FILES,
EXTRACTING_METADATA,
UPLOADING,
CANCELLING,
FINISH,
}
export type UploadPhase =
| "preparing"
| "readingMetadata"
| "uploading"
| "cancelling"
| "done";
export enum UPLOAD_RESULT {
FAILED,

View File

@@ -1,58 +0,0 @@
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import CloseIcon from "@mui/icons-material/Close";
import {
DialogTitle,
IconButton,
Typography,
type DialogProps,
} from "@mui/material";
import React from "react";
interface DialogTitleWithCloseButtonProps {
onClose: () => void;
}
const DialogTitleWithCloseButton: React.FC<
React.PropsWithChildren<DialogTitleWithCloseButtonProps>
> = ({ children, onClose }) => {
return (
<DialogTitle>
<SpaceBetweenFlex>
<Typography variant="h3" fontWeight={"bold"}>
{children}
</Typography>
{onClose && (
<IconButton
aria-label="close"
onClick={onClose}
sx={{ float: "right" }}
color="secondary"
>
<CloseIcon />
</IconButton>
)}
</SpaceBetweenFlex>
</DialogTitle>
);
};
export default DialogTitleWithCloseButton;
export const dialogCloseHandler =
({
staticBackdrop,
nonClosable,
onClose,
}: {
staticBackdrop?: boolean;
nonClosable?: boolean;
onClose: () => void;
}): DialogProps["onClose"] =>
(_, reason) => {
if (nonClosable) {
// no-op
} else if (staticBackdrop && reason === "backdropClick") {
// no-op
} else {
onClose();
}
};