add upload progress context

This commit is contained in:
Abhinav 2022-06-11 01:53:00 +05:30
parent b51afc7efe
commit ae3a68378f
17 changed files with 336 additions and 292 deletions

View file

@ -1,7 +1,7 @@
import { DialogContent } from '@mui/material'; import { DialogContent } from '@mui/material';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import { UPLOAD_STAGES, FileUploadResults } from 'constants/upload'; import { UPLOAD_STAGES, UPLOAD_RESULT } from 'constants/upload';
import React from 'react'; import React, { useContext, useMemo } from 'react';
import { UploadProgressFooter } from './footer'; import { UploadProgressFooter } from './footer';
import { UploadProgressHeader } from './header'; import { UploadProgressHeader } from './header';
import { InProgressSection } from './inProgressSection'; import { InProgressSection } from './inProgressSection';
@ -9,49 +9,46 @@ import { ResultSection } from './resultSection';
import { NotUploadSectionHeader } from './styledComponents'; import { NotUploadSectionHeader } from './styledComponents';
import { getOSSpecificDesktopAppDownloadLink } from 'utils/common'; import { getOSSpecificDesktopAppDownloadLink } from 'utils/common';
import DialogBoxBase from 'components/DialogBox/base'; import DialogBoxBase from 'components/DialogBox/base';
export function UploadProgressDialog({ import UploadProgressContext from 'contexts/uploadProgress';
handleClose,
setExpanded, export function UploadProgressDialog() {
expanded, const { open, onClose, uploadStage, finishedUploads } = useContext(
fileProgressStatuses, UploadProgressContext
sectionInfo, );
fileUploadResultMap,
filesNotUploaded, const hasUnUploadedFiles = useMemo(() => {
...props 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
) {
return true;
} else {
return false;
}
}, [finishedUploads]);
return ( return (
<DialogBoxBase maxWidth="xs" open={props.show} onClose={handleClose}> <DialogBoxBase maxWidth="xs" open={open} onClose={onClose}>
<UploadProgressHeader <UploadProgressHeader />
uploadStage={props.uploadStage} {(uploadStage === UPLOAD_STAGES.UPLOADING ||
setExpanded={setExpanded} uploadStage === UPLOAD_STAGES.FINISH) && (
expanded={expanded}
handleClose={handleClose}
fileCounter={props.fileCounter}
now={props.now}
/>
{(props.uploadStage === UPLOAD_STAGES.UPLOADING ||
props.uploadStage === UPLOAD_STAGES.FINISH) && (
<DialogContent sx={{ '&&&': { px: 0 } }}> <DialogContent sx={{ '&&&': { px: 0 } }}>
{props.uploadStage === UPLOAD_STAGES.UPLOADING && ( {uploadStage === UPLOAD_STAGES.UPLOADING && (
<InProgressSection <InProgressSection />
filenames={props.filenames}
fileProgressStatuses={fileProgressStatuses}
sectionTitle={constants.INPROGRESS_UPLOADS}
sectionInfo={sectionInfo}
/>
)} )}
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.UPLOADED}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.UPLOADED}
sectionTitle={constants.SUCCESSFUL_UPLOADS} sectionTitle={constants.SUCCESSFUL_UPLOADS}
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={
fileUploadResultMap={fileUploadResultMap} UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL
fileUploadResult={
FileUploadResults.UPLOADED_WITH_STATIC_THUMBNAIL
} }
sectionTitle={ sectionTitle={
constants.THUMBNAIL_GENERATION_FAILED_UPLOADS constants.THUMBNAIL_GENERATION_FAILED_UPLOADS
@ -59,40 +56,32 @@ export function UploadProgressDialog({
sectionInfo={constants.THUMBNAIL_GENERATION_FAILED_INFO} sectionInfo={constants.THUMBNAIL_GENERATION_FAILED_INFO}
/> />
{props.uploadStage === UPLOAD_STAGES.FINISH && {uploadStage === UPLOAD_STAGES.FINISH &&
filesNotUploaded && ( hasUnUploadedFiles && (
<NotUploadSectionHeader> <NotUploadSectionHeader>
{constants.FILE_NOT_UPLOADED_LIST} {constants.FILE_NOT_UPLOADED_LIST}
</NotUploadSectionHeader> </NotUploadSectionHeader>
)} )}
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.BLOCKED}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.BLOCKED}
sectionTitle={constants.BLOCKED_UPLOADS} sectionTitle={constants.BLOCKED_UPLOADS}
sectionInfo={constants.ETAGS_BLOCKED( sectionInfo={constants.ETAGS_BLOCKED(
getOSSpecificDesktopAppDownloadLink() getOSSpecificDesktopAppDownloadLink()
)} )}
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.FAILED}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.FAILED}
sectionTitle={constants.FAILED_UPLOADS} sectionTitle={constants.FAILED_UPLOADS}
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.ALREADY_UPLOADED}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.ALREADY_UPLOADED}
sectionTitle={constants.SKIPPED_FILES} sectionTitle={constants.SKIPPED_FILES}
sectionInfo={constants.SKIPPED_INFO} sectionInfo={constants.SKIPPED_INFO}
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={
fileUploadResultMap={fileUploadResultMap} UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE
fileUploadResult={
FileUploadResults.LARGER_THAN_AVAILABLE_STORAGE
} }
sectionTitle={ sectionTitle={
constants.LARGER_THAN_AVAILABLE_STORAGE_UPLOADS constants.LARGER_THAN_AVAILABLE_STORAGE_UPLOADS
@ -102,29 +91,18 @@ export function UploadProgressDialog({
} }
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.UNSUPPORTED}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.UNSUPPORTED}
sectionTitle={constants.UNSUPPORTED_FILES} sectionTitle={constants.UNSUPPORTED_FILES}
sectionInfo={constants.UNSUPPORTED_INFO} sectionInfo={constants.UNSUPPORTED_INFO}
/> />
<ResultSection <ResultSection
filenames={props.filenames} uploadResult={UPLOAD_RESULT.TOO_LARGE}
fileUploadResultMap={fileUploadResultMap}
fileUploadResult={FileUploadResults.TOO_LARGE}
sectionTitle={constants.TOO_LARGE_UPLOADS} sectionTitle={constants.TOO_LARGE_UPLOADS}
sectionInfo={constants.TOO_LARGE_INFO} sectionInfo={constants.TOO_LARGE_INFO}
/> />
</DialogContent> </DialogContent>
)} )}
{props.uploadStage === UPLOAD_STAGES.FINISH && ( {uploadStage === UPLOAD_STAGES.FINISH && <UploadProgressFooter />}
<UploadProgressFooter
uploadStage={props.uploadStage}
retryFailed={props.retryFailed}
closeModal={handleClose}
fileUploadResultMap={fileUploadResultMap}
/>
)}
</DialogBoxBase> </DialogBoxBase>
); );
} }

