Merge branch 'master' into upload-refactoring
This commit is contained in:
commit
5f386f3c93
|
@ -9,7 +9,7 @@ import router from 'next/router';
|
||||||
import { changeEmail, getOTTForEmailChange } from 'services/userService';
|
import { changeEmail, getOTTForEmailChange } from 'services/userService';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { AppContext } from 'pages/_app';
|
import { AppContext } from 'pages/_app';
|
||||||
import englishConstants from 'utils/strings/englishConstants';
|
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||||
|
|
||||||
interface formValues {
|
interface formValues {
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -72,6 +72,7 @@ function ChangeEmailForm(props:Props) {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await changeEmail(email, ott);
|
await changeEmail(email, ott);
|
||||||
|
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
|
||||||
appContext.setDisappearingFlashMessage({ message: constants.EMAIL_UDPATE_SUCCESSFUL, severity: 'success' });
|
appContext.setDisappearingFlashMessage({ message: constants.EMAIL_UDPATE_SUCCESSFUL, severity: 'success' });
|
||||||
router.push('/gallery');
|
router.push('/gallery');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -125,7 +126,7 @@ function ChangeEmailForm(props:Props) {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs ="4" >
|
<Col xs ="4" >
|
||||||
<Button variant="link" onClick={()=>setShowOttInputVisibility(false)}>
|
<Button variant="link" onClick={()=>setShowOttInputVisibility(false)}>
|
||||||
{englishConstants.CHANGE}
|
{constants.CHANGE}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</EmailRow>
|
</EmailRow>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { VariableSizeList as List } from 'react-window';
|
||||||
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
|
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
|
||||||
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
|
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
|
||||||
import { SetDialogMessage } from './MessageDialog';
|
import { SetDialogMessage } from './MessageDialog';
|
||||||
import { VIDEO_PLAYBACK_FAILED } from 'utils/common/errorUtil';
|
import { CustomError } from 'utils/common/errorUtil';
|
||||||
import {
|
import {
|
||||||
GAP_BTW_TILES, DATE_CONTAINER_HEIGHT, IMAGE_CONTAINER_MAX_HEIGHT,
|
GAP_BTW_TILES, DATE_CONTAINER_HEIGHT, IMAGE_CONTAINER_MAX_HEIGHT,
|
||||||
IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS, SPACE_BTW_DATES,
|
IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS, SPACE_BTW_DATES,
|
||||||
|
@ -306,7 +306,7 @@ const PhotoFrame = ({
|
||||||
const t = setTimeout(
|
const t = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
reject(
|
reject(
|
||||||
Error(`${VIDEO_PLAYBACK_FAILED} err: wait time exceeded`),
|
Error(`${CustomError.VIDEO_PLAYBACK_FAILED} err: wait time exceeded`),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
WAIT_FOR_VIDEO_PLAYBACK,
|
WAIT_FOR_VIDEO_PLAYBACK,
|
||||||
|
|
|
@ -60,6 +60,8 @@ export default function Sidebar(props: Props) {
|
||||||
setUser({ ...user, email: userDetails.email });
|
setUser({ ...user, email: userDetails.email });
|
||||||
SetUsage(convertToHumanReadable(userDetails.usage));
|
SetUsage(convertToHumanReadable(userDetails.usage));
|
||||||
setSubscription(userDetails.subscription);
|
setSubscription(userDetails.subscription);
|
||||||
|
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email: userDetails.email });
|
||||||
|
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
|
||||||
};
|
};
|
||||||
main();
|
main();
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
|
@ -19,16 +19,18 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Group = styled.div`
|
const Group = styled.div`
|
||||||
display: flex;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
width: 36px;
|
width: 46px;
|
||||||
height: 36px;
|
height: 34px;
|
||||||
margin-left: -36px;
|
position: absolute;
|
||||||
display: flex;
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -77,10 +79,10 @@ export default function SingleInputForm(props: Props) {
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
</Group>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
<Form.Control.Feedback type="invalid">
|
||||||
{errors.passphrase}
|
{errors.passphrase}
|
||||||
</Form.Control.Feedback>
|
</Form.Control.Feedback>
|
||||||
|
</Group>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
buttonText={props.buttonText}
|
buttonText={props.buttonText}
|
||||||
|
|
26
src/components/icons/ExpandLess.tsx
Normal file
26
src/components/icons/ExpandLess.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
export default function ExpandLess(props) {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height={props.height}
|
||||||
|
viewBox={props.viewBox}
|
||||||
|
width={props.width}
|
||||||
|
fill="#000000">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none"/>
|
||||||
|
<path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14l-6-6z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpandLess.defaultProps = {
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
open: false,
|
||||||
|
};
|
26
src/components/icons/ExpandMore.tsx
Normal file
26
src/components/icons/ExpandMore.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
export default function ExpandMore(props) {
|
||||||
|
return (
|
||||||
|
<div >
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height={props.height}
|
||||||
|
viewBox={props.viewBox}
|
||||||
|
width={props.width}
|
||||||
|
fill="#000000">
|
||||||
|
<path d="M24 24H0V0h24v24z" fill="none" opacity=".87"/>
|
||||||
|
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpandMore.defaultProps = {
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
open: false,
|
||||||
|
};
|
|
@ -1,16 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Alert from 'react-bootstrap/Alert';
|
import Alert from 'react-bootstrap/Alert';
|
||||||
|
import { getVariantColor } from './LinkButton';
|
||||||
|
|
||||||
export default function AlertBanner({ bannerMessage }) {
|
interface Props{
|
||||||
|
bannerMessage?:any
|
||||||
|
variant?:string
|
||||||
|
children?:any
|
||||||
|
}
|
||||||
|
export default function AlertBanner(props:Props) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
variant="danger"
|
variant={props.variant??'danger'}
|
||||||
style={{
|
style={{
|
||||||
display: bannerMessage ? 'block' : 'none',
|
display: props.bannerMessage || props.children ? 'block' : 'none',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
background: 'none',
|
||||||
|
borderRadius: '0px',
|
||||||
|
color: getVariantColor(props.variant),
|
||||||
|
padding: 0,
|
||||||
|
margin: '0 25px',
|
||||||
|
marginBottom: '10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{bannerMessage}
|
{props.bannerMessage?props.bannerMessage:props.children}
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ enum ButtonVariant {
|
||||||
success = 'success',
|
success = 'success',
|
||||||
danger = 'danger',
|
danger = 'danger',
|
||||||
secondary = 'secondary',
|
secondary = 'secondary',
|
||||||
|
warning='warning'
|
||||||
}
|
}
|
||||||
type Props = React.PropsWithChildren<{
|
type Props = React.PropsWithChildren<{
|
||||||
onClick: any;
|
onClick: any;
|
||||||
|
@ -11,8 +12,7 @@ type Props = React.PropsWithChildren<{
|
||||||
style?: any;
|
style?: any;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function LinkButton(props: Props) {
|
export function getVariantColor(variant: string) {
|
||||||
function getButtonColor(variant: string) {
|
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case ButtonVariant.success:
|
case ButtonVariant.success:
|
||||||
return '#2dc262';
|
return '#2dc262';
|
||||||
|
@ -20,14 +20,17 @@ export default function LinkButton(props: Props) {
|
||||||
return '#c93f3f';
|
return '#c93f3f';
|
||||||
case ButtonVariant.secondary:
|
case ButtonVariant.secondary:
|
||||||
return '#858585';
|
return '#858585';
|
||||||
|
case ButtonVariant.warning:
|
||||||
|
return '#D7BB63';
|
||||||
default:
|
default:
|
||||||
return '#d1d1d1';
|
return '#d1d1d1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export default function LinkButton(props: Props) {
|
||||||
return (
|
return (
|
||||||
<h5
|
<h5
|
||||||
style={{
|
style={{
|
||||||
color: getButtonColor(props.variant),
|
color: getVariantColor(props.variant),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
...props.style,
|
...props.style,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import DeleteIcon from 'components/icons/DeleteIcon';
|
||||||
import CrossIcon from 'components/icons/CrossIcon';
|
import CrossIcon from 'components/icons/CrossIcon';
|
||||||
import AddIcon from 'components/icons/AddIcon';
|
import AddIcon from 'components/icons/AddIcon';
|
||||||
import { IconButton } from 'components/Container';
|
import { IconButton } from 'components/Container';
|
||||||
import constants from 'utils/strings/englishConstants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
addToCollectionHelper: (collectionName, collection) => void;
|
addToCollectionHelper: (collectionName, collection) => void;
|
||||||
|
|
|
@ -38,6 +38,7 @@ interface AnalysisResult {
|
||||||
suggestedCollectionName: string;
|
suggestedCollectionName: string;
|
||||||
multipleFolders: boolean;
|
multipleFolders: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Upload(props: Props) {
|
export default function Upload(props: Props) {
|
||||||
const [progressView, setProgressView] = useState(false);
|
const [progressView, setProgressView] = useState(false);
|
||||||
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
|
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
|
||||||
|
@ -45,6 +46,7 @@ export default function Upload(props: Props) {
|
||||||
);
|
);
|
||||||
const [fileCounter, setFileCounter] = useState({ current: 0, total: 0 });
|
const [fileCounter, setFileCounter] = useState({ current: 0, total: 0 });
|
||||||
const [fileProgress, setFileProgress] = useState(new Map<string, number>());
|
const [fileProgress, setFileProgress] = useState(new Map<string, number>());
|
||||||
|
const [uploadResult, setUploadResult]=useState(new Map<string, number>());
|
||||||
const [percentComplete, setPercentComplete] = useState(0);
|
const [percentComplete, setPercentComplete] = useState(0);
|
||||||
const [choiceModalView, setChoiceModalView] = useState(false);
|
const [choiceModalView, setChoiceModalView] = useState(false);
|
||||||
const [fileAnalysisResult, setFileAnalysisResult] = useState<AnalysisResult>(null);
|
const [fileAnalysisResult, setFileAnalysisResult] = useState<AnalysisResult>(null);
|
||||||
|
@ -77,6 +79,7 @@ export default function Upload(props: Props) {
|
||||||
setUploadStage(UPLOAD_STAGES.START);
|
setUploadStage(UPLOAD_STAGES.START);
|
||||||
setFileCounter({ current: 0, total: 0 });
|
setFileCounter({ current: 0, total: 0 });
|
||||||
setFileProgress(new Map<string, number>());
|
setFileProgress(new Map<string, number>());
|
||||||
|
setUploadResult(new Map<string, number>());
|
||||||
setPercentComplete(0);
|
setPercentComplete(0);
|
||||||
setProgressView(true);
|
setProgressView(true);
|
||||||
};
|
};
|
||||||
|
@ -209,6 +212,7 @@ export default function Upload(props: Props) {
|
||||||
setFileCounter,
|
setFileCounter,
|
||||||
setUploadStage,
|
setUploadStage,
|
||||||
setFileProgress,
|
setFileProgress,
|
||||||
|
setUploadResult,
|
||||||
},
|
},
|
||||||
props.setFiles,
|
props.setFiles,
|
||||||
);
|
);
|
||||||
|
@ -258,6 +262,7 @@ export default function Upload(props: Props) {
|
||||||
closeModal={() => setProgressView(false)}
|
closeModal={() => setProgressView(false)}
|
||||||
retryFailed={retryFailed}
|
retryFailed={retryFailed}
|
||||||
fileRejections={props.fileRejections}
|
fileRejections={props.fileRejections}
|
||||||
|
uploadResult={uploadResult}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import React from 'react';
|
import ExpandLess from 'components/icons/ExpandLess';
|
||||||
|
import ExpandMore from 'components/icons/ExpandMore';
|
||||||
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Alert, Button, Modal, ProgressBar,
|
Button, Modal, ProgressBar,
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
import { FileRejection } from 'react-dropzone';
|
import { FileRejection } from 'react-dropzone';
|
||||||
import { UPLOAD_STAGES, FileUploadErrorCode } from 'services/upload/uploadService';
|
import { FileUploadResults, UPLOAD_STAGES } from 'services/upload/uploadService';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
import AlertBanner from './AlertBanner';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fileCounter;
|
fileCounter;
|
||||||
|
@ -15,24 +20,95 @@ interface Props {
|
||||||
fileProgress: Map<string, number>;
|
fileProgress: Map<string, number>;
|
||||||
show;
|
show;
|
||||||
fileRejections:FileRejection[]
|
fileRejections:FileRejection[]
|
||||||
|
uploadResult:Map<string, number>;
|
||||||
}
|
}
|
||||||
interface FileProgressStatuses{
|
interface FileProgresses{
|
||||||
fileName:string;
|
fileName:string;
|
||||||
progress:number;
|
progress:number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Content =styled.div<{collapsed:boolean, sm?:boolean, height?:number}>`
|
||||||
|
overflow: hidden;
|
||||||
|
height:${(props)=> props.collapsed?'0px':props.height+'px'};
|
||||||
|
transition:${(props)=> 'height '+0.001*props.height+'s ease-out'};
|
||||||
|
margin-bottom: 20px;
|
||||||
|
& >p {
|
||||||
|
padding-left:35px;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const FileList =styled.ul`
|
||||||
|
padding-left:50px;
|
||||||
|
margin-top:5px;
|
||||||
|
& > li {
|
||||||
|
padding-left:10px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
color:#ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SectionTitle =styled.div`
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
padding:0 20px;
|
||||||
|
color:#eee;
|
||||||
|
font-size:20px;
|
||||||
|
cursor:pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
interface ResultSectionProps{
|
||||||
|
fileUploadResultMap: Map<FileUploadResults, string[]>;
|
||||||
|
fileUploadResult:FileUploadResults;
|
||||||
|
sectionTitle;
|
||||||
|
sectionInfo;
|
||||||
|
infoHeight:number;
|
||||||
|
}
|
||||||
|
const ResultSection =(props:ResultSectionProps)=>{
|
||||||
|
const [listView, setListView]=useState(false);
|
||||||
|
const fileList=props.fileUploadResultMap?.get(props.fileUploadResult);
|
||||||
|
if (!fileList?.length) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (<>
|
||||||
|
<SectionTitle onClick={()=>setListView(!listView)} > {props.sectionTitle} {listView?<ExpandLess/>:<ExpandMore/>}</SectionTitle>
|
||||||
|
<Content collapsed={!listView} height={fileList.length *33 + props.infoHeight}>
|
||||||
|
<p>{props.sectionInfo}</p>
|
||||||
|
<FileList>
|
||||||
|
{fileList.map((fileName) => (
|
||||||
|
|
||||||
|
<li key={fileName}>
|
||||||
|
{fileName}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</FileList>
|
||||||
|
</Content>
|
||||||
|
</>);
|
||||||
|
};
|
||||||
|
|
||||||
export default function UploadProgress(props: Props) {
|
export default function UploadProgress(props: Props) {
|
||||||
const fileProgressStatuses = [] as FileProgressStatuses[];
|
const fileProgressStatuses = [] as FileProgresses[];
|
||||||
|
const fileUploadResultMap = new Map<FileUploadResults, string[]>();
|
||||||
|
let filesNotUploaded=false;
|
||||||
|
|
||||||
if (props.fileProgress) {
|
if (props.fileProgress) {
|
||||||
for (const [fileName, progress] of props.fileProgress) {
|
for (const [fileName, progress] of props.fileProgress) {
|
||||||
fileProgressStatuses.push({ fileName, progress });
|
fileProgressStatuses.push({ fileName, progress });
|
||||||
}
|
}
|
||||||
for (const { file } of props.fileRejections) {
|
|
||||||
fileProgressStatuses.push({ fileName: file.name, progress: FileUploadErrorCode.UNSUPPORTED });
|
|
||||||
}
|
}
|
||||||
fileProgressStatuses.sort((a, b) => {
|
if (props.uploadResult) {
|
||||||
if (b.progress !== -1 && a.progress === -1) return 1;
|
for (const [fileName, progress] of props.uploadResult) {
|
||||||
});
|
if (!fileUploadResultMap.has(progress)) {
|
||||||
|
fileUploadResultMap.set(progress, []);
|
||||||
}
|
}
|
||||||
|
if (progress<0) {
|
||||||
|
filesNotUploaded=true;
|
||||||
|
}
|
||||||
|
const fileList= fileUploadResultMap.get(progress);
|
||||||
|
fileUploadResultMap.set(progress, [...fileList, fileName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show={props.show}
|
show={props.show}
|
||||||
|
@ -48,7 +124,12 @@ export default function UploadProgress(props: Props) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Modal.Header
|
<Modal.Header
|
||||||
style={{ display: 'flex', justifyContent: 'center', textAlign: 'center', borderBottom: 'none', paddingTop: '30px', paddingBottom: '0px' }}
|
style={{ display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderBottom: 'none',
|
||||||
|
paddingTop: '30px',
|
||||||
|
paddingBottom: '0px' }}
|
||||||
closeButton={props.uploadStage === UPLOAD_STAGES.FINISH}
|
closeButton={props.uploadStage === UPLOAD_STAGES.FINISH}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@ -61,14 +142,7 @@ export default function UploadProgress(props: Props) {
|
||||||
</h4>
|
</h4>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{props.uploadStage===UPLOAD_STAGES.FINISH ? (
|
{(props.uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
|
||||||
fileProgressStatuses.length !== 0 && (
|
|
||||||
<Alert variant="warning">
|
|
||||||
{constants.FAILED_UPLOAD_FILE_LIST}
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
) :
|
|
||||||
(props.uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
|
|
||||||
props.uploadStage === UPLOAD_STAGES.UPLOADING) &&
|
props.uploadStage === UPLOAD_STAGES.UPLOADING) &&
|
||||||
(
|
(
|
||||||
< ProgressBar
|
< ProgressBar
|
||||||
|
@ -76,15 +150,10 @@ export default function UploadProgress(props: Props) {
|
||||||
animated
|
animated
|
||||||
variant="upload-progress-bar"
|
variant="upload-progress-bar"
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
{fileProgressStatuses?.length > 0 && (
|
}
|
||||||
<ul
|
{fileProgressStatuses.length>0 &&
|
||||||
style={{
|
<FileList>
|
||||||
marginTop: '10px',
|
|
||||||
overflow: 'auto',
|
|
||||||
maxHeight: '250px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{fileProgressStatuses.map(({ fileName, progress }) => (
|
{fileProgressStatuses.map(({ fileName, progress }) => (
|
||||||
<li key={fileName} style={{ marginTop: '12px' }}>
|
<li key={fileName} style={{ marginTop: '12px' }}>
|
||||||
{props.uploadStage===UPLOAD_STAGES.FINISH ?
|
{props.uploadStage===UPLOAD_STAGES.FINISH ?
|
||||||
|
@ -92,28 +161,42 @@ export default function UploadProgress(props: Props) {
|
||||||
constants.FILE_UPLOAD_PROGRESS(
|
constants.FILE_UPLOAD_PROGRESS(
|
||||||
fileName,
|
fileName,
|
||||||
progress,
|
progress,
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</FileList>}
|
||||||
|
|
||||||
|
<ResultSection fileUploadResultMap={fileUploadResultMap} fileUploadResult={FileUploadResults.UPLOADED} sectionTitle={constants.SUCCESSFUL_UPLOADS} sectionInfo={constants.SUCCESS_INFO} infoHeight={32}/>
|
||||||
|
|
||||||
|
{props.uploadStage === UPLOAD_STAGES.FINISH && filesNotUploaded && (
|
||||||
|
<AlertBanner variant="warning">
|
||||||
|
{constants.FILE_NOT_UPLOADED_LIST}
|
||||||
|
</AlertBanner>
|
||||||
)}
|
)}
|
||||||
|
<ResultSection fileUploadResultMap={fileUploadResultMap} fileUploadResult={FileUploadResults.BLOCKED} sectionTitle={constants.BLOCKED_UPLOADS} sectionInfo={constants.ETAGS_BLOCKED(DESKTOP_APP_DOWNLOAD_URL)} infoHeight={140}/>
|
||||||
|
<ResultSection fileUploadResultMap={fileUploadResultMap} fileUploadResult={FileUploadResults.FAILED} sectionTitle={constants.FAILED_UPLOADS} sectionInfo={constants.FAILED_INFO} infoHeight={48}/>
|
||||||
|
<ResultSection fileUploadResultMap={fileUploadResultMap} fileUploadResult={FileUploadResults.SKIPPED} sectionTitle={constants.SKIPPED_FILES} sectionInfo={constants.SKIPPED_INFO} infoHeight={32}/>
|
||||||
|
<ResultSection fileUploadResultMap={fileUploadResultMap} fileUploadResult={FileUploadResults.UNSUPPORTED} sectionTitle={constants.UNSUPPORTED_FILES} sectionInfo={constants.UNSUPPORTED_INFO} infoHeight={32}/>
|
||||||
|
|
||||||
{props.uploadStage === UPLOAD_STAGES.FINISH && (
|
{props.uploadStage === UPLOAD_STAGES.FINISH && (
|
||||||
<Modal.Footer style={{ border: 'none' }}>
|
<Modal.Footer style={{ border: 'none' }}>
|
||||||
{props.uploadStage===UPLOAD_STAGES.FINISH && (fileProgressStatuses?.length === 0 ? (
|
{props.uploadStage===UPLOAD_STAGES.FINISH && ((fileUploadResultMap?.get(FileUploadResults.FAILED)?.length>0 || fileUploadResultMap?.get(FileUploadResults.BLOCKED)?.length>0)? (
|
||||||
<Button
|
|
||||||
variant="outline-secondary"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
onClick={props.closeModal}
|
|
||||||
>
|
|
||||||
{constants.CLOSE}
|
|
||||||
</Button>) : (
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline-success"
|
variant="outline-success"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
onClick={props.retryFailed}
|
onClick={props.retryFailed}
|
||||||
>
|
>
|
||||||
{constants.RETRY}
|
{constants.RETRY_FAILED}
|
||||||
</Button>))}
|
</Button>
|
||||||
|
) : ( <Button
|
||||||
|
variant="outline-secondary"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onClick={props.closeModal}
|
||||||
|
>
|
||||||
|
{constants.CLOSE}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
|
|
|
@ -133,6 +133,10 @@ const GlobalStyles = createGlobalStyle`
|
||||||
margin:5% auto;
|
margin:5% auto;
|
||||||
width:90%;
|
width:90%;
|
||||||
}
|
}
|
||||||
|
.modal-body{
|
||||||
|
max-height:80vh;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
.modal-xl{
|
.modal-xl{
|
||||||
max-width:90% !important;
|
max-width:90% !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import { LoadingOverlay } from 'components/LoadingOverlay';
|
||||||
import PhotoFrame from 'components/PhotoFrame';
|
import PhotoFrame from 'components/PhotoFrame';
|
||||||
import { getSelectedFileIds, sortFilesIntoCollections } from 'utils/file';
|
import { getSelectedFileIds, sortFilesIntoCollections } from 'utils/file';
|
||||||
import { addFilesToCollection } from 'utils/collection';
|
import { addFilesToCollection } from 'utils/collection';
|
||||||
import { errorCodes } from 'utils/common/errorUtil';
|
|
||||||
import SearchBar, { DateValue } from 'components/SearchBar';
|
import SearchBar, { DateValue } from 'components/SearchBar';
|
||||||
import { Bbox } from 'services/searchService';
|
import { Bbox } from 'services/searchService';
|
||||||
import SelectedFileOptions from 'components/pages/gallery/SelectedFileOptions';
|
import SelectedFileOptions from 'components/pages/gallery/SelectedFileOptions';
|
||||||
|
@ -55,6 +54,7 @@ import PlanSelector from 'components/pages/gallery/PlanSelector';
|
||||||
import Upload from 'components/pages/gallery/Upload';
|
import Upload from 'components/pages/gallery/Upload';
|
||||||
import Collections from 'components/pages/gallery/Collections';
|
import Collections from 'components/pages/gallery/Collections';
|
||||||
import { AppContext } from 'pages/_app';
|
import { AppContext } from 'pages/_app';
|
||||||
|
import { CustomError, ServerErrorCodes } from 'utils/common/errorUtil';
|
||||||
|
|
||||||
export enum FILE_TYPE {
|
export enum FILE_TYPE {
|
||||||
IMAGE,
|
IMAGE,
|
||||||
|
@ -200,7 +200,7 @@ export default function Gallery() {
|
||||||
try {
|
try {
|
||||||
checkConnectivity();
|
checkConnectivity();
|
||||||
if (!(await isTokenValid())) {
|
if (!(await isTokenValid())) {
|
||||||
throw new Error(errorCodes.ERR_SESSION_EXPIRED);
|
throw new Error(ServerErrorCodes.SESSION_EXPIRED);
|
||||||
}
|
}
|
||||||
!silent && loadingBar.current?.continuousStart();
|
!silent && loadingBar.current?.continuousStart();
|
||||||
await billingService.syncSubscription();
|
await billingService.syncSubscription();
|
||||||
|
@ -209,7 +209,7 @@ export default function Gallery() {
|
||||||
await initDerivativeState(collections, files);
|
await initDerivativeState(collections, files);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
switch (e.message) {
|
switch (e.message) {
|
||||||
case errorCodes.ERR_SESSION_EXPIRED:
|
case ServerErrorCodes.SESSION_EXPIRED:
|
||||||
setBannerMessage(constants.SESSION_EXPIRED_MESSAGE);
|
setBannerMessage(constants.SESSION_EXPIRED_MESSAGE);
|
||||||
setDialogMessage({
|
setDialogMessage({
|
||||||
title: constants.SESSION_EXPIRED,
|
title: constants.SESSION_EXPIRED,
|
||||||
|
@ -219,11 +219,9 @@ export default function Gallery() {
|
||||||
text: constants.LOGIN,
|
text: constants.LOGIN,
|
||||||
action: logoutUser,
|
action: logoutUser,
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
},
|
} });
|
||||||
nonClosable: true,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case errorCodes.ERR_KEY_MISSING:
|
case CustomError.KEY_MISSING:
|
||||||
clearKeys();
|
clearKeys();
|
||||||
router.push('/credentials');
|
router.push('/credentials');
|
||||||
break;
|
break;
|
||||||
|
@ -308,7 +306,7 @@ export default function Gallery() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadingBar.current.complete();
|
loadingBar.current.complete();
|
||||||
switch (e.status?.toString()) {
|
switch (e.status?.toString()) {
|
||||||
case errorCodes.ERR_FORBIDDEN:
|
case ServerErrorCodes.FORBIDDEN:
|
||||||
setDialogMessage({
|
setDialogMessage({
|
||||||
title: constants.ERROR,
|
title: constants.ERROR,
|
||||||
staticBackdrop: true,
|
staticBackdrop: true,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { checkConnectivity, runningInBrowser } from 'utils/common/';
|
||||||
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { convertToHumanReadable } from 'utils/billingUtil';
|
import { convertToHumanReadable } from 'utils/billingUtil';
|
||||||
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||||
import { SUBSCRIPTION_VERIFICATION_ERROR } from 'utils/common/errorUtil';
|
import { CustomError } from 'utils/common/errorUtil';
|
||||||
import HTTPService from './HTTPService';
|
import HTTPService from './HTTPService';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ class billingService {
|
||||||
try {
|
try {
|
||||||
await this.verifySubscription();
|
await this.verifySubscription();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(SUBSCRIPTION_VERIFICATION_ERROR);
|
throw new Error(CustomError.SUBSCRIPTION_VERIFICATION_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { logError } from 'utils/sentry';
|
||||||
import { CHUNKS_COMBINED_FOR_UPLOAD, MultipartUploadURLs, RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, UploadFile } from './uploadService';
|
import { CHUNKS_COMBINED_FOR_UPLOAD, MultipartUploadURLs, RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, UploadFile } from './uploadService';
|
||||||
import * as convert from 'xml-js';
|
import * as convert from 'xml-js';
|
||||||
import { File } from '../fileService';
|
import { File } from '../fileService';
|
||||||
|
import { CustomError } from 'utils/common/errorUtil';
|
||||||
|
|
||||||
const ENDPOINT = getEndpoint();
|
const ENDPOINT = getEndpoint();
|
||||||
const MAX_URL_REQUESTS = 50;
|
const MAX_URL_REQUESTS = 50;
|
||||||
|
@ -153,7 +154,7 @@ class NetworkClient {
|
||||||
trackUploadProgress(filename, percentPerPart, index),
|
trackUploadProgress(filename, percentPerPart, index),
|
||||||
);
|
);
|
||||||
if (!resp?.headers?.etag) {
|
if (!resp?.headers?.etag) {
|
||||||
const err=Error('no header/etag present in response body');
|
const err=Error(CustomError.ETAG_MISSING);
|
||||||
logError(err);
|
logError(err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { FILE_TYPE } from 'pages/gallery';
|
import { FILE_TYPE } from 'pages/gallery';
|
||||||
import { THUMBNAIL_GENERATION_FAILED } from 'utils/common/errorUtil';
|
import { CustomError } from 'utils/common/errorUtil';
|
||||||
import { fileIsHEIC, convertHEIC2JPEG } from 'utils/file';
|
import { fileIsHEIC, convertHEIC2JPEG } from 'utils/file';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { getUint8ArrayView } from './readFileService';
|
import { getUint8ArrayView } from './readFileService';
|
||||||
|
@ -81,7 +81,7 @@ export async function generateImageThumbnail(file:globalThis.File) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
logError(e);
|
logError(e);
|
||||||
reject(Error(`${THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
timeout = setTimeout(
|
timeout = setTimeout(
|
||||||
|
@ -128,7 +128,7 @@ export async function generateVideoThumbnail(file:globalThis.File) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
logError(e);
|
logError(e);
|
||||||
reject(Error(`${THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
video.preload = 'metadata';
|
video.preload = 'metadata';
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { File, fileAttribute } from '../fileService';
|
||||||
import { Collection } from '../collectionService';
|
import { Collection } from '../collectionService';
|
||||||
import { FILE_TYPE, SetFiles } from 'pages/gallery';
|
import { FILE_TYPE, SetFiles } from 'pages/gallery';
|
||||||
import {
|
import {
|
||||||
|
CustomError,
|
||||||
handleError,
|
handleError,
|
||||||
parseError,
|
parseError,
|
||||||
} from 'utils/common/errorUtil';
|
} from 'utils/common/errorUtil';
|
||||||
|
@ -29,10 +30,12 @@ const TwoSecondInMillSeconds = 2000;
|
||||||
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
|
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
|
||||||
export const CHUNKS_COMBINED_FOR_UPLOAD = 5;
|
export const CHUNKS_COMBINED_FOR_UPLOAD = 5;
|
||||||
|
|
||||||
export enum FileUploadErrorCode {
|
export enum FileUploadResults {
|
||||||
FAILED = -1,
|
FAILED = -1,
|
||||||
SKIPPED = -2,
|
SKIPPED = -2,
|
||||||
UNSUPPORTED = -3,
|
UNSUPPORTED = -3,
|
||||||
|
BLOCKED=-4,
|
||||||
|
UPLOADED = 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileWithCollection {
|
export interface FileWithCollection {
|
||||||
|
@ -114,6 +117,7 @@ class UploadService {
|
||||||
private filesCompleted: number;
|
private filesCompleted: number;
|
||||||
private totalFileCount: number;
|
private totalFileCount: number;
|
||||||
private fileProgress: Map<string, number>;
|
private fileProgress: Map<string, number>;
|
||||||
|
private uploadResult:Map<string, number>;
|
||||||
private metadataMap: Map<string, ParsedMetaDataJSON>;
|
private metadataMap: Map<string, ParsedMetaDataJSON>;
|
||||||
private filesToBeUploaded: FileWithCollection[];
|
private filesToBeUploaded: FileWithCollection[];
|
||||||
private progressBarProps;
|
private progressBarProps;
|
||||||
|
@ -132,6 +136,7 @@ class UploadService {
|
||||||
|
|
||||||
this.filesCompleted = 0;
|
this.filesCompleted = 0;
|
||||||
this.fileProgress = new Map<string, number>();
|
this.fileProgress = new Map<string, number>();
|
||||||
|
this.uploadResult = new Map<string, number>();
|
||||||
this.failedFiles = [];
|
this.failedFiles = [];
|
||||||
this.metadataMap = new Map<string, ParsedMetaDataJSON>();
|
this.metadataMap = new Map<string, ParsedMetaDataJSON>();
|
||||||
this.progressBarProps = progressBarProps;
|
this.progressBarProps = progressBarProps;
|
||||||
|
@ -233,11 +238,9 @@ class UploadService {
|
||||||
|
|
||||||
if (this.fileAlreadyInCollection(file, collection)) {
|
if (this.fileAlreadyInCollection(file, collection)) {
|
||||||
// set progress to -2 indicating that file upload was skipped
|
// set progress to -2 indicating that file upload was skipped
|
||||||
this.fileProgress.set(rawFile.name, FileUploadErrorCode.SKIPPED);
|
this.fileProgress.set(rawFile.name, FileUploadResults.SKIPPED);
|
||||||
this.updateProgressBarUI();
|
this.updateProgressBarUI();
|
||||||
await sleep(TwoSecondInMillSeconds);
|
await sleep(TwoSecondInMillSeconds);
|
||||||
// remove completed files for file progress list
|
|
||||||
this.fileProgress.delete(rawFile.name);
|
|
||||||
} else {
|
} else {
|
||||||
encryptedFile = await this.encryptFile(worker, file, collection.key);
|
encryptedFile = await this.encryptFile(worker, file, collection.key);
|
||||||
|
|
||||||
|
@ -262,19 +265,26 @@ class UploadService {
|
||||||
|
|
||||||
uploadFile = null;
|
uploadFile = null;
|
||||||
|
|
||||||
this.fileProgress.delete(rawFile.name);
|
this.fileProgress.set(rawFile.name, FileUploadResults.UPLOADED);
|
||||||
|
|
||||||
this.filesCompleted++;
|
this.filesCompleted++;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e, 'file upload failed');
|
logError(e, 'file upload failed');
|
||||||
this.failedFiles.push(fileWithCollection);
|
logError(e, 'file upload failed');
|
||||||
// set progress to -1 indicating that file upload failed but keep it to show in the file-upload list progress
|
|
||||||
this.fileProgress.set(rawFile.name, FileUploadErrorCode.FAILED);
|
|
||||||
handleError(e);
|
handleError(e);
|
||||||
|
this.failedFiles.push(fileWithCollection);
|
||||||
|
if (e.message ===CustomError.ETAG_MISSING) {
|
||||||
|
this.fileProgress.set(rawFile.name, FileUploadResults.BLOCKED);
|
||||||
|
} else {
|
||||||
|
this.fileProgress.set(rawFile.name, FileUploadResults.FAILED);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
file=null;
|
file=null;
|
||||||
encryptedFile=null;
|
encryptedFile=null;
|
||||||
}
|
}
|
||||||
|
this.uploadResult.set(rawFile.name, this.fileProgress.get(rawFile.name));
|
||||||
|
this.fileProgress.delete(rawFile.name);
|
||||||
this.updateProgressBarUI();
|
this.updateProgressBarUI();
|
||||||
|
|
||||||
if (this.filesToBeUploaded.length > 0) {
|
if (this.filesToBeUploaded.length > 0) {
|
||||||
|
@ -290,7 +300,7 @@ class UploadService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateProgressBarUI() {
|
private updateProgressBarUI() {
|
||||||
const { setPercentComplete, setFileCounter, setFileProgress } =
|
const { setPercentComplete, setFileCounter, setFileProgress, setUploadResult } =
|
||||||
this.progressBarProps;
|
this.progressBarProps;
|
||||||
setFileCounter({
|
setFileCounter({
|
||||||
finished: this.filesCompleted,
|
finished: this.filesCompleted,
|
||||||
|
@ -309,6 +319,7 @@ class UploadService {
|
||||||
}
|
}
|
||||||
setPercentComplete(percentComplete);
|
setPercentComplete(percentComplete);
|
||||||
setFileProgress(this.fileProgress);
|
setFileProgress(this.fileProgress);
|
||||||
|
setUploadResult(this.uploadResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFile(reader: FileReader, receivedFile: globalThis.File, metadataMap:Map<string, ParsedMetaDataJSON>) {
|
async readFile(reader: FileReader, receivedFile: globalThis.File, metadataMap:Map<string, ParsedMetaDataJSON>) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { NextRouter } from 'next/router';
|
||||||
import { SetDialogMessage } from 'components/MessageDialog';
|
import { SetDialogMessage } from 'components/MessageDialog';
|
||||||
import { SetLoading } from 'pages/gallery';
|
import { SetLoading } from 'pages/gallery';
|
||||||
import { getData, LS_KEYS } from './storage/localStorage';
|
import { getData, LS_KEYS } from './storage/localStorage';
|
||||||
import { SUBSCRIPTION_VERIFICATION_ERROR } from './common/errorUtil';
|
import { CustomError } from './common/errorUtil';
|
||||||
|
|
||||||
const STRIPE = 'stripe';
|
const STRIPE = 'stripe';
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ export async function updateSubscription(
|
||||||
close: { text: constants.CANCEL },
|
close: { text: constants.CANCEL },
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case SUBSCRIPTION_VERIFICATION_ERROR:
|
case CustomError.SUBSCRIPTION_VERIFICATION_ERROR:
|
||||||
setDialogMessage({
|
setDialogMessage({
|
||||||
title: constants.ERROR,
|
title: constants.ERROR,
|
||||||
content: constants.SUBSCRIPTION_VERIFICATION_FAILED,
|
content: constants.SUBSCRIPTION_VERIFICATION_FAILED,
|
||||||
|
@ -239,7 +239,7 @@ export async function checkSubscriptionPurchase(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setDialogMessage({
|
setDialogMessage({
|
||||||
title: constants.ERROR,
|
title: constants.ERROR,
|
||||||
content: SUBSCRIPTION_VERIFICATION_ERROR,
|
content: CustomError.SUBSCRIPTION_VERIFICATION_ERROR,
|
||||||
close: {},
|
close: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
export const errorCodes = {
|
export const ServerErrorCodes = {
|
||||||
ERR_STORAGE_LIMIT_EXCEEDED: '426',
|
SESSION_EXPIRED: '401',
|
||||||
ERR_NO_ACTIVE_SUBSCRIPTION: '402',
|
NO_ACTIVE_SUBSCRIPTION: '402',
|
||||||
ERR_NO_INTERNET_CONNECTION: '1',
|
FORBIDDEN: '403',
|
||||||
ERR_SESSION_EXPIRED: '401',
|
STORAGE_LIMIT_EXCEEDED: '426',
|
||||||
ERR_KEY_MISSING: '2',
|
|
||||||
ERR_FORBIDDEN: '403',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const SUBSCRIPTION_VERIFICATION_ERROR = 'Subscription verification failed';
|
export const CustomError ={
|
||||||
|
SUBSCRIPTION_VERIFICATION_ERROR: 'Subscription verification failed',
|
||||||
|
THUMBNAIL_GENERATION_FAILED: 'thumbnail generation failed',
|
||||||
|
VIDEO_PLAYBACK_FAILED: 'video playback failed',
|
||||||
|
ETAG_MISSING: 'no header/etag present in response body',
|
||||||
|
KEY_MISSING: 'encrypted key missing from localStorage',
|
||||||
|
};
|
||||||
|
|
||||||
export const THUMBNAIL_GENERATION_FAILED = 'thumbnail generation failed';
|
|
||||||
export const VIDEO_PLAYBACK_FAILED = 'video playback failed';
|
|
||||||
|
|
||||||
export function parseError(error) {
|
export function parseError(error) {
|
||||||
let errorMessage = null;
|
let parsedMessage = null;
|
||||||
if (error?.status) {
|
if (error?.status) {
|
||||||
const errorCode = error.status.toString();
|
const errorCode = error.status.toString();
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case errorCodes.ERR_NO_ACTIVE_SUBSCRIPTION:
|
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
|
||||||
errorMessage = constants.SUBSCRIPTION_EXPIRED;
|
parsedMessage = constants.SUBSCRIPTION_EXPIRED;
|
||||||
break;
|
break;
|
||||||
case errorCodes.ERR_STORAGE_LIMIT_EXCEEDED:
|
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
|
||||||
errorMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
parsedMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
||||||
break;
|
break;
|
||||||
case errorCodes.ERR_NO_INTERNET_CONNECTION:
|
case ServerErrorCodes.SESSION_EXPIRED:
|
||||||
errorMessage = constants.NO_INTERNET_CONNECTION;
|
parsedMessage = constants.SESSION_EXPIRED_MESSAGE;
|
||||||
break;
|
|
||||||
case errorCodes.ERR_SESSION_EXPIRED:
|
|
||||||
errorMessage = constants.SESSION_EXPIRED_MESSAGE;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (errorMessage) {
|
if (parsedMessage) {
|
||||||
return { parsedError: new Error(errorMessage), parsed: true };
|
return { parsedError: new Error(parsedMessage), parsed: true };
|
||||||
} else {
|
} else {
|
||||||
return ({
|
return ({
|
||||||
parsedError: new Error(`${constants.UNKNOWN_ERROR} ${error}`), parsed: false,
|
parsedError: new Error(`${constants.UNKNOWN_ERROR} ${error}`), parsed: false,
|
||||||
|
@ -48,6 +47,6 @@ export function handleError(error) {
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
throw parsedError;
|
throw parsedError;
|
||||||
} else {
|
} else {
|
||||||
// shallow error don't break the caller flow
|
// swallow error don't break the caller flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
const DESKTOP_APP_DOWNLOAD_URL = 'https://github.com/ente-io/bhari-frame/releases/';
|
export const DESKTOP_APP_DOWNLOAD_URL = 'https://github.com/ente-io/bhari-frame/releases/latest';
|
||||||
|
|
||||||
const retrySleepTime = [2000, 5000, 10000];
|
const retrySleepTime = [2000, 5000, 10000];
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { B64EncryptionResult } from 'services/upload/uploadService';
|
||||||
import CryptoWorker from 'utils/crypto';
|
import CryptoWorker from 'utils/crypto';
|
||||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||||
import { errorCodes } from './errorUtil';
|
import { CustomError } from './errorUtil';
|
||||||
|
|
||||||
export const getActualKey = async () => {
|
export const getActualKey = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -18,7 +18,7 @@ export const getActualKey = async () => {
|
||||||
);
|
);
|
||||||
return key;
|
return key;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(errorCodes.ERR_KEY_MISSING);
|
throw new Error(CustomError.KEY_MISSING);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -89,12 +89,11 @@ const englishConstants = {
|
||||||
0: 'preparing to upload',
|
0: 'preparing to upload',
|
||||||
1: 'reading google metadata files',
|
1: 'reading google metadata files',
|
||||||
2: (fileCounter) => `${fileCounter.finished} / ${fileCounter.total} files backed up`,
|
2: (fileCounter) => `${fileCounter.finished} / ${fileCounter.total} files backed up`,
|
||||||
3: 'backup complete!',
|
3: 'backup complete',
|
||||||
},
|
},
|
||||||
UPLOADING_FILES: 'file upload',
|
UPLOADING_FILES: 'file upload',
|
||||||
FAILED_UPLOAD_FILE_LIST: 'upload failed for following files',
|
FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded',
|
||||||
UNSUPPORTED_FILE_LIST: 'unsupported files',
|
FILE_UPLOAD_PROGRESS: (name:string, progress:number) => (
|
||||||
FILE_UPLOAD_PROGRESS: (name, progress) => (
|
|
||||||
<div id={name}>
|
<div id={name}>
|
||||||
<strong>{name}</strong>
|
<strong>{name}</strong>
|
||||||
{' - '}
|
{' - '}
|
||||||
|
@ -105,7 +104,7 @@ const englishConstants = {
|
||||||
case -2:
|
case -2:
|
||||||
return 'already uploaded, skipping...';
|
return 'already uploaded, skipping...';
|
||||||
case -3:
|
case -3:
|
||||||
return 'unsupported file format, ignored';
|
return ',unsupported file format, skipping....';
|
||||||
default:
|
default:
|
||||||
return `${progress}%`;
|
return `${progress}%`;
|
||||||
}
|
}
|
||||||
|
@ -452,7 +451,30 @@ const englishConstants = {
|
||||||
SEND_OTT: 'send otp',
|
SEND_OTT: 'send otp',
|
||||||
EMAIl_ALREADY_OWNED: 'email already taken',
|
EMAIl_ALREADY_OWNED: 'email already taken',
|
||||||
EMAIL_UDPATE_SUCCESSFUL: 'your email has been udpated successfully',
|
EMAIL_UDPATE_SUCCESSFUL: 'your email has been udpated successfully',
|
||||||
|
UPLOAD_FAILED: 'upload failed',
|
||||||
|
ETAGS_BLOCKED: (url:string)=>(<>
|
||||||
|
<p> we were unable to upload the following files because of your browser configuration.</p>
|
||||||
|
<p> please disable any addons that might be preventing ente from using <code>eTags</code> to upload large files, or use our
|
||||||
|
{' '}
|
||||||
|
<a
|
||||||
|
href={url} style={{ color: '#2dc262', textDecoration: 'underline' }}
|
||||||
|
target="_blank" rel="noreferrer">
|
||||||
|
desktop app
|
||||||
|
</a>
|
||||||
|
{' '}
|
||||||
|
for a more reliable import experience.</p>
|
||||||
|
</>),
|
||||||
|
|
||||||
|
RETRY_FAILED: 'retry failed uploads',
|
||||||
|
FAILED_UPLOADS: 'failed uploads ',
|
||||||
|
SKIPPED_FILES: 'duplicate files',
|
||||||
|
UNSUPPORTED_FILES: 'unsupported files',
|
||||||
|
SUCCESSFUL_UPLOADS: 'successful uploads',
|
||||||
|
FAILED_INFO: ' unable to upload these files because of network issue, you can retry upload these files',
|
||||||
|
SKIPPED_INFO: 'these files already existed in the album',
|
||||||
|
UNSUPPORTED_INFO: 'these files are currently not supported by ente',
|
||||||
|
SUCCESS_INFO: 'successfully backed-up memories',
|
||||||
|
BLOCKED_UPLOADS: 'blocked uploads',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default englishConstants;
|
export default englishConstants;
|
||||||
|
|
Loading…
Reference in a new issue