diff --git a/src/components/UploadProgress/InProgressSection.tsx b/src/components/UploadProgress/InProgressSection.tsx new file mode 100644 index 000000000..dea782772 --- /dev/null +++ b/src/components/UploadProgress/InProgressSection.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import FileList from 'components/FileList'; +import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { SectionInfo, InProgressItemContainer } from './styledComponents'; +import { FileProgresses } from '.'; + +export interface InProgressProps { + filenames: Map; + sectionTitle: string; + fileProgressStatuses: FileProgresses[]; + sectionInfo?: any; +} +export const InProgressSection = (props: InProgressProps) => { + const fileList = props.fileProgressStatuses ?? []; + + return ( + + }> + {props.sectionTitle} + + + {props.sectionInfo && ( + {props.sectionInfo} + )} + ( + + {props.filenames.get(fileID)} + {`-`} + {`${progress}%`} + + ))} + /> + + + ); +}; diff --git a/src/components/UploadProgress/Minimized.tsx b/src/components/UploadProgress/Minimized.tsx new file mode 100644 index 000000000..56bc105c1 --- /dev/null +++ b/src/components/UploadProgress/Minimized.tsx @@ -0,0 +1,21 @@ +import { Snackbar, Paper } from '@mui/material'; +import React from 'react'; +import { UploadProgressHeader } from './header'; +export function MinimizedUploadProgress(props) { + return ( + } + open={true} + anchorOrigin={{ + horizontal: 'right', + vertical: 'bottom', + }}> + + + + + ); +} diff --git a/src/components/UploadProgress/ResultSection.tsx b/src/components/UploadProgress/ResultSection.tsx new file mode 100644 index 000000000..4a8e66bea --- /dev/null +++ b/src/components/UploadProgress/ResultSection.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import FileList from 'components/FileList'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { SectionInfo, ResultItemContainer } from './styledComponents'; +import { FileUploadResults } from 'constants/upload'; + +export interface ResultSectionProps { + filenames: Map; + fileUploadResultMap: Map; + fileUploadResult: FileUploadResults; + sectionTitle: any; + sectionInfo?: any; +} +export const ResultSection = (props: ResultSectionProps) => { + const fileList = props.fileUploadResultMap?.get(props.fileUploadResult); + if (!fileList?.length) { + return <>; + } + return ( + + }> + {props.sectionTitle} + + + {props.sectionInfo && ( + {props.sectionInfo} + )} + ( + + {props.filenames.get(fileID)} + + ))} + /> + + + ); +}; diff --git a/src/components/UploadProgress/dialog.tsx b/src/components/UploadProgress/dialog.tsx new file mode 100644 index 000000000..84471f3fe --- /dev/null +++ b/src/components/UploadProgress/dialog.tsx @@ -0,0 +1,121 @@ +import { Dialog, DialogContent } from '@mui/material'; +import constants from 'utils/strings/constants'; +import { UPLOAD_STAGES, FileUploadResults } from 'constants/upload'; +import React from 'react'; +import { UploadProgressFooter } from './footer'; +import { UploadProgressHeader } from './header'; +import { InProgressSection } from './inProgressSection'; +import { ResultSection } from './resultSection'; +import { NotUploadSectionHeader } from './styledComponents'; +import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common'; +export function UploadProgressDialog({ + handleHideModal, + setExpanded, + expanded, + fileProgressStatuses, + sectionInfo, + fileUploadResultMap, + filesNotUploaded, + ...props +}) { + return ( + + + + {props.uploadStage === UPLOAD_STAGES.UPLOADING && ( + + )} + + + + {props.uploadStage === UPLOAD_STAGES.FINISH && + filesNotUploaded && ( + + {constants.FILE_NOT_UPLOADED_LIST} + + )} + + + + + + + + + + {props.uploadStage === UPLOAD_STAGES.FINISH && ( + + )} + + ); +} diff --git a/src/components/UploadProgress/footer.tsx b/src/components/UploadProgress/footer.tsx new file mode 100644 index 000000000..c1d73b8a9 --- /dev/null +++ b/src/components/UploadProgress/footer.tsx @@ -0,0 +1,28 @@ +import { Button, DialogActions } from '@mui/material'; +import { UPLOAD_STAGES, FileUploadResults } from 'constants/upload'; +import React from 'react'; +import constants from 'utils/strings/constants'; +export function UploadProgressFooter({ + uploadStage, + fileUploadResultMap, + retryFailed, + closeModal, +}) { + return ( + + {uploadStage === UPLOAD_STAGES.FINISH && + (fileUploadResultMap?.get(FileUploadResults.FAILED)?.length > + 0 || + fileUploadResultMap?.get(FileUploadResults.BLOCKED)?.length > + 0 ? ( + + ) : ( + + ))} + + ); +} diff --git a/src/components/UploadProgress/header.tsx b/src/components/UploadProgress/header.tsx new file mode 100644 index 000000000..3ebdb21f5 --- /dev/null +++ b/src/components/UploadProgress/header.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { UploadProgressBar } from './progressBar'; +import { UploadProgressTitle } from './title'; + +export function UploadProgressHeader({ + uploadStage, + setExpanded, + expanded, + handleHideModal, + fileCounter, + now, +}) { + return ( + <> + + + + ); +} diff --git a/src/components/UploadProgress/inProgressSection.tsx b/src/components/UploadProgress/inProgressSection.tsx new file mode 100644 index 000000000..dea782772 --- /dev/null +++ b/src/components/UploadProgress/inProgressSection.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import FileList from 'components/FileList'; +import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { SectionInfo, InProgressItemContainer } from './styledComponents'; +import { FileProgresses } from '.'; + +export interface InProgressProps { + filenames: Map; + sectionTitle: string; + fileProgressStatuses: FileProgresses[]; + sectionInfo?: any; +} +export const InProgressSection = (props: InProgressProps) => { + const fileList = props.fileProgressStatuses ?? []; + + return ( + + }> + {props.sectionTitle} + + + {props.sectionInfo && ( + {props.sectionInfo} + )} + ( + + {props.filenames.get(fileID)} + {`-`} + {`${progress}%`} + + ))} + /> + + + ); +}; diff --git a/src/components/UploadProgress/index.tsx b/src/components/UploadProgress/index.tsx new file mode 100644 index 000000000..a891de55e --- /dev/null +++ b/src/components/UploadProgress/index.tsx @@ -0,0 +1,102 @@ +import { UploadProgressDialog } from './dialog'; +import { MinimizedUploadProgress } from './minimized'; +import React, { useContext, useState } from 'react'; + +import constants from 'utils/strings/constants'; +import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload'; +import { AppContext } from 'pages/_app'; + +interface Props { + fileCounter; + uploadStage; + now; + closeModal; + retryFailed; + fileProgress: Map; + filenames: Map; + show; + uploadResult: Map; + hasLivePhotos: boolean; + cancelUploads: () => void; +} +export interface FileProgresses { + fileID: number; + progress: number; +} + +export default function UploadProgress(props: Props) { + const appContext = useContext(AppContext); + const [expanded, setExpanded] = useState(true); + const fileProgressStatuses = [] as FileProgresses[]; + const fileUploadResultMap = new Map(); + 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) { + filesNotUploaded = true; + } + const fileList = fileUploadResultMap.get(progress); + + fileUploadResultMap.set(progress, [...fileList, localID]); + } + } + if (props.hasLivePhotos) { + sectionInfo = constants.LIVE_PHOTOS_DETECTED(); + } + + function handleHideModal() { + if (props.uploadStage !== UPLOAD_STAGES.FINISH) { + appContext.setDialogMessage({ + title: constants.STOP_UPLOADS_HEADER, + content: constants.STOP_ALL_UPLOADS_MESSAGE, + proceed: { + text: constants.YES_STOP_UPLOADS, + variant: 'danger', + action: props.cancelUploads, + }, + close: { + text: constants.NO, + variant: 'secondary', + action: () => {}, + }, + }); + } else { + props.closeModal(); + } + } + return ( + <> + {expanded ? ( + + ) : ( + + )} + + ); +} diff --git a/src/components/UploadProgress/minimized.tsx b/src/components/UploadProgress/minimized.tsx new file mode 100644 index 000000000..56bc105c1 --- /dev/null +++ b/src/components/UploadProgress/minimized.tsx @@ -0,0 +1,21 @@ +import { Snackbar, Paper } from '@mui/material'; +import React from 'react'; +import { UploadProgressHeader } from './header'; +export function MinimizedUploadProgress(props) { + return ( + } + open={true} + anchorOrigin={{ + horizontal: 'right', + vertical: 'bottom', + }}> + + + + + ); +} diff --git a/src/components/UploadProgress/progressBar.tsx b/src/components/UploadProgress/progressBar.tsx new file mode 100644 index 000000000..ac2cdf8a6 --- /dev/null +++ b/src/components/UploadProgress/progressBar.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { LinearProgress, Divider } from '@mui/material'; +import { UPLOAD_STAGES } from 'constants/upload'; + +export function UploadProgressBar({ uploadStage, now }) { + return ( + <> + {(uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES || + uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA || + uploadStage === UPLOAD_STAGES.UPLOADING) && ( + + )} + + + ); +} diff --git a/src/components/UploadProgress/resultSection.tsx b/src/components/UploadProgress/resultSection.tsx new file mode 100644 index 000000000..4a8e66bea --- /dev/null +++ b/src/components/UploadProgress/resultSection.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import FileList from 'components/FileList'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { SectionInfo, ResultItemContainer } from './styledComponents'; +import { FileUploadResults } from 'constants/upload'; + +export interface ResultSectionProps { + filenames: Map; + fileUploadResultMap: Map; + fileUploadResult: FileUploadResults; + sectionTitle: any; + sectionInfo?: any; +} +export const ResultSection = (props: ResultSectionProps) => { + const fileList = props.fileUploadResultMap?.get(props.fileUploadResult); + if (!fileList?.length) { + return <>; + } + return ( + + }> + {props.sectionTitle} + + + {props.sectionInfo && ( + {props.sectionInfo} + )} + ( + + {props.filenames.get(fileID)} + + ))} + /> + + + ); +}; diff --git a/src/components/UploadProgress/styledComponents.tsx b/src/components/UploadProgress/styledComponents.tsx new file mode 100644 index 000000000..6bf26a10d --- /dev/null +++ b/src/components/UploadProgress/styledComponents.tsx @@ -0,0 +1,62 @@ +import { + getVariantColor, + ButtonVariant, +} from 'components/pages/gallery/LinkButton'; +import styled from 'styled-components'; + +export const SectionTitle = styled.div` + display: flex; + justify-content: space-between; + color: #eee; + font-size: 20px; + cursor: pointer; +`; + +export const Section = styled.div` + margin: 20px 0; + padding: 0 20px; +`; +export const SectionInfo = styled.div` + margin: 4px 0; + padding-left: 15px; +`; + +export const SectionContent = styled.div` + padding-right: 35px; +`; + +export const NotUploadSectionHeader = styled.div` + margin-top: 30px; + text-align: center; + color: ${getVariantColor(ButtonVariant.warning)}; + border-bottom: 1px solid ${getVariantColor(ButtonVariant.warning)}; + margin: 0 20px; +`; + +export const InProgressItemContainer = styled.div` + display: inline-block; + & > span { + display: inline-block; + } + & > span:first-of-type { + position: relative; + top: 5px; + max-width: 287px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + & > .separator { + margin: 0 5px; + } +`; + +export const ResultItemContainer = styled.div` + position: relative; + top: 5px; + display: inline-block; + max-width: 334px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +`; diff --git a/src/components/UploadProgress/title.tsx b/src/components/UploadProgress/title.tsx new file mode 100644 index 000000000..21ca1fc12 --- /dev/null +++ b/src/components/UploadProgress/title.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import Close from '@mui/icons-material/Close'; +import { + DialogTitle, + Box, + Typography, + IconButton, + styled, + Stack, +} from '@mui/material'; +import { SpaceBetweenFlex } from 'components/Container'; +import { UPLOAD_STAGES } from 'constants/upload'; +import constants from 'utils/strings/constants'; +import { MaximizeIcon } from 'components/icons/Maximize'; +import { MinimizeIcon } from 'components/icons/Minimize'; + +const IconButtonWithBG = styled(IconButton)(({ theme }) => ({ + backgroundColor: theme.palette.secondary.main, +})); + +const UploadProgressTitleText = (expanded) => ( + + {constants.FILE_UPLOAD} + +); + +function UploadProgressSubtitleText(props) { + return ( + + {props.uploadStage === UPLOAD_STAGES.UPLOADING + ? constants.UPLOAD_STAGE_MESSAGE[props.uploadStage]( + props.fileCounter + ) + : constants.UPLOAD_STAGE_MESSAGE[props.uploadStage]} + + ); +} + +export function UploadProgressTitle({ + setExpanded, + expanded, + handleHideModal, + ...props +}) { + const toggleExpanded = () => setExpanded((expanded) => !expanded); + + return ( + + + + + + + + + + {expanded ? : } + + + + + + + + + ); +} diff --git a/src/components/icons/Maximize.tsx b/src/components/icons/Maximize.tsx new file mode 100644 index 000000000..7c3df7a9b --- /dev/null +++ b/src/components/icons/Maximize.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import OpenInFullIcon from '@mui/icons-material/OpenInFull'; + +export function MaximizeIcon() { + return ( + + ); +} diff --git a/src/components/icons/Minimize.tsx b/src/components/icons/Minimize.tsx new file mode 100644 index 000000000..bf98dcf83 --- /dev/null +++ b/src/components/icons/Minimize.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; + +export function MinimizeIcon() { + return ( + + ); +} diff --git a/src/components/pages/gallery/Upload.tsx b/src/components/pages/gallery/Upload.tsx index 76ebea4c0..0387bc938 100644 --- a/src/components/pages/gallery/Upload.tsx +++ b/src/components/pages/gallery/Upload.tsx @@ -3,7 +3,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { syncCollections, createAlbum } from 'services/collectionService'; import constants from 'utils/strings/constants'; -import UploadProgress from './UploadProgress'; +import UploadProgress from '../../UploadProgress'; import UploadStrategyChoiceModal from './UploadStrategyChoiceModal'; import { SetCollectionNamerAttributes } from '../../Collections/CollectionNamer'; diff --git a/src/components/pages/gallery/UploadProgress.tsx b/src/components/pages/gallery/UploadProgress.tsx deleted file mode 100644 index 4c8fda830..000000000 --- a/src/components/pages/gallery/UploadProgress.tsx +++ /dev/null @@ -1,416 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import React, { useContext, useState } from 'react'; - -import styled from 'styled-components'; -import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common'; -import constants from 'utils/strings/constants'; -import { ButtonVariant, getVariantColor } from './LinkButton'; -import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload'; -import FileList from 'components/FileList'; -import { AppContext } from 'pages/_app'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Divider, - IconButton, - LinearProgress, - Typography, -} from '@mui/material'; -import { FlexWrapper, SpaceBetweenFlex } from 'components/Container'; -import Close from '@mui/icons-material/Close'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import OpenInFullIcon from '@mui/icons-material/OpenInFull'; -import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; -interface Props { - fileCounter; - uploadStage; - now; - closeModal; - retryFailed; - fileProgress: Map; - filenames: Map; - show; - uploadResult: Map; - hasLivePhotos: boolean; - cancelUploads: () => void; -} -interface FileProgresses { - fileID: number; - progress: number; -} - -const SectionTitle = styled.div` - display: flex; - justify-content: space-between; - color: #eee; - font-size: 20px; - cursor: pointer; -`; - -const Section = styled.div` - margin: 20px 0; - padding: 0 20px; -`; -const SectionInfo = styled.div` - margin: 4px 0; - padding-left: 15px; -`; - -const SectionContent = styled.div` - padding-right: 35px; -`; - -const NotUploadSectionHeader = styled.div` - margin-top: 30px; - text-align: center; - color: ${getVariantColor(ButtonVariant.warning)}; - border-bottom: 1px solid ${getVariantColor(ButtonVariant.warning)}; - margin: 0 20px; -`; - -const InProgressItemContainer = styled.div` - display: inline-block; - & > span { - display: inline-block; - } - & > span:first-of-type { - position: relative; - top: 5px; - max-width: 287px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - & > .separator { - margin: 0 5px; - } -`; - -const ResultItemContainer = styled.div` - position: relative; - top: 5px; - display: inline-block; - max-width: 334px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -`; - -interface ResultSectionProps { - filenames: Map; - fileUploadResultMap: Map; - fileUploadResult: FileUploadResults; - sectionTitle: any; - sectionInfo?: any; -} -const ResultSection = (props: ResultSectionProps) => { - const fileList = props.fileUploadResultMap?.get(props.fileUploadResult); - if (!fileList?.length) { - return <>; - } - return ( - - }> - {props.sectionTitle} - - - {props.sectionInfo && ( - {props.sectionInfo} - )} - ( - - {props.filenames.get(fileID)} - - ))} - /> - - - ); -}; - -interface InProgressProps { - filenames: Map; - sectionTitle: string; - fileProgressStatuses: FileProgresses[]; - sectionInfo?: any; -} -const InProgressSection = (props: InProgressProps) => { - const fileList = props.fileProgressStatuses ?? []; - - return ( - - }> - {props.sectionTitle} - - - {props.sectionInfo && ( - {props.sectionInfo} - )} - ( - - {props.filenames.get(fileID)} - {`-`} - {`${progress}%`} - - ))} - /> - - - ); -}; - -export default function UploadProgress(props: Props) { - const appContext = useContext(AppContext); - const [expanded, setExpanded] = useState(true); - const fileProgressStatuses = [] as FileProgresses[]; - const fileUploadResultMap = new Map(); - 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) { - filesNotUploaded = true; - } - const fileList = fileUploadResultMap.get(progress); - - fileUploadResultMap.set(progress, [...fileList, localID]); - } - } - if (props.hasLivePhotos) { - sectionInfo = constants.LIVE_PHOTOS_DETECTED(); - } - - function handleHideModal() { - if (props.uploadStage !== UPLOAD_STAGES.FINISH) { - appContext.setDialogMessage({ - title: constants.STOP_UPLOADS_HEADER, - content: constants.STOP_ALL_UPLOADS_MESSAGE, - proceed: { - text: constants.YES_STOP_UPLOADS, - variant: 'danger', - action: props.cancelUploads, - }, - close: { - text: constants.NO, - variant: 'secondary', - action: () => {}, - }, - }); - } else { - props.closeModal(); - } - } - return ( - <> - - - - - - {constants.FILE_UPLOAD} - - - {props.uploadStage === UPLOAD_STAGES.UPLOADING - ? constants.UPLOAD_STAGE_MESSAGE[ - props.uploadStage - ](props.fileCounter) - : constants.UPLOAD_STAGE_MESSAGE[ - props.uploadStage - ]} - - - - - setExpanded((e) => !e)} - sx={{ - m: 0.5, - backgroundColor: (theme) => - theme.palette.secondary.main, - }}> - {expanded ? ( - - ) : ( - - )} - - - theme.palette.secondary.main, - }}> - - - - - - - {(props.uploadStage === - UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES || - props.uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA || - props.uploadStage === UPLOAD_STAGES.UPLOADING) && ( - - )} - - {expanded && ( - - {props.uploadStage === UPLOAD_STAGES.UPLOADING && ( - - )} - - - - {props.uploadStage === UPLOAD_STAGES.FINISH && - filesNotUploaded && ( - - {constants.FILE_NOT_UPLOADED_LIST} - - )} - - - - - - - - - )} - {props.uploadStage === UPLOAD_STAGES.FINISH && ( - - {props.uploadStage === UPLOAD_STAGES.FINISH && - (fileUploadResultMap?.get(FileUploadResults.FAILED) - ?.length > 0 || - fileUploadResultMap?.get(FileUploadResults.BLOCKED) - ?.length > 0 ? ( - - ) : ( - - ))} - - )} - - - ); -}