Inline fin
This commit is contained in:
@@ -1,17 +1,48 @@
|
||||
import { type UploadPhase } from "@/new/photos/services/upload/types";
|
||||
import { FilledIconButton } from "@/new/photos/components/mui";
|
||||
import {
|
||||
UPLOAD_RESULT,
|
||||
type UploadPhase,
|
||||
} from "@/new/photos/services/upload/types";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { Paper, Snackbar } from "@mui/material";
|
||||
import {
|
||||
SpaceBetweenFlex,
|
||||
VerticallyCenteredFlex,
|
||||
} from "@ente/shared/components/Container";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
|
||||
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Snackbar,
|
||||
Stack,
|
||||
styled,
|
||||
Typography,
|
||||
type AccordionProps,
|
||||
type DialogProps,
|
||||
type TypographyProps,
|
||||
} from "@mui/material";
|
||||
import ItemList from "components/ItemList";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import type {
|
||||
InProgressUpload,
|
||||
SegregatedFinishedUploads,
|
||||
UploadCounter,
|
||||
UploadFileNames,
|
||||
} from "services/upload/uploadManager";
|
||||
import UploadProgressContext from "./UploadProgress/context";
|
||||
import { UploadProgressDialog } from "./UploadProgress/dialog";
|
||||
import { UploadProgressHeader } from "./UploadProgress/header";
|
||||
|
||||
interface UploadProgressProps {
|
||||
open: boolean;
|
||||
@@ -90,6 +121,36 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface UploadProgressContextT {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
uploadCounter: UploadCounter;
|
||||
uploadPhase: UploadPhase;
|
||||
percentComplete: number;
|
||||
retryFailed: () => void;
|
||||
inProgressUploads: InProgressUpload[];
|
||||
uploadFileNames: UploadFileNames;
|
||||
finishedUploads: SegregatedFinishedUploads;
|
||||
hasLivePhotos: boolean;
|
||||
expanded: boolean;
|
||||
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const UploadProgressContext = createContext<UploadProgressContextT>({
|
||||
open: null,
|
||||
onClose: () => null,
|
||||
uploadCounter: null,
|
||||
uploadPhase: undefined,
|
||||
percentComplete: null,
|
||||
retryFailed: () => null,
|
||||
inProgressUploads: null,
|
||||
uploadFileNames: null,
|
||||
finishedUploads: null,
|
||||
hasLivePhotos: null,
|
||||
expanded: null,
|
||||
setExpanded: () => null,
|
||||
});
|
||||
|
||||
const MinimizedUploadProgress: React.FC = () => (
|
||||
<Snackbar open anchorOrigin={{ horizontal: "right", vertical: "bottom" }}>
|
||||
<Paper sx={{ width: "min(360px, 100svw)" }}>
|
||||
@@ -97,3 +158,411 @@ const MinimizedUploadProgress: React.FC = () => (
|
||||
</Paper>
|
||||
</Snackbar>
|
||||
);
|
||||
|
||||
function UploadProgressHeader() {
|
||||
return (
|
||||
<>
|
||||
<UploadProgressTitle />
|
||||
<UploadProgressBar />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const UploadProgressTitleText = ({ expanded }) => {
|
||||
return (
|
||||
<Typography variant={expanded ? "h2" : "h3"}>
|
||||
{t("FILE_UPLOAD")}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
function UploadProgressSubtitleText() {
|
||||
const { uploadPhase, uploadCounter } = useContext(UploadProgressContext);
|
||||
|
||||
return (
|
||||
<Typography
|
||||
variant="body"
|
||||
fontWeight={"normal"}
|
||||
color="text.muted"
|
||||
marginTop={"4px"}
|
||||
>
|
||||
{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");
|
||||
}
|
||||
};
|
||||
|
||||
const UploadProgressTitle: React.FC = () => {
|
||||
const { setExpanded, onClose, expanded } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
const toggleExpanded = () => setExpanded((expanded) => !expanded);
|
||||
|
||||
return (
|
||||
<DialogTitle>
|
||||
<SpaceBetweenFlex>
|
||||
<Box>
|
||||
<UploadProgressTitleText expanded={expanded} />
|
||||
<UploadProgressSubtitleText />
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<FilledIconButton onClick={toggleExpanded}>
|
||||
{expanded ? <UnfoldLessIcon /> : <UnfoldMoreIcon />}
|
||||
</FilledIconButton>
|
||||
<FilledIconButton onClick={onClose}>
|
||||
<Close />
|
||||
</FilledIconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
</SpaceBetweenFlex>
|
||||
</DialogTitle>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadProgressBar: React.FC = () => {
|
||||
const { uploadPhase, percentComplete } = useContext(UploadProgressContext);
|
||||
return (
|
||||
<Box>
|
||||
{(uploadPhase == "readingMetadata" ||
|
||||
uploadPhase == "uploading") && (
|
||||
<>
|
||||
<LinearProgress
|
||||
sx={{
|
||||
height: "2px",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
variant="determinate"
|
||||
value={percentComplete}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function UploadProgressDialog() {
|
||||
const { open, onClose, uploadPhase, finishedUploads } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
|
||||
const [hasUnUploadedFiles, setHasUnUploadedFiles] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
finishedUploads.get(UPLOAD_RESULT.ALREADY_UPLOADED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE)
|
||||
?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.TOO_LARGE)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.UNSUPPORTED)?.length > 0
|
||||
) {
|
||||
setHasUnUploadedFiles(true);
|
||||
} else {
|
||||
setHasUnUploadedFiles(false);
|
||||
}
|
||||
}, [finishedUploads]);
|
||||
|
||||
const handleClose: DialogProps["onClose"] = (_, reason) => {
|
||||
if (reason != "backdropClick") onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
||||
<UploadProgressHeader />
|
||||
{(uploadPhase == "uploading" || uploadPhase == "done") && (
|
||||
<DialogContent sx={{ "&&&": { px: 0 } }}>
|
||||
{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>
|
||||
)}
|
||||
{uploadPhase == "done" && <DoneFooter />}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
const InProgressSection: React.FC = () => {
|
||||
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } =
|
||||
useContext(UploadProgressContext);
|
||||
const fileList = inProgressUploads ?? [];
|
||||
|
||||
const renderListItem = ({ localFileID, progress }) => {
|
||||
return (
|
||||
<InProgressItemContainer key={localFileID}>
|
||||
<span>{uploadFileNames.get(localFileID)}</span>
|
||||
{uploadPhase == "uploading" && (
|
||||
<>
|
||||
{" "}
|
||||
<span className="separator">{`-`}</span>
|
||||
<span>{`${progress}%`}</span>
|
||||
</>
|
||||
)}
|
||||
</InProgressItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getItemTitle = ({ localFileID, progress }) => {
|
||||
return `${uploadFileNames.get(localFileID)} - ${progress}%`;
|
||||
};
|
||||
|
||||
const generateItemKey = ({ localFileID, progress }) => {
|
||||
return `${localFileID}-${progress}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<UploadProgressSection>
|
||||
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
|
||||
<TitleText
|
||||
title={t("INPROGRESS_UPLOADS")}
|
||||
count={inProgressUploads?.length}
|
||||
/>
|
||||
</UploadProgressSectionTitle>
|
||||
<UploadProgressSectionContent>
|
||||
{hasLivePhotos && (
|
||||
<SectionInfo>{t("LIVE_PHOTOS_DETECTED")}</SectionInfo>
|
||||
)}
|
||||
<ItemList
|
||||
items={fileList}
|
||||
generateItemKey={generateItemKey}
|
||||
getItemTitle={getItemTitle}
|
||||
renderListItem={renderListItem}
|
||||
maxHeight={160}
|
||||
itemSize={35}
|
||||
/>
|
||||
</UploadProgressSectionContent>
|
||||
</UploadProgressSection>
|
||||
);
|
||||
};
|
||||
|
||||
const InProgressItemContainer = styled("div")`
|
||||
display: inline-block;
|
||||
& > span {
|
||||
display: inline-block;
|
||||
}
|
||||
& > span:first-of-type {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
max-width: 340px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
& > .separator {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const UploadProgressSection = styled((props: AccordionProps) => (
|
||||
<Accordion disableGutters elevation={0} square {...props} />
|
||||
))(({ theme }) => ({
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
"&:last-child": {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
"&:before": {
|
||||
display: "none",
|
||||
},
|
||||
}));
|
||||
|
||||
const UploadProgressSectionTitle = styled(AccordionSummary)(() => ({
|
||||
backgroundColor: "rgba(255, 255, 255, .05)",
|
||||
}));
|
||||
|
||||
const UploadProgressSectionContent = styled(AccordionDetails)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const SectionInfo = (props: TypographyProps) => (
|
||||
<Typography
|
||||
color={"text.muted"}
|
||||
variant="small"
|
||||
{...props}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
);
|
||||
|
||||
const NotUploadSectionHeader = styled("div")(
|
||||
({ theme }) => `
|
||||
text-align: center;
|
||||
color: ${theme.colors.danger.A700};
|
||||
border-bottom: 1px solid ${theme.colors.danger.A700};
|
||||
margin:${theme.spacing(3, 2, 1)}
|
||||
`,
|
||||
);
|
||||
|
||||
interface ResultSectionProps {
|
||||
uploadResult: UPLOAD_RESULT;
|
||||
sectionTitle: string;
|
||||
sectionInfo?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ResultSection: React.FC<ResultSectionProps> = ({
|
||||
uploadResult,
|
||||
sectionTitle,
|
||||
sectionInfo,
|
||||
}) => {
|
||||
const { finishedUploads, uploadFileNames } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
const fileList = finishedUploads.get(uploadResult);
|
||||
|
||||
if (!fileList?.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const renderListItem = (fileID) => {
|
||||
return (
|
||||
<ResultItemContainer key={fileID}>
|
||||
{uploadFileNames.get(fileID)}
|
||||
</ResultItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getItemTitle = (fileID) => {
|
||||
return uploadFileNames.get(fileID);
|
||||
};
|
||||
|
||||
const generateItemKey = (fileID) => {
|
||||
return fileID;
|
||||
};
|
||||
|
||||
return (
|
||||
<UploadProgressSection>
|
||||
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
|
||||
<TitleText title={sectionTitle} count={fileList?.length} />
|
||||
</UploadProgressSectionTitle>
|
||||
<UploadProgressSectionContent>
|
||||
{sectionInfo && <SectionInfo>{sectionInfo}</SectionInfo>}
|
||||
<ItemList
|
||||
items={fileList}
|
||||
generateItemKey={generateItemKey}
|
||||
getItemTitle={getItemTitle}
|
||||
renderListItem={renderListItem}
|
||||
maxHeight={160}
|
||||
itemSize={35}
|
||||
/>
|
||||
</UploadProgressSectionContent>
|
||||
</UploadProgressSection>
|
||||
);
|
||||
};
|
||||
|
||||
const ResultItemContainer = styled("div")`
|
||||
position: relative;
|
||||
top: 5px;
|
||||
display: inline-block;
|
||||
max-width: 394px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
interface TitleTextProps {
|
||||
title: string;
|
||||
count: number | undefined;
|
||||
}
|
||||
|
||||
const TitleText: React.FC<TitleTextProps> = ({ title, count }) => (
|
||||
<VerticallyCenteredFlex gap={"4px"}>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography variant="small" color="text.faint">
|
||||
{"•"}
|
||||
</Typography>
|
||||
<Typography variant="small" color="text.faint">
|
||||
{count ?? 0}
|
||||
</Typography>
|
||||
</VerticallyCenteredFlex>
|
||||
);
|
||||
|
||||
const DoneFooter: React.FC = () => {
|
||||
const { uploadPhase, finishedUploads, retryFailed, onClose } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogActions>
|
||||
{uploadPhase == "done" &&
|
||||
(finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
|
||||
finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? (
|
||||
<Button variant="contained" fullWidth onClick={retryFailed}>
|
||||
{t("RETRY_FAILED")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="contained" fullWidth onClick={onClose}>
|
||||
{t("close")}
|
||||
</Button>
|
||||
))}
|
||||
</DialogActions>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { type UploadPhase } from "@/new/photos/services/upload/types";
|
||||
import { createContext } from "react";
|
||||
import type {
|
||||
InProgressUpload,
|
||||
SegregatedFinishedUploads,
|
||||
UploadCounter,
|
||||
UploadFileNames,
|
||||
} from "services/upload/uploadManager";
|
||||
|
||||
interface UploadProgressContextType {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
uploadCounter: UploadCounter;
|
||||
uploadPhase: UploadPhase;
|
||||
percentComplete: number;
|
||||
retryFailed: () => void;
|
||||
inProgressUploads: InProgressUpload[];
|
||||
uploadFileNames: UploadFileNames;
|
||||
finishedUploads: SegregatedFinishedUploads;
|
||||
hasLivePhotos: boolean;
|
||||
expanded: boolean;
|
||||
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
const defaultUploadProgressContext: UploadProgressContextType = {
|
||||
open: null,
|
||||
onClose: () => null,
|
||||
uploadCounter: null,
|
||||
uploadPhase: undefined,
|
||||
percentComplete: null,
|
||||
retryFailed: () => null,
|
||||
inProgressUploads: null,
|
||||
uploadFileNames: null,
|
||||
finishedUploads: null,
|
||||
hasLivePhotos: null,
|
||||
expanded: null,
|
||||
setExpanded: () => null,
|
||||
};
|
||||
const UploadProgressContext = createContext<UploadProgressContextType>(
|
||||
defaultUploadProgressContext,
|
||||
);
|
||||
|
||||
export default UploadProgressContext;
|
||||
@@ -1,331 +0,0 @@
|
||||
import { UPLOAD_RESULT } from "@/new/photos/services/upload/types";
|
||||
import { VerticallyCenteredFlex } from "@ente/shared/components/Container";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
styled,
|
||||
Typography,
|
||||
type AccordionProps,
|
||||
type DialogProps,
|
||||
type TypographyProps,
|
||||
} from "@mui/material";
|
||||
import ItemList from "components/ItemList";
|
||||
import { t } from "i18next";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import UploadProgressContext from "./context";
|
||||
import { UploadProgressHeader } from "./header";
|
||||
|
||||
export function UploadProgressDialog() {
|
||||
const { open, onClose, uploadPhase, finishedUploads } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
|
||||
const [hasUnUploadedFiles, setHasUnUploadedFiles] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
finishedUploads.get(UPLOAD_RESULT.ALREADY_UPLOADED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE)
|
||||
?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.TOO_LARGE)?.length > 0 ||
|
||||
finishedUploads.get(UPLOAD_RESULT.UNSUPPORTED)?.length > 0
|
||||
) {
|
||||
setHasUnUploadedFiles(true);
|
||||
} else {
|
||||
setHasUnUploadedFiles(false);
|
||||
}
|
||||
}, [finishedUploads]);
|
||||
|
||||
const handleClose: DialogProps["onClose"] = (_, reason) => {
|
||||
if (reason != "backdropClick") onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
||||
<UploadProgressHeader />
|
||||
{(uploadPhase == "uploading" || uploadPhase == "done") && (
|
||||
<DialogContent sx={{ "&&&": { px: 0 } }}>
|
||||
{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>
|
||||
)}
|
||||
{uploadPhase == "done" && <DoneFooter />}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
const InProgressSection: React.FC = () => {
|
||||
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } =
|
||||
useContext(UploadProgressContext);
|
||||
const fileList = inProgressUploads ?? [];
|
||||
|
||||
const renderListItem = ({ localFileID, progress }) => {
|
||||
return (
|
||||
<InProgressItemContainer key={localFileID}>
|
||||
<span>{uploadFileNames.get(localFileID)}</span>
|
||||
{uploadPhase == "uploading" && (
|
||||
<>
|
||||
{" "}
|
||||
<span className="separator">{`-`}</span>
|
||||
<span>{`${progress}%`}</span>
|
||||
</>
|
||||
)}
|
||||
</InProgressItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getItemTitle = ({ localFileID, progress }) => {
|
||||
return `${uploadFileNames.get(localFileID)} - ${progress}%`;
|
||||
};
|
||||
|
||||
const generateItemKey = ({ localFileID, progress }) => {
|
||||
return `${localFileID}-${progress}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<UploadProgressSection>
|
||||
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
|
||||
<TitleText
|
||||
title={t("INPROGRESS_UPLOADS")}
|
||||
count={inProgressUploads?.length}
|
||||
/>
|
||||
</UploadProgressSectionTitle>
|
||||
<UploadProgressSectionContent>
|
||||
{hasLivePhotos && (
|
||||
<SectionInfo>{t("LIVE_PHOTOS_DETECTED")}</SectionInfo>
|
||||
)}
|
||||
<ItemList
|
||||
items={fileList}
|
||||
generateItemKey={generateItemKey}
|
||||
getItemTitle={getItemTitle}
|
||||
renderListItem={renderListItem}
|
||||
maxHeight={160}
|
||||
itemSize={35}
|
||||
/>
|
||||
</UploadProgressSectionContent>
|
||||
</UploadProgressSection>
|
||||
);
|
||||
};
|
||||
|
||||
const InProgressItemContainer = styled("div")`
|
||||
display: inline-block;
|
||||
& > span {
|
||||
display: inline-block;
|
||||
}
|
||||
& > span:first-of-type {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
max-width: 340px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
& > .separator {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const UploadProgressSection = styled((props: AccordionProps) => (
|
||||
<Accordion disableGutters elevation={0} square {...props} />
|
||||
))(({ theme }) => ({
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
"&:last-child": {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
"&:before": {
|
||||
display: "none",
|
||||
},
|
||||
}));
|
||||
|
||||
const UploadProgressSectionTitle = styled(AccordionSummary)(() => ({
|
||||
backgroundColor: "rgba(255, 255, 255, .05)",
|
||||
}));
|
||||
|
||||
const UploadProgressSectionContent = styled(AccordionDetails)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const SectionInfo = (props: TypographyProps) => (
|
||||
<Typography
|
||||
color={"text.muted"}
|
||||
variant="small"
|
||||
{...props}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
);
|
||||
|
||||
const NotUploadSectionHeader = styled("div")(
|
||||
({ theme }) => `
|
||||
text-align: center;
|
||||
color: ${theme.colors.danger.A700};
|
||||
border-bottom: 1px solid ${theme.colors.danger.A700};
|
||||
margin:${theme.spacing(3, 2, 1)}
|
||||
`,
|
||||
);
|
||||
|
||||
interface ResultSectionProps {
|
||||
uploadResult: UPLOAD_RESULT;
|
||||
sectionTitle: string;
|
||||
sectionInfo?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ResultSection: React.FC<ResultSectionProps> = ({
|
||||
uploadResult,
|
||||
sectionTitle,
|
||||
sectionInfo,
|
||||
}) => {
|
||||
const { finishedUploads, uploadFileNames } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
const fileList = finishedUploads.get(uploadResult);
|
||||
|
||||
if (!fileList?.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const renderListItem = (fileID) => {
|
||||
return (
|
||||
<ResultItemContainer key={fileID}>
|
||||
{uploadFileNames.get(fileID)}
|
||||
</ResultItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getItemTitle = (fileID) => {
|
||||
return uploadFileNames.get(fileID);
|
||||
};
|
||||
|
||||
const generateItemKey = (fileID) => {
|
||||
return fileID;
|
||||
};
|
||||
|
||||
return (
|
||||
<UploadProgressSection>
|
||||
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
|
||||
<TitleText title={sectionTitle} count={fileList?.length} />
|
||||
</UploadProgressSectionTitle>
|
||||
<UploadProgressSectionContent>
|
||||
{sectionInfo && <SectionInfo>{sectionInfo}</SectionInfo>}
|
||||
<ItemList
|
||||
items={fileList}
|
||||
generateItemKey={generateItemKey}
|
||||
getItemTitle={getItemTitle}
|
||||
renderListItem={renderListItem}
|
||||
maxHeight={160}
|
||||
itemSize={35}
|
||||
/>
|
||||
</UploadProgressSectionContent>
|
||||
</UploadProgressSection>
|
||||
);
|
||||
};
|
||||
|
||||
const ResultItemContainer = styled("div")`
|
||||
position: relative;
|
||||
top: 5px;
|
||||
display: inline-block;
|
||||
max-width: 394px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
interface TitleTextProps {
|
||||
title: string;
|
||||
count: number | undefined;
|
||||
}
|
||||
|
||||
const TitleText: React.FC<TitleTextProps> = ({ title, count }) => (
|
||||
<VerticallyCenteredFlex gap={"4px"}>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography variant="small" color="text.faint">
|
||||
{"•"}
|
||||
</Typography>
|
||||
<Typography variant="small" color="text.faint">
|
||||
{count ?? 0}
|
||||
</Typography>
|
||||
</VerticallyCenteredFlex>
|
||||
);
|
||||
|
||||
const DoneFooter: React.FC = () => {
|
||||
const { uploadPhase, finishedUploads, retryFailed, onClose } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
|
||||
return (
|
||||
<DialogActions>
|
||||
{uploadPhase == "done" &&
|
||||
(finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
|
||||
finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? (
|
||||
<Button variant="contained" fullWidth onClick={retryFailed}>
|
||||
{t("RETRY_FAILED")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="contained" fullWidth onClick={onClose}>
|
||||
{t("close")}
|
||||
</Button>
|
||||
))}
|
||||
</DialogActions>
|
||||
);
|
||||
};
|
||||
@@ -1,118 +0,0 @@
|
||||
import { FilledIconButton } from "@/new/photos/components/mui";
|
||||
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";
|
||||
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
|
||||
import {
|
||||
Box,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
LinearProgress,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import React, { useContext } from "react";
|
||||
import type { UploadCounter } from "services/upload/uploadManager";
|
||||
import UploadProgressContext from "./context";
|
||||
|
||||
export function UploadProgressHeader() {
|
||||
return (
|
||||
<>
|
||||
<UploadProgressTitle />
|
||||
<UploadProgressBar />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const UploadProgressTitleText = ({ expanded }) => {
|
||||
return (
|
||||
<Typography variant={expanded ? "h2" : "h3"}>
|
||||
{t("FILE_UPLOAD")}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
function UploadProgressSubtitleText() {
|
||||
const { uploadPhase, uploadCounter } = useContext(UploadProgressContext);
|
||||
|
||||
return (
|
||||
<Typography
|
||||
variant="body"
|
||||
fontWeight={"normal"}
|
||||
color="text.muted"
|
||||
marginTop={"4px"}
|
||||
>
|
||||
{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");
|
||||
}
|
||||
};
|
||||
|
||||
const UploadProgressTitle: React.FC = () => {
|
||||
const { setExpanded, onClose, expanded } = useContext(
|
||||
UploadProgressContext,
|
||||
);
|
||||
const toggleExpanded = () => setExpanded((expanded) => !expanded);
|
||||
|
||||
return (
|
||||
<DialogTitle>
|
||||
<SpaceBetweenFlex>
|
||||
<Box>
|
||||
<UploadProgressTitleText expanded={expanded} />
|
||||
<UploadProgressSubtitleText />
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<FilledIconButton onClick={toggleExpanded}>
|
||||
{expanded ? <UnfoldLessIcon /> : <UnfoldMoreIcon />}
|
||||
</FilledIconButton>
|
||||
<FilledIconButton onClick={onClose}>
|
||||
<Close />
|
||||
</FilledIconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
</SpaceBetweenFlex>
|
||||
</DialogTitle>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadProgressBar: React.FC = () => {
|
||||
const { uploadPhase, percentComplete } = useContext(UploadProgressContext);
|
||||
return (
|
||||
<Box>
|
||||
{(uploadPhase == "readingMetadata" ||
|
||||
uploadPhase == "uploading") && (
|
||||
<>
|
||||
<LinearProgress
|
||||
sx={{
|
||||
height: "2px",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
variant="determinate"
|
||||
value={percentComplete}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user