View file

@ -1,25 +1,27 @@
import { Button, DialogActions } from '@mui/material'; import { Button, DialogActions } from '@mui/material';
import { UPLOAD_STAGES, FileUploadResults } from 'constants/upload'; import { UPLOAD_STAGES, UPLOAD_RESULT } from 'constants/upload';
import React from 'react'; import React, { useContext } from 'react';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
export function UploadProgressFooter({ import UploadProgressContext from 'contexts/uploadProgress';
uploadStage,
fileUploadResultMap, export function UploadProgressFooter() {
retryFailed, const { uploadStage, finishedUploads, retryFailed, onClose } = useContext(
closeModal, UploadProgressContext
}) { );
return ( return (
<DialogActions> <DialogActions>
{uploadStage === UPLOAD_STAGES.FINISH && {uploadStage === UPLOAD_STAGES.FINISH &&
(fileUploadResultMap?.get(FileUploadResults.FAILED)?.length > (finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
0 || finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? (
fileUploadResultMap?.get(FileUploadResults.BLOCKED)?.length >
0 ? (
<Button variant="contained" fullWidth onClick={retryFailed}> <Button variant="contained" fullWidth onClick={retryFailed}>
{constants.RETRY_FAILED} {constants.RETRY_FAILED}
</Button> </Button>
) : ( ) : (
<Button variant="contained" fullWidth onClick={closeModal}> <Button
variant="contained"
fullWidth
onClick={() => onClose.bind(null)}>
{constants.CLOSE} {constants.CLOSE}
</Button> </Button>
))} ))}

View file

@ -2,24 +2,11 @@ import React from 'react';
import { UploadProgressBar } from './progressBar'; import { UploadProgressBar } from './progressBar';
import { UploadProgressTitle } from './title'; import { UploadProgressTitle } from './title';
export function UploadProgressHeader({ export function UploadProgressHeader() {
uploadStage,
setExpanded,
expanded,
handleClose,
fileCounter,
now,
}) {
return ( return (
<> <>
<UploadProgressTitle <UploadProgressTitle />
uploadStage={uploadStage} <UploadProgressBar />
setExpanded={setExpanded}
expanded={expanded}
handleClose={handleClose}
fileCounter={fileCounter}
/>
<UploadProgressBar now={now} uploadStage={uploadStage} />
</> </>
); );
} }

View file

@ -1,37 +1,35 @@
import React from 'react'; import React, { useContext } from 'react';
import FileList from 'components/FileList'; import FileList from 'components/FileList';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { InProgressItemContainer } from './styledComponents'; import { InProgressItemContainer } from './styledComponents';
import { FileProgresses } from '.';
import { import {
SectionInfo, SectionInfo,
UploadProgressSection, UploadProgressSection,
UploadProgressSectionContent, UploadProgressSectionContent,
UploadProgressSectionTitle, UploadProgressSectionTitle,
} from './section'; } from './section';
import UploadProgressContext from 'contexts/uploadProgress';
import constants from 'utils/strings/constants';
export interface InProgressProps { export const InProgressSection = () => {
filenames: Map<number, string>; const { inProgressUploads, hasLivePhotos, uploadFileNames } = useContext(
sectionTitle: string; UploadProgressContext
fileProgressStatuses: FileProgresses[]; );
sectionInfo?: any; const fileList = inProgressUploads ?? [];
}
export const InProgressSection = (props: InProgressProps) => {
const fileList = props.fileProgressStatuses ?? [];
return ( return (
<UploadProgressSection defaultExpanded> <UploadProgressSection defaultExpanded>
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}> <UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
{props.sectionTitle} {constants.INPROGRESS_UPLOADS}
</UploadProgressSectionTitle> </UploadProgressSectionTitle>
<UploadProgressSectionContent> <UploadProgressSectionContent>
{props.sectionInfo && ( {hasLivePhotos && (
<SectionInfo>{props.sectionInfo}</SectionInfo> <SectionInfo>{constants.LIVE_PHOTOS_DETECTED}</SectionInfo>
)} )}
<FileList <FileList
fileList={fileList.map(({ fileID, progress }) => ( fileList={fileList.map(({ localFileID, progress }) => (
<InProgressItemContainer key={fileID}> <InProgressItemContainer key={localFileID}>
<span>{props.filenames.get(fileID)}</span> <span>{uploadFileNames.get(localFileID)}</span>
<span className="separator">{`-`}</span> <span className="separator">{`-`}</span>
<span>{`${progress}%`}</span> <span>{`${progress}%`}</span>
</InProgressItemContainer> </InProgressItemContainer>

View file

@ -1,64 +1,71 @@
import { UploadProgressDialog } from './dialog'; import { UploadProgressDialog } from './dialog';
import { MinimizedUploadProgress } from './minimized'; import { MinimizedUploadProgress } from './minimized';
import React, { useContext, useState } from 'react'; import React, { useContext, useMemo, useState } from 'react';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload'; import { UPLOAD_STAGES } from 'constants/upload';
import { AppContext } from 'pages/_app'; import { AppContext } from 'pages/_app';
import { dialogCloseHandler } from 'components/DialogBox/base'; import { dialogCloseHandler } from 'components/DialogBox/base';
import {
UploadFileNames,
UploadCounter,
InProgressUploads,
InProgressUpload,
FinishedUploads,
SegregatedFinishedUploads,
} from 'types/upload/ui';
import UploadProgressContext from 'contexts/uploadProgress';
interface Props { interface Props {
fileCounter; open: boolean;
uploadStage; onClose: () => void;
now; uploadCounter: UploadCounter;
closeModal; uploadStage: UPLOAD_STAGES;
retryFailed; percentComplete: number;
fileProgress: Map<number, number>; retryFailed: () => void;
filenames: Map<number, string>; inProgressUploads: InProgressUploads;
show; uploadFileNames: UploadFileNames;
uploadResult: Map<number, FileUploadResults>; finishedUploads: FinishedUploads;
hasLivePhotos: boolean; hasLivePhotos: boolean;
cancelUploads: () => void; cancelUploads: () => void;
} }
export interface FileProgresses {
fileID: number;
progress: number;
}
export default function UploadProgress(props: Props) { export default function UploadProgress({
open,
uploadCounter,
uploadStage,
percentComplete,
retryFailed,
uploadFileNames,
hasLivePhotos,
...props
}: Props) {
const appContext = useContext(AppContext); const appContext = useContext(AppContext);
const [expanded, setExpanded] = useState(true); const [expanded, setExpanded] = useState(true);
const fileProgressStatuses = [] as FileProgresses[];
const fileUploadResultMap = new Map<FileUploadResults, number[]>();
let filesNotUploaded = false;
let sectionInfo = null;
if (props.fileProgress) {
for (const [localID, progress] of props.fileProgress) {
fileProgressStatuses.push({
fileID: localID,
progress,
});
}
}
if (props.uploadResult) {
for (const [localID, progress] of props.uploadResult) {
if (!fileUploadResultMap.has(progress)) {
fileUploadResultMap.set(progress, []);
}
if (
progress !== FileUploadResults.UPLOADED &&
progress !== FileUploadResults.UPLOADED_WITH_STATIC_THUMBNAIL
) {
filesNotUploaded = true;
}
const fileList = fileUploadResultMap.get(progress);
fileUploadResultMap.set(progress, [...fileList, localID]); const inProgressUploads = useMemo(
() =>
[...props.inProgressUploads.entries()].map(
([localFileID, progress]) =>
({
localFileID,
progress,
} as InProgressUpload)
),
[props.inProgressUploads]
);
const finishedUploads = useMemo(() => {
const finishedUploads = new Map() as SegregatedFinishedUploads;
for (const [localID, result] of props.finishedUploads) {
if (!finishedUploads.has(result)) {
finishedUploads.set(result, []);
}
finishedUploads.get(result).push(localID);
} }
} return finishedUploads;
if (props.hasLivePhotos) { }, [props.finishedUploads]);
sectionInfo = constants.LIVE_PHOTOS_DETECTED;
}
function confirmCancelUpload() { function confirmCancelUpload() {
appContext.setDialogMessage({ appContext.setDialogMessage({
@ -78,10 +85,10 @@ export default function UploadProgress(props: Props) {
} }
function onClose() { function onClose() {
if (props.uploadStage !== UPLOAD_STAGES.FINISH) { if (uploadStage !== UPLOAD_STAGES.FINISH) {
confirmCancelUpload(); confirmCancelUpload();
} else { } else {
props.closeModal(); props.onClose();
} }
} }
@ -90,26 +97,22 @@ export default function UploadProgress(props: Props) {
}); });
return ( return (
<> <UploadProgressContext.Provider
{expanded ? ( value={{
<UploadProgressDialog open,
handleClose={handleClose} onClose: handleClose,
setExpanded={setExpanded} uploadCounter,
expanded={expanded} uploadStage,
fileProgressStatuses={fileProgressStatuses} percentComplete,
sectionInfo={sectionInfo} retryFailed,
fileUploadResultMap={fileUploadResultMap} inProgressUploads,
filesNotUploaded={filesNotUploaded} uploadFileNames,
{...props} finishedUploads,
/> hasLivePhotos,
) : ( expanded,
<MinimizedUploadProgress setExpanded,
setExpanded={setExpanded} }}>
expanded={expanded} {expanded ? <UploadProgressDialog /> : <MinimizedUploadProgress />}
handleClose={handleClose} </UploadProgressContext.Provider>
{...props}
/>
)}
</>
); );
} }

View file

@ -13,7 +13,7 @@ export function MinimizedUploadProgress(props) {
sx={{ sx={{
width: '360px', width: '360px',
}}> }}>
<UploadProgressHeader {...props} /> <UploadProgressHeader />
</Paper> </Paper>
</Snackbar> </Snackbar>
); );

View file

@ -1,8 +1,10 @@
import React from 'react'; import React, { useContext } from 'react';
import { LinearProgress, Divider, Box } from '@mui/material'; import { LinearProgress, Divider, Box } from '@mui/material';
import { UPLOAD_STAGES } from 'constants/upload'; import { UPLOAD_STAGES } from 'constants/upload';
import UploadProgressContext from 'contexts/uploadProgress';
export function UploadProgressBar({ uploadStage, now }) { export function UploadProgressBar() {
const { uploadStage, percentComplete } = useContext(UploadProgressContext);
return ( return (
<Box> <Box>
{(uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES || {(uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
@ -15,7 +17,7 @@ export function UploadProgressBar({ uploadStage, now }) {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}} }}
variant="determinate" variant="determinate"
value={now} value={percentComplete}
/> />
<Divider /> <Divider />
</> </>

View file

@ -1,25 +1,28 @@
import React from 'react'; import React, { useContext } from 'react';
import FileList from 'components/FileList'; import FileList from 'components/FileList';
import { Typography } from '@mui/material'; import { Typography } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { ResultItemContainer } from './styledComponents'; import { ResultItemContainer } from './styledComponents';
import { FileUploadResults } from 'constants/upload'; import { UPLOAD_RESULT } from 'constants/upload';
import { import {
SectionInfo, SectionInfo,
UploadProgressSection, UploadProgressSection,
UploadProgressSectionContent, UploadProgressSectionContent,
UploadProgressSectionTitle, UploadProgressSectionTitle,
} from './section'; } from './section';
import UploadProgressContext from 'contexts/uploadProgress';
export interface ResultSectionProps { export interface ResultSectionProps {
filenames: Map<number, string>; uploadResult: UPLOAD_RESULT;
fileUploadResultMap: Map<FileUploadResults, number[]>;
fileUploadResult: FileUploadResults;
sectionTitle: any; sectionTitle: any;
sectionInfo?: any; sectionInfo?: any;
} }
export const ResultSection = (props: ResultSectionProps) => { export const ResultSection = (props: ResultSectionProps) => {
const fileList = props.fileUploadResultMap?.get(props.fileUploadResult); const { finishedUploads, uploadFileNames } = useContext(
UploadProgressContext
);
const fileList = finishedUploads.get(props.uploadResult);
if (!fileList?.length) { if (!fileList?.length) {
return <></>; return <></>;
} }
@ -35,7 +38,7 @@ export const ResultSection = (props: ResultSectionProps) => {
<FileList <FileList
fileList={fileList.map((fileID) => ( fileList={fileList.map((fileID) => (
<ResultItemContainer key={fileID}> <ResultItemContainer key={fileID}>
{props.filenames.get(fileID)} {uploadFileNames.get(fileID)}
</ResultItemContainer> </ResultItemContainer>
))} ))}
/> />

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { useContext } from 'react';
import Close from '@mui/icons-material/Close'; import Close from '@mui/icons-material/Close';
import { import {
DialogTitle, DialogTitle,
@ -13,6 +13,7 @@ import { UPLOAD_STAGES } from 'constants/upload';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import { MaximizeIcon } from 'components/icons/Maximize'; import { MaximizeIcon } from 'components/icons/Maximize';
import { MinimizeIcon } from 'components/icons/Minimize'; import { MinimizeIcon } from 'components/icons/Minimize';
import UploadProgressContext from 'contexts/uploadProgress';
const IconButtonWithBG = styled(IconButton)(({ theme }) => ({ const IconButtonWithBG = styled(IconButton)(({ theme }) => ({
backgroundColor: theme.palette.secondary.main, backgroundColor: theme.palette.secondary.main,
@ -33,24 +34,22 @@ line-height: 36px;
</Typography> </Typography>
); );
function UploadProgressSubtitleText(props) { function UploadProgressSubtitleText() {
const { uploadStage, uploadCounter } = useContext(UploadProgressContext);
return ( return (
<Typography color="text.secondary"> <Typography color="text.secondary">
{props.uploadStage === UPLOAD_STAGES.UPLOADING {uploadStage === UPLOAD_STAGES.UPLOADING
? constants.UPLOAD_STAGE_MESSAGE[props.uploadStage]( ? constants.UPLOAD_STAGE_MESSAGE[uploadStage](uploadCounter)
props.fileCounter : constants.UPLOAD_STAGE_MESSAGE[uploadStage]}
)
: constants.UPLOAD_STAGE_MESSAGE[props.uploadStage]}
</Typography> </Typography>
); );
} }
export function UploadProgressTitle({ export function UploadProgressTitle() {
setExpanded, const { setExpanded, onClose, expanded } = useContext(
expanded, UploadProgressContext
handleClose, );
...props
}) {
const toggleExpanded = () => setExpanded((expanded) => !expanded); const toggleExpanded = () => setExpanded((expanded) => !expanded);
return ( return (
@ -58,14 +57,14 @@ export function UploadProgressTitle({
<SpaceBetweenFlex> <SpaceBetweenFlex>
<Box> <Box>
<UploadProgressTitleText expanded={expanded} /> <UploadProgressTitleText expanded={expanded} />
<UploadProgressSubtitleText {...props} /> <UploadProgressSubtitleText />
</Box> </Box>
<Box> <Box>
<Stack direction={'row'} spacing={1}> <Stack direction={'row'} spacing={1}>
<IconButtonWithBG onClick={toggleExpanded}> <IconButtonWithBG onClick={toggleExpanded}>
{expanded ? <MinimizeIcon /> : <MaximizeIcon />} {expanded ? <MinimizeIcon /> : <MaximizeIcon />}
</IconButtonWithBG> </IconButtonWithBG>
<IconButtonWithBG onClick={handleClose}> <IconButtonWithBG onClick={onClose.bind(null)}>
<Close /> <Close />
</IconButtonWithBG> </IconButtonWithBG>
</Stack> </Stack>

View file

@ -19,7 +19,6 @@ import { METADATA_FOLDER_NAME } from 'constants/export';
import { CustomError } from 'utils/error'; import { CustomError } from 'utils/error';
import { Collection } from 'types/collection'; import { Collection } from 'types/collection';
import { SetLoading, SetFiles } from 'types/gallery'; import { SetLoading, SetFiles } from 'types/gallery';
import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload';
import { ElectronFile, FileWithCollection } from 'types/upload'; import { ElectronFile, FileWithCollection } from 'types/upload';
import UploadTypeSelector from '../../UploadTypeSelector'; import UploadTypeSelector from '../../UploadTypeSelector';
import Router from 'next/router'; import Router from 'next/router';
@ -27,6 +26,13 @@ import { isCanvasBlocked } from 'utils/upload/isCanvasBlocked';
import { downloadApp } from 'utils/common'; import { downloadApp } from 'utils/common';
import DiscFullIcon from '@mui/icons-material/DiscFull'; import DiscFullIcon from '@mui/icons-material/DiscFull';
import { NotificationAttributes } from 'types/Notification'; import { NotificationAttributes } from 'types/Notification';
import {
UploadFileNames,
InProgressUploads,
UploadCounter,
FinishedUploads,
} from 'types/upload/ui';
import { UPLOAD_STAGES } from 'constants/upload';
const FIRST_ALBUM_NAME = 'My First Album'; const FIRST_ALBUM_NAME = 'My First Album';
@ -74,16 +80,13 @@ const NULL_ANALYSIS_RESULT = {
}; };
export default function Upload(props: Props) { export default function Upload(props: Props) {
const [progressView, setProgressView] = useState(false); const [uploadProgressView, setUploadProgressView] = useState(false);
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>( const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>();
UPLOAD_STAGES.START const [uploadFileNames, setUploadFileNames] = useState<UploadFileNames>();
); const [uploadCounter, setUploadCounter] = useState<UploadCounter>();
const [filenames, setFilenames] = useState(new Map<number, string>()); const [inProgressUploads, setInProgressUploads] =
const [fileCounter, setFileCounter] = useState({ finished: 0, total: 0 }); useState<InProgressUploads>();
const [fileProgress, setFileProgress] = useState(new Map<number, number>()); const [finishedUploads, setFinishedUploads] = useState<FinishedUploads>();
const [uploadResult, setUploadResult] = useState(
new Map<number, FileUploadResults>()
);
const [percentComplete, setPercentComplete] = useState(0); const [percentComplete, setPercentComplete] = useState(0);
const [hasLivePhotos, setHasLivePhotos] = useState(false); const [hasLivePhotos, setHasLivePhotos] = useState(false);
@ -103,11 +106,11 @@ export default function Upload(props: Props) {
UploadManager.initUploader( UploadManager.initUploader(
{ {
setPercentComplete, setPercentComplete,
setFileCounter, setUploadCounter,
setFileProgress, setInProgressUploads,
setUploadResult, setFinishedUploads,
setUploadStage, setUploadStage,
setFilenames, setUploadFilenames: setUploadFileNames,
setHasLivePhotos, setHasLivePhotos,
}, },
props.setFiles props.setFiles
@ -171,12 +174,12 @@ export default function Upload(props: Props) {
const uploadInit = function () { const uploadInit = function () {
setUploadStage(UPLOAD_STAGES.START); setUploadStage(UPLOAD_STAGES.START);
setFileCounter({ finished: 0, total: 0 }); setUploadCounter({ finished: 0, total: 0 });
setFileProgress(new Map<number, number>()); setInProgressUploads(new Map());
setUploadResult(new Map<number, number>()); setFinishedUploads(new Map());
setPercentComplete(0); setPercentComplete(0);
props.closeCollectionSelector(); props.closeCollectionSelector();
setProgressView(true); setUploadProgressView(true);
}; };
const resumeDesktopUpload = async ( const resumeDesktopUpload = async (
@ -307,7 +310,7 @@ export default function Upload(props: Props) {
); );
} }
} catch (e) { } catch (e) {
setProgressView(false); setUploadProgressView(false);
logError(e, 'Failed to create album'); logError(e, 'Failed to create album');
appContext.setDialogMessage({ appContext.setDialogMessage({
title: constants.ERROR, title: constants.ERROR,
@ -354,7 +357,7 @@ export default function Upload(props: Props) {
); );
} catch (err) { } catch (err) {
showUserFacingError(err.message); showUserFacingError(err.message);
setProgressView(false); setUploadProgressView(false);
throw err; throw err;
} finally { } finally {
props.setUploadInProgress(false); props.setUploadInProgress(false);
@ -371,7 +374,7 @@ export default function Upload(props: Props) {
} catch (err) { } catch (err) {
showUserFacingError(err.message); showUserFacingError(err.message);
setProgressView(false); setUploadProgressView(false);
} finally { } finally {
props.setUploadInProgress(false); props.setUploadInProgress(false);
props.syncWithRemote(); props.syncWithRemote();
@ -494,7 +497,7 @@ export default function Upload(props: Props) {
}; };
const cancelUploads = async () => { const cancelUploads = async () => {
setProgressView(false); setUploadProgressView(false);
if (isElectron()) { if (isElectron()) {
ImportService.cancelRemainingUploads(); ImportService.cancelRemainingUploads();
} }
@ -502,6 +505,8 @@ export default function Upload(props: Props) {
Router.reload(); Router.reload();
}; };
const closeUploadProgress = () => setUploadProgressView(false);
return ( return (
<> <>
<UploadStrategyChoiceModal <UploadStrategyChoiceModal
@ -532,16 +537,16 @@ export default function Upload(props: Props) {
} }
/> />
<UploadProgress <UploadProgress
now={percentComplete} open={uploadProgressView}
filenames={filenames} onClose={closeUploadProgress}
fileCounter={fileCounter} percentComplete={percentComplete}
uploadFileNames={uploadFileNames}
uploadCounter={uploadCounter}
uploadStage={uploadStage} uploadStage={uploadStage}
fileProgress={fileProgress} inProgressUploads={inProgressUploads}
hasLivePhotos={hasLivePhotos} hasLivePhotos={hasLivePhotos}
show={progressView}
closeModal={() => setProgressView(false)}
retryFailed={retryFailed} retryFailed={retryFailed}
uploadResult={uploadResult} finishedUploads={finishedUploads}
cancelUploads={cancelUploads} cancelUploads={cancelUploads}
/> />
</> </>

View file

@ -31,7 +31,7 @@ export enum UPLOAD_STAGES {
FINISH, FINISH,
} }
export enum FileUploadResults { export enum UPLOAD_RESULT {
FAILED, FAILED,
ALREADY_UPLOADED, ALREADY_UPLOADED,
UNSUPPORTED, UNSUPPORTED,

View file

@ -0,0 +1,43 @@
import { DialogProps } from '@mui/material';
import { UPLOAD_STAGES } from 'constants/upload';
import { createContext } from 'react';
import {
UploadCounter,
InProgressUpload,
UploadFileNames,
SegregatedFinishedUploads,
} from 'types/upload/ui';
interface UploadProgressContextType {
open: boolean;
onClose: DialogProps['onClose'];
uploadCounter: UploadCounter;
uploadStage: UPLOAD_STAGES;
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,
uploadStage: null,
percentComplete: null,
retryFailed: () => null,
inProgressUploads: null,
uploadFileNames: null,
finishedUploads: null,
hasLivePhotos: null,
expanded: null,
setExpanded: () => null,
};
const UploadProgressContext = createContext<UploadProgressContextType>(
defaultUploadProgressContext
);
export default UploadProgressContext;

View file

@ -1,16 +1,16 @@
import { import {
FileUploadResults, UPLOAD_RESULT,
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
UPLOAD_STAGES, UPLOAD_STAGES,
} from 'constants/upload'; } from 'constants/upload';
import { ProgressUpdater } from 'types/upload'; import { ProgressUpdater } from 'types/upload/ui';
class UIService { class UIService {
private perFileProgress: number; private perFileProgress: number;
private filesUploaded: number; private filesUploaded: number;
private totalFileCount: number; private totalFileCount: number;
private fileProgress: Map<number, number>; private inProgressUploads: Map<number, number>;
private uploadResult: Map<number, FileUploadResults>; private finishedUploads: Map<number, UPLOAD_RESULT>;
private progressUpdater: ProgressUpdater; private progressUpdater: ProgressUpdater;
init(progressUpdater: ProgressUpdater) { init(progressUpdater: ProgressUpdater) {
@ -20,8 +20,8 @@ class UIService {
reset(count: number) { reset(count: number) {
this.setTotalFileCount(count); this.setTotalFileCount(count);
this.filesUploaded = 0; this.filesUploaded = 0;
this.fileProgress = new Map<number, number>(); this.inProgressUploads = new Map<number, number>();
this.uploadResult = new Map<number, FileUploadResults>(); this.finishedUploads = new Map<number, UPLOAD_RESULT>();
this.updateProgressBarUI(); this.updateProgressBarUI();
} }
@ -31,7 +31,7 @@ class UIService {
} }
setFileProgress(key: number, progress: number) { setFileProgress(key: number, progress: number) {
this.fileProgress.set(key, progress); this.inProgressUploads.set(key, progress);
this.updateProgressBarUI(); this.updateProgressBarUI();
} }
@ -44,7 +44,7 @@ class UIService {
} }
setFilenames(filenames: Map<number, string>) { setFilenames(filenames: Map<number, string>) {
this.progressUpdater.setFilenames(filenames); this.progressUpdater.setUploadFilenames(filenames);
} }
setHasLivePhoto(hasLivePhoto: boolean) { setHasLivePhoto(hasLivePhoto: boolean) {
@ -56,18 +56,18 @@ class UIService {
this.updateProgressBarUI(); this.updateProgressBarUI();
} }
moveFileToResultList(key: number, uploadResult: FileUploadResults) { moveFileToResultList(key: number, uploadResult: UPLOAD_RESULT) {
this.uploadResult.set(key, uploadResult); this.finishedUploads.set(key, uploadResult);
this.fileProgress.delete(key); this.inProgressUploads.delete(key);
this.updateProgressBarUI(); this.updateProgressBarUI();
} }
updateProgressBarUI() { updateProgressBarUI() {
const { const {
setPercentComplete, setPercentComplete,
setFileCounter, setUploadCounter: setFileCounter,
setFileProgress, setInProgressUploads,
setUploadResult, setFinishedUploads,
} = this.progressUpdater; } = this.progressUpdater;
setFileCounter({ setFileCounter({
finished: this.filesUploaded, finished: this.filesUploaded,
@ -75,10 +75,10 @@ class UIService {
}); });
let percentComplete = let percentComplete =
this.perFileProgress * this.perFileProgress *
(this.uploadResult.size || this.filesUploaded); (this.finishedUploads.size || this.filesUploaded);
if (this.fileProgress) { if (this.inProgressUploads) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_, progress] of this.fileProgress) { for (const [_, progress] of this.inProgressUploads) {
// filter negative indicator values during percentComplete calculation // filter negative indicator values during percentComplete calculation
if (progress < 0) { if (progress < 0) {
continue; continue;
@ -88,8 +88,8 @@ class UIService {
} }
setPercentComplete(percentComplete); setPercentComplete(percentComplete);
setFileProgress(this.fileProgress); setInProgressUploads(this.inProgressUploads);
setUploadResult(this.uploadResult); setFinishedUploads(this.finishedUploads);
} }
trackUploadProgress( trackUploadProgress(
@ -108,7 +108,7 @@ class UIService {
return { return {
cancel, cancel,
onUploadProgress: (event) => { onUploadProgress: (event) => {
this.fileProgress.set( this.inProgressUploads.set(
fileLocalID, fileLocalID,
Math.min( Math.min(
Math.round( Math.round(

View file

@ -26,12 +26,11 @@ import {
MetadataAndFileTypeInfoMap, MetadataAndFileTypeInfoMap,
ParsedMetadataJSON, ParsedMetadataJSON,
ParsedMetadataJSONMap, ParsedMetadataJSONMap,
ProgressUpdater,
} from 'types/upload'; } from 'types/upload';
import { import {
UPLOAD_STAGES, UPLOAD_RESULT,
FileUploadResults,
MAX_FILE_SIZE_SUPPORTED, MAX_FILE_SIZE_SUPPORTED,
UPLOAD_STAGES,
} from 'constants/upload'; } from 'constants/upload';
import { ComlinkWorker } from 'utils/comlink'; import { ComlinkWorker } from 'utils/comlink';
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
@ -39,6 +38,7 @@ import uiService from './uiService';
import { logUploadInfo } from 'utils/upload'; import { logUploadInfo } from 'utils/upload';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import ImportService from 'services/importService'; import ImportService from 'services/importService';
import { ProgressUpdater } from 'types/upload/ui';
const MAX_CONCURRENT_UPLOADS = 4; const MAX_CONCURRENT_UPLOADS = 4;
const FILE_UPLOAD_COMPLETED = 100; const FILE_UPLOAD_COMPLETED = 100;
@ -350,7 +350,7 @@ class UploadManager {
} }
async postUploadTask( async postUploadTask(
fileUploadResult: FileUploadResults, fileUploadResult: UPLOAD_RESULT,
uploadedFile: EnteFile, uploadedFile: EnteFile,
skipDecryption: boolean, skipDecryption: boolean,
fileWithCollection: FileWithCollection fileWithCollection: FileWithCollection
@ -359,9 +359,9 @@ class UploadManager {
logUploadInfo(`uploadedFile ${JSON.stringify(uploadedFile)}`); logUploadInfo(`uploadedFile ${JSON.stringify(uploadedFile)}`);
if ( if (
(fileUploadResult === FileUploadResults.UPLOADED || (fileUploadResult === UPLOAD_RESULT.UPLOADED ||
fileUploadResult === fileUploadResult ===
FileUploadResults.UPLOADED_WITH_STATIC_THUMBNAIL) && UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL) &&
!skipDecryption !skipDecryption
) { ) {
const decryptedFile = await decryptFile( const decryptedFile = await decryptFile(
@ -387,8 +387,8 @@ class UploadManager {
.push(decryptedFile); .push(decryptedFile);
} }
if ( if (
fileUploadResult === FileUploadResults.FAILED || fileUploadResult === UPLOAD_RESULT.FAILED ||
fileUploadResult === FileUploadResults.BLOCKED fileUploadResult === UPLOAD_RESULT.BLOCKED
) { ) {
this.failedFiles.push(fileWithCollection); this.failedFiles.push(fileWithCollection);
} }

View file

@ -10,7 +10,7 @@ import UploadHttpClient from './uploadHttpClient';
import UIService from './uiService'; import UIService from './uiService';
import UploadService from './uploadService'; import UploadService from './uploadService';
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
import { FileUploadResults, MAX_FILE_SIZE_SUPPORTED } from 'constants/upload'; import { UPLOAD_RESULT, MAX_FILE_SIZE_SUPPORTED } from 'constants/upload';
import { FileWithCollection, BackupedFile, UploadFile } from 'types/upload'; import { FileWithCollection, BackupedFile, UploadFile } from 'types/upload';
import { logUploadInfo } from 'utils/upload'; import { logUploadInfo } from 'utils/upload';
import { convertBytesToHumanReadable } from 'utils/billing'; import { convertBytesToHumanReadable } from 'utils/billing';
@ -18,7 +18,7 @@ import { sleep } from 'utils/common';
import { addToCollection } from 'services/collectionService'; import { addToCollection } from 'services/collectionService';
interface UploadResponse { interface UploadResponse {
fileUploadResult: FileUploadResults; fileUploadResult: UPLOAD_RESULT;
uploadedFile?: EnteFile; uploadedFile?: EnteFile;
skipDecryption?: boolean; skipDecryption?: boolean;
} }
@ -41,7 +41,7 @@ export default async function uploader(
try { try {
const fileSize = UploadService.getAssetSize(uploadAsset); const fileSize = UploadService.getAssetSize(uploadAsset);
if (fileSize >= MAX_FILE_SIZE_SUPPORTED) { if (fileSize >= MAX_FILE_SIZE_SUPPORTED) {
return { fileUploadResult: FileUploadResults.TOO_LARGE }; return { fileUploadResult: UPLOAD_RESULT.TOO_LARGE };
} }
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) { if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
@ -52,7 +52,7 @@ export default async function uploader(
if (fileAlreadyInCollection(existingFilesInCollection, metadata)) { if (fileAlreadyInCollection(existingFilesInCollection, metadata)) {
logUploadInfo(`skipped upload for ${fileNameSize}`); logUploadInfo(`skipped upload for ${fileNameSize}`);
return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; return { fileUploadResult: UPLOAD_RESULT.ALREADY_UPLOADED };
} }
const sameFileInOtherCollection = findSameFileInOtherCollection( const sameFileInOtherCollection = findSameFileInOtherCollection(
@ -68,7 +68,7 @@ export default async function uploader(
resultFile.collectionID = collection.id; resultFile.collectionID = collection.id;
await addToCollection(collection, [resultFile]); await addToCollection(collection, [resultFile]);
return { return {
fileUploadResult: FileUploadResults.UPLOADED, fileUploadResult: UPLOAD_RESULT.UPLOADED,
uploadedFile: resultFile, uploadedFile: resultFile,
skipDecryption: true, skipDecryption: true,
}; };
@ -82,7 +82,7 @@ export default async function uploader(
fileAlreadyInCollection(existingFiles, metadata) fileAlreadyInCollection(existingFiles, metadata)
) { ) {
logUploadInfo(`deduped upload for ${fileNameSize}`); logUploadInfo(`deduped upload for ${fileNameSize}`);
return { fileUploadResult: FileUploadResults.ALREADY_UPLOADED }; return { fileUploadResult: UPLOAD_RESULT.ALREADY_UPLOADED };
} }
logUploadInfo(`reading asset ${fileNameSize}`); logUploadInfo(`reading asset ${fileNameSize}`);
@ -125,8 +125,8 @@ export default async function uploader(
return { return {
fileUploadResult: metadata.hasStaticThumbnail fileUploadResult: metadata.hasStaticThumbnail
? FileUploadResults.UPLOADED_WITH_STATIC_THUMBNAIL ? UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL
: FileUploadResults.UPLOADED, : UPLOAD_RESULT.UPLOADED,
uploadedFile: uploadedFile, uploadedFile: uploadedFile,
}; };
} catch (e) { } catch (e) {
@ -140,16 +140,16 @@ export default async function uploader(
const error = handleUploadError(e); const error = handleUploadError(e);
switch (error.message) { switch (error.message) {
case CustomError.ETAG_MISSING: case CustomError.ETAG_MISSING:
return { fileUploadResult: FileUploadResults.BLOCKED }; return { fileUploadResult: UPLOAD_RESULT.BLOCKED };
case CustomError.UNSUPPORTED_FILE_FORMAT: case CustomError.UNSUPPORTED_FILE_FORMAT:
return { fileUploadResult: FileUploadResults.UNSUPPORTED }; return { fileUploadResult: UPLOAD_RESULT.UNSUPPORTED };
case CustomError.FILE_TOO_LARGE: case CustomError.FILE_TOO_LARGE:
return { return {
fileUploadResult: fileUploadResult:
FileUploadResults.LARGER_THAN_AVAILABLE_STORAGE, UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE,
}; };
default: default:
return { fileUploadResult: FileUploadResults.FAILED }; return { fileUploadResult: UPLOAD_RESULT.FAILED };
} }
} }
} }

View file

@ -1,5 +1,4 @@
import { FILE_TYPE } from 'constants/file'; import { FILE_TYPE } from 'constants/file';
import { UPLOAD_STAGES } from 'constants/upload';
import { Collection } from 'types/collection'; import { Collection } from 'types/collection';
import { fileAttribute } from 'types/file'; import { fileAttribute } from 'types/file';
@ -56,21 +55,6 @@ export interface FileTypeInfo {
videoType?: string; videoType?: string;
} }
export interface ProgressUpdater {
setPercentComplete: React.Dispatch<React.SetStateAction<number>>;
setFileCounter: React.Dispatch<
React.SetStateAction<{
finished: number;
total: number;
}>
>;
setUploadStage: React.Dispatch<React.SetStateAction<UPLOAD_STAGES>>;
setFileProgress: React.Dispatch<React.SetStateAction<Map<number, number>>>;
setUploadResult: React.Dispatch<React.SetStateAction<Map<number, number>>>;
setFilenames: React.Dispatch<React.SetStateAction<Map<number, string>>>;
setHasLivePhotos: React.Dispatch<React.SetStateAction<boolean>>;
}
/* /*
* ElectronFile is a custom interface that is used to represent * ElectronFile is a custom interface that is used to represent
* any file on disk as a File-like object in the Electron desktop app. * any file on disk as a File-like object in the Electron desktop app.

40
src/types/upload/ui.ts Normal file
View file

@ -0,0 +1,40 @@
import { UPLOAD_RESULT, UPLOAD_STAGES } from 'constants/upload';
export type FileID = number;
export type FileName = string;
export type PercentageUploaded = number;
export type UploadFileNames = Map<FileID, FileName>;
export interface UploadCounter {
finished: number;
total: number;
}
export interface InProgressUpload {
localFileID: FileID;
progress: PercentageUploaded;
}
export interface FinishedUpload {
localFileID: FileID;
result: UPLOAD_RESULT;
}
export type InProgressUploads = Map<FileID, PercentageUploaded>;
export type FinishedUploads = Map<FileID, UPLOAD_RESULT>;
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>>;
setInProgressUploads: React.Dispatch<
React.SetStateAction<InProgressUploads>
>;
setFinishedUploads: React.Dispatch<React.SetStateAction<FinishedUploads>>;
setUploadFilenames: React.Dispatch<React.SetStateAction<UploadFileNames>>;
setHasLivePhotos: React.Dispatch<React.SetStateAction<boolean>>;
}