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

View file

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

View file

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

View file

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

View file

@ -1,64 +1,71 @@
import { UploadProgressDialog } from './dialog';
import { MinimizedUploadProgress } from './minimized';
import React, { useContext, useState } from 'react';
import React, { useContext, useMemo, useState } from 'react';
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 { dialogCloseHandler } from 'components/DialogBox/base';
import {
UploadFileNames,
UploadCounter,
InProgressUploads,
InProgressUpload,
FinishedUploads,
SegregatedFinishedUploads,
} from 'types/upload/ui';
import UploadProgressContext from 'contexts/uploadProgress';
interface Props {
fileCounter;
uploadStage;
now;
closeModal;
retryFailed;
fileProgress: Map<number, number>;
filenames: Map<number, string>;
show;
uploadResult: Map<number, FileUploadResults>;
open: boolean;
onClose: () => void;
uploadCounter: UploadCounter;
uploadStage: UPLOAD_STAGES;
percentComplete: number;
retryFailed: () => void;
inProgressUploads: InProgressUploads;
uploadFileNames: UploadFileNames;
finishedUploads: FinishedUploads;
hasLivePhotos: boolean;
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 [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);
}
}
if (props.hasLivePhotos) {
sectionInfo = constants.LIVE_PHOTOS_DETECTED;
}
return finishedUploads;
}, [props.finishedUploads]);
function confirmCancelUpload() {
appContext.setDialogMessage({
@ -78,10 +85,10 @@ export default function UploadProgress(props: Props) {
}
function onClose() {
if (props.uploadStage !== UPLOAD_STAGES.FINISH) {
if (uploadStage !== UPLOAD_STAGES.FINISH) {
confirmCancelUpload();
} else {
props.closeModal();
props.onClose();
}
}
@ -90,26 +97,22 @@ export default function UploadProgress(props: Props) {
});
return (
<>
{expanded ? (
<UploadProgressDialog
handleClose={handleClose}
setExpanded={setExpanded}
expanded={expanded}
fileProgressStatuses={fileProgressStatuses}
sectionInfo={sectionInfo}
fileUploadResultMap={fileUploadResultMap}
filesNotUploaded={filesNotUploaded}
{...props}
/>
) : (
<MinimizedUploadProgress
setExpanded={setExpanded}
expanded={expanded}
handleClose={handleClose}
{...props}
/>
)}
</>
<UploadProgressContext.Provider
value={{
open,
onClose: handleClose,
uploadCounter,
uploadStage,
percentComplete,
retryFailed,
inProgressUploads,
uploadFileNames,
finishedUploads,
hasLivePhotos,
expanded,
setExpanded,
}}>
{expanded ? <UploadProgressDialog /> : <MinimizedUploadProgress />}
</UploadProgressContext.Provider>
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,7 +31,7 @@ export enum UPLOAD_STAGES {
FINISH,
}
export enum FileUploadResults {
export enum UPLOAD_RESULT {
FAILED,
ALREADY_UPLOADED,
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 {
FileUploadResults,
UPLOAD_RESULT,
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
UPLOAD_STAGES,
} from 'constants/upload';
import { ProgressUpdater } from 'types/upload';
import { ProgressUpdater } from 'types/upload/ui';
class UIService {
private perFileProgress: number;
private filesUploaded: number;
private totalFileCount: number;
private fileProgress: Map<number, number>;
private uploadResult: Map<number, FileUploadResults>;
private inProgressUploads: Map<number, number>;
private finishedUploads: Map<number, UPLOAD_RESULT>;
private progressUpdater: ProgressUpdater;
init(progressUpdater: ProgressUpdater) {
@ -20,8 +20,8 @@ class UIService {
reset(count: number) {
this.setTotalFileCount(count);
this.filesUploaded = 0;
this.fileProgress = new Map<number, number>();
this.uploadResult = new Map<number, FileUploadResults>();
this.inProgressUploads = new Map<number, number>();
this.finishedUploads = new Map<number, UPLOAD_RESULT>();
this.updateProgressBarUI();
}
@ -31,7 +31,7 @@ class UIService {
}
setFileProgress(key: number, progress: number) {
this.fileProgress.set(key, progress);
this.inProgressUploads.set(key, progress);
this.updateProgressBarUI();
}
@ -44,7 +44,7 @@ class UIService {
}
setFilenames(filenames: Map<number, string>) {
this.progressUpdater.setFilenames(filenames);
this.progressUpdater.setUploadFilenames(filenames);
}
setHasLivePhoto(hasLivePhoto: boolean) {
@ -56,18 +56,18 @@ class UIService {
this.updateProgressBarUI();
}
moveFileToResultList(key: number, uploadResult: FileUploadResults) {
this.uploadResult.set(key, uploadResult);
this.fileProgress.delete(key);
moveFileToResultList(key: number, uploadResult: UPLOAD_RESULT) {
this.finishedUploads.set(key, uploadResult);
this.inProgressUploads.delete(key);
this.updateProgressBarUI();
}
updateProgressBarUI() {
const {
setPercentComplete,
setFileCounter,
setFileProgress,
setUploadResult,
setUploadCounter: setFileCounter,
setInProgressUploads,
setFinishedUploads,
} = this.progressUpdater;
setFileCounter({
finished: this.filesUploaded,
@ -75,10 +75,10 @@ class UIService {
});
let percentComplete =
this.perFileProgress *
(this.uploadResult.size || this.filesUploaded);
if (this.fileProgress) {
(this.finishedUploads.size || this.filesUploaded);
if (this.inProgressUploads) {
// 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
if (progress < 0) {
continue;
@ -88,8 +88,8 @@ class UIService {
}
setPercentComplete(percentComplete);
setFileProgress(this.fileProgress);
setUploadResult(this.uploadResult);
setInProgressUploads(this.inProgressUploads);
setFinishedUploads(this.finishedUploads);
}
trackUploadProgress(
@ -108,7 +108,7 @@ class UIService {
return {
cancel,
onUploadProgress: (event) => {
this.fileProgress.set(
this.inProgressUploads.set(
fileLocalID,
Math.min(
Math.round(

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import { FILE_TYPE } from 'constants/file';
import { UPLOAD_STAGES } from 'constants/upload';
import { Collection } from 'types/collection';
import { fileAttribute } from 'types/file';
@ -56,21 +55,6 @@ export interface FileTypeInfo {
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
* 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>>;
}