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 styled from 'styled-components';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import englishConstants from 'utils/strings/englishConstants';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
|
||||
interface formValues {
|
||||
email: string;
|
||||
|
@ -72,6 +72,7 @@ function ChangeEmailForm(props:Props) {
|
|||
try {
|
||||
setLoading(true);
|
||||
await changeEmail(email, ott);
|
||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
|
||||
appContext.setDisappearingFlashMessage({ message: constants.EMAIL_UDPATE_SUCCESSFUL, severity: 'success' });
|
||||
router.push('/gallery');
|
||||
} catch (e) {
|
||||
|
@ -125,7 +126,7 @@ function ChangeEmailForm(props:Props) {
|
|||
</Col>
|
||||
<Col xs ="4" >
|
||||
<Button variant="link" onClick={()=>setShowOttInputVisibility(false)}>
|
||||
{englishConstants.CHANGE}
|
||||
{constants.CHANGE}
|
||||
</Button>
|
||||
</Col>
|
||||
</EmailRow>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { VariableSizeList as List } from 'react-window';
|
|||
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
|
||||
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
|
||||
import { SetDialogMessage } from './MessageDialog';
|
||||
import { VIDEO_PLAYBACK_FAILED } from 'utils/common/errorUtil';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
import {
|
||||
GAP_BTW_TILES, DATE_CONTAINER_HEIGHT, IMAGE_CONTAINER_MAX_HEIGHT,
|
||||
IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS, SPACE_BTW_DATES,
|
||||
|
@ -306,7 +306,7 @@ const PhotoFrame = ({
|
|||
const t = setTimeout(
|
||||
() => {
|
||||
reject(
|
||||
Error(`${VIDEO_PLAYBACK_FAILED} err: wait time exceeded`),
|
||||
Error(`${CustomError.VIDEO_PLAYBACK_FAILED} err: wait time exceeded`),
|
||||
);
|
||||
},
|
||||
WAIT_FOR_VIDEO_PLAYBACK,
|
||||
|
|
|
@ -60,6 +60,8 @@ export default function Sidebar(props: Props) {
|
|||
setUser({ ...user, email: userDetails.email });
|
||||
SetUsage(convertToHumanReadable(userDetails.usage));
|
||||
setSubscription(userDetails.subscription);
|
||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email: userDetails.email });
|
||||
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
|
||||
};
|
||||
main();
|
||||
}, [isOpen]);
|
||||
|
|
|
@ -19,16 +19,18 @@ interface Props {
|
|||
}
|
||||
|
||||
const Group = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-left: -36px;
|
||||
display: flex;
|
||||
width: 46px;
|
||||
height: 34px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
border-radius: 5px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
|
@ -77,10 +79,10 @@ export default function SingleInputForm(props: Props) {
|
|||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</Button>
|
||||
}
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.passphrase}
|
||||
</Form.Control.Feedback>
|
||||
</Group>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.passphrase}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<SubmitButton
|
||||
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 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 (
|
||||
<Alert
|
||||
variant="danger"
|
||||
variant={props.variant??'danger'}
|
||||
style={{
|
||||
display: bannerMessage ? 'block' : 'none',
|
||||
display: props.bannerMessage || props.children ? 'block' : 'none',
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ enum ButtonVariant {
|
|||
success = 'success',
|
||||
danger = 'danger',
|
||||
secondary = 'secondary',
|
||||
warning='warning'
|
||||
}
|
||||
type Props = React.PropsWithChildren<{
|
||||
onClick: any;
|
||||
|
@ -11,23 +12,25 @@ type Props = React.PropsWithChildren<{
|
|||
style?: any;
|
||||
}>;
|
||||
|
||||
export default function LinkButton(props: Props) {
|
||||
function getButtonColor(variant: string) {
|
||||
switch (variant) {
|
||||
case ButtonVariant.success:
|
||||
return '#2dc262';
|
||||
case ButtonVariant.danger:
|
||||
return '#c93f3f';
|
||||
case ButtonVariant.secondary:
|
||||
return '#858585';
|
||||
default:
|
||||
return '#d1d1d1';
|
||||
}
|
||||
export function getVariantColor(variant: string) {
|
||||
switch (variant) {
|
||||
case ButtonVariant.success:
|
||||
return '#2dc262';
|
||||
case ButtonVariant.danger:
|
||||
return '#c93f3f';
|
||||
case ButtonVariant.secondary:
|
||||
return '#858585';
|
||||
case ButtonVariant.warning:
|
||||
return '#D7BB63';
|
||||
default:
|
||||
return '#d1d1d1';
|
||||
}
|
||||
}
|
||||
export default function LinkButton(props: Props) {
|
||||
return (
|
||||
<h5
|
||||
style={{
|
||||
color: getButtonColor(props.variant),
|
||||
color: getVariantColor(props.variant),
|
||||
cursor: 'pointer',
|
||||
marginBottom: 0,
|
||||
...props.style,
|
||||
|
|
|
@ -7,7 +7,7 @@ import DeleteIcon from 'components/icons/DeleteIcon';
|
|||
import CrossIcon from 'components/icons/CrossIcon';
|
||||
import AddIcon from 'components/icons/AddIcon';
|
||||
import { IconButton } from 'components/Container';
|
||||
import constants from 'utils/strings/englishConstants';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
interface Props {
|
||||
addToCollectionHelper: (collectionName, collection) => void;
|
||||
|
|
|
@ -38,6 +38,7 @@ interface AnalysisResult {
|
|||
suggestedCollectionName: string;
|
||||
multipleFolders: boolean;
|
||||
}
|
||||
|
||||
export default function Upload(props: Props) {
|
||||
const [progressView, setProgressView] = useState(false);
|
||||
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 [fileProgress, setFileProgress] = useState(new Map<string, number>());
|
||||
const [uploadResult, setUploadResult]=useState(new Map<string, number>());
|
||||
const [percentComplete, setPercentComplete] = useState(0);
|
||||
const [choiceModalView, setChoiceModalView] = useState(false);
|
||||
const [fileAnalysisResult, setFileAnalysisResult] = useState<AnalysisResult>(null);
|
||||
|
@ -77,6 +79,7 @@ export default function Upload(props: Props) {
|
|||
setUploadStage(UPLOAD_STAGES.START);
|
||||
setFileCounter({ current: 0, total: 0 });
|
||||
setFileProgress(new Map<string, number>());
|
||||
setUploadResult(new Map<string, number>());
|
||||
setPercentComplete(0);
|
||||
setProgressView(true);
|
||||
};
|
||||
|
@ -209,6 +212,7 @@ export default function Upload(props: Props) {
|
|||
setFileCounter,
|
||||
setUploadStage,
|
||||
setFileProgress,
|
||||
setUploadResult,
|
||||
},
|
||||
props.setFiles,
|
||||
);
|
||||
|
@ -258,6 +262,7 @@ export default function Upload(props: Props) {
|
|||
closeModal={() => setProgressView(false)}
|
||||
retryFailed={retryFailed}
|
||||
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 {
|
||||
Alert, Button, Modal, ProgressBar,
|
||||
Button, Modal, ProgressBar,
|
||||
} from 'react-bootstrap';
|
||||
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 AlertBanner from './AlertBanner';
|
||||
|
||||
interface Props {
|
||||
fileCounter;
|
||||
|
@ -15,24 +20,95 @@ interface Props {
|
|||
fileProgress: Map<string, number>;
|
||||
show;
|
||||
fileRejections:FileRejection[]
|
||||
uploadResult:Map<string, number>;
|
||||
}
|
||||
interface FileProgressStatuses{
|
||||
interface FileProgresses{
|
||||
fileName:string;
|
||||
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) {
|
||||
const fileProgressStatuses = [] as FileProgressStatuses[];
|
||||
const fileProgressStatuses = [] as FileProgresses[];
|
||||
const fileUploadResultMap = new Map<FileUploadResults, string[]>();
|
||||
let filesNotUploaded=false;
|
||||
|
||||
if (props.fileProgress) {
|
||||
for (const [fileName, progress] of props.fileProgress) {
|
||||
fileProgressStatuses.push({ fileName, progress });
|
||||
}
|
||||
for (const { file } of props.fileRejections) {
|
||||
fileProgressStatuses.push({ fileName: file.name, progress: FileUploadErrorCode.UNSUPPORTED });
|
||||
}
|
||||
fileProgressStatuses.sort((a, b) => {
|
||||
if (b.progress !== -1 && a.progress === -1) return 1;
|
||||
});
|
||||
}
|
||||
if (props.uploadResult) {
|
||||
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 (
|
||||
<Modal
|
||||
show={props.show}
|
||||
|
@ -48,7 +124,12 @@ export default function UploadProgress(props: Props) {
|
|||
}
|
||||
>
|
||||
<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}
|
||||
>
|
||||
|
||||
|
@ -61,14 +142,7 @@ export default function UploadProgress(props: Props) {
|
|||
</h4>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{props.uploadStage===UPLOAD_STAGES.FINISH ? (
|
||||
fileProgressStatuses.length !== 0 && (
|
||||
<Alert variant="warning">
|
||||
{constants.FAILED_UPLOAD_FILE_LIST}
|
||||
</Alert>
|
||||
)
|
||||
) :
|
||||
(props.uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
|
||||
{(props.uploadStage === UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES ||
|
||||
props.uploadStage === UPLOAD_STAGES.UPLOADING) &&
|
||||
(
|
||||
< ProgressBar
|
||||
|
@ -76,44 +150,53 @@ export default function UploadProgress(props: Props) {
|
|||
animated
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
)}
|
||||
{fileProgressStatuses?.length > 0 && (
|
||||
<ul
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
overflow: 'auto',
|
||||
maxHeight: '250px',
|
||||
}}
|
||||
>
|
||||
{fileProgressStatuses.map(({ fileName, progress }) => (
|
||||
<li key={fileName} style={{ marginTop: '12px' }}>
|
||||
{props.uploadStage===UPLOAD_STAGES.FINISH ?
|
||||
fileName :
|
||||
constants.FILE_UPLOAD_PROGRESS(
|
||||
fileName,
|
||||
progress,
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
{fileProgressStatuses.length>0 &&
|
||||
<FileList>
|
||||
{fileProgressStatuses.map(({ fileName, progress }) => (
|
||||
<li key={fileName} style={{ marginTop: '12px' }}>
|
||||
{props.uploadStage===UPLOAD_STAGES.FINISH ?
|
||||
fileName :
|
||||
constants.FILE_UPLOAD_PROGRESS(
|
||||
fileName,
|
||||
progress,
|
||||
)
|
||||
}
|
||||
</li>
|
||||
))}
|
||||
</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 && (
|
||||
<Modal.Footer style={{ border: 'none' }}>
|
||||
{props.uploadStage===UPLOAD_STAGES.FINISH && (fileProgressStatuses?.length === 0 ? (
|
||||
<Button
|
||||
variant="outline-secondary"
|
||||
style={{ width: '100%' }}
|
||||
onClick={props.closeModal}
|
||||
>
|
||||
{constants.CLOSE}
|
||||
</Button>) : (
|
||||
{props.uploadStage===UPLOAD_STAGES.FINISH && ((fileUploadResultMap?.get(FileUploadResults.FAILED)?.length>0 || fileUploadResultMap?.get(FileUploadResults.BLOCKED)?.length>0)? (
|
||||
<Button
|
||||
variant="outline-success"
|
||||
style={{ width: '100%' }}
|
||||
onClick={props.retryFailed}
|
||||
>
|
||||
{constants.RETRY}
|
||||
</Button>))}
|
||||
{constants.RETRY_FAILED}
|
||||
</Button>
|
||||
) : ( <Button
|
||||
variant="outline-secondary"
|
||||
style={{ width: '100%' }}
|
||||
onClick={props.closeModal}
|
||||
>
|
||||
{constants.CLOSE}
|
||||
</Button>
|
||||
))}
|
||||
</Modal.Footer>
|
||||
)}
|
||||
</Modal.Body>
|
||||
|
|
|
@ -133,6 +133,10 @@ const GlobalStyles = createGlobalStyle`
|
|||
margin:5% auto;
|
||||
width:90%;
|
||||
}
|
||||
.modal-body{
|
||||
max-height:80vh;
|
||||
overflow:auto;
|
||||
}
|
||||
.modal-xl{
|
||||
max-width:90% !important;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import { LoadingOverlay } from 'components/LoadingOverlay';
|
|||
import PhotoFrame from 'components/PhotoFrame';
|
||||
import { getSelectedFileIds, sortFilesIntoCollections } from 'utils/file';
|
||||
import { addFilesToCollection } from 'utils/collection';
|
||||
import { errorCodes } from 'utils/common/errorUtil';
|
||||
import SearchBar, { DateValue } from 'components/SearchBar';
|
||||
import { Bbox } from 'services/searchService';
|
||||
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 Collections from 'components/pages/gallery/Collections';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { CustomError, ServerErrorCodes } from 'utils/common/errorUtil';
|
||||
|
||||
export enum FILE_TYPE {
|
||||
IMAGE,
|
||||
|
@ -200,7 +200,7 @@ export default function Gallery() {
|
|||
try {
|
||||
checkConnectivity();
|
||||
if (!(await isTokenValid())) {
|
||||
throw new Error(errorCodes.ERR_SESSION_EXPIRED);
|
||||
throw new Error(ServerErrorCodes.SESSION_EXPIRED);
|
||||
}
|
||||
!silent && loadingBar.current?.continuousStart();
|
||||
await billingService.syncSubscription();
|
||||
|
@ -209,7 +209,7 @@ export default function Gallery() {
|
|||
await initDerivativeState(collections, files);
|
||||
} catch (e) {
|
||||
switch (e.message) {
|
||||
case errorCodes.ERR_SESSION_EXPIRED:
|
||||
case ServerErrorCodes.SESSION_EXPIRED:
|
||||
setBannerMessage(constants.SESSION_EXPIRED_MESSAGE);
|
||||
setDialogMessage({
|
||||
title: constants.SESSION_EXPIRED,
|
||||
|
@ -219,11 +219,9 @@ export default function Gallery() {
|
|||
text: constants.LOGIN,
|
||||
action: logoutUser,
|
||||
variant: 'success',
|
||||
},
|
||||
nonClosable: true,
|
||||
});
|
||||
} });
|
||||
break;
|
||||
case errorCodes.ERR_KEY_MISSING:
|
||||
case CustomError.KEY_MISSING:
|
||||
clearKeys();
|
||||
router.push('/credentials');
|
||||
break;
|
||||
|
@ -308,7 +306,7 @@ export default function Gallery() {
|
|||
} catch (e) {
|
||||
loadingBar.current.complete();
|
||||
switch (e.status?.toString()) {
|
||||
case errorCodes.ERR_FORBIDDEN:
|
||||
case ServerErrorCodes.FORBIDDEN:
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
staticBackdrop: true,
|
||||
|
|
|
@ -4,7 +4,7 @@ import { checkConnectivity, runningInBrowser } from 'utils/common/';
|
|||
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { convertToHumanReadable } from 'utils/billingUtil';
|
||||
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 { logError } from 'utils/sentry';
|
||||
|
||||
|
@ -142,7 +142,7 @@ class billingService {
|
|||
try {
|
||||
await this.verifySubscription();
|
||||
} 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 * as convert from 'xml-js';
|
||||
import { File } from '../fileService';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
const MAX_URL_REQUESTS = 50;
|
||||
|
@ -153,7 +154,7 @@ class NetworkClient {
|
|||
trackUploadProgress(filename, percentPerPart, index),
|
||||
);
|
||||
if (!resp?.headers?.etag) {
|
||||
const err=Error('no header/etag present in response body');
|
||||
const err=Error(CustomError.ETAG_MISSING);
|
||||
logError(err);
|
||||
throw err;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { logError } from 'utils/sentry';
|
||||
import { getUint8ArrayView } from './readFileService';
|
||||
|
@ -81,7 +81,7 @@ export async function generateImageThumbnail(file:globalThis.File) {
|
|||
} catch (e) {
|
||||
reject(e);
|
||||
logError(e);
|
||||
reject(Error(`${THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||
}
|
||||
};
|
||||
timeout = setTimeout(
|
||||
|
@ -128,7 +128,7 @@ export async function generateVideoThumbnail(file:globalThis.File) {
|
|||
} catch (e) {
|
||||
reject(e);
|
||||
logError(e);
|
||||
reject(Error(`${THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
||||
}
|
||||
});
|
||||
video.preload = 'metadata';
|
||||
|
|
|
@ -2,6 +2,7 @@ import { File, fileAttribute } from '../fileService';
|
|||
import { Collection } from '../collectionService';
|
||||
import { FILE_TYPE, SetFiles } from 'pages/gallery';
|
||||
import {
|
||||
CustomError,
|
||||
handleError,
|
||||
parseError,
|
||||
} from 'utils/common/errorUtil';
|
||||
|
@ -29,10 +30,12 @@ const TwoSecondInMillSeconds = 2000;
|
|||
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
|
||||
export const CHUNKS_COMBINED_FOR_UPLOAD = 5;
|
||||
|
||||
export enum FileUploadErrorCode {
|
||||
export enum FileUploadResults {
|
||||
FAILED = -1,
|
||||
SKIPPED = -2,
|
||||
UNSUPPORTED = -3,
|
||||
BLOCKED=-4,
|
||||
UPLOADED = 100,
|
||||
}
|
||||
|
||||
export interface FileWithCollection {
|
||||
|
@ -114,6 +117,7 @@ class UploadService {
|
|||
private filesCompleted: number;
|
||||
private totalFileCount: number;
|
||||
private fileProgress: Map<string, number>;
|
||||
private uploadResult:Map<string, number>;
|
||||
private metadataMap: Map<string, ParsedMetaDataJSON>;
|
||||
private filesToBeUploaded: FileWithCollection[];
|
||||
private progressBarProps;
|
||||
|
@ -132,6 +136,7 @@ class UploadService {
|
|||
|
||||
this.filesCompleted = 0;
|
||||
this.fileProgress = new Map<string, number>();
|
||||
this.uploadResult = new Map<string, number>();
|
||||
this.failedFiles = [];
|
||||
this.metadataMap = new Map<string, ParsedMetaDataJSON>();
|
||||
this.progressBarProps = progressBarProps;
|
||||
|
@ -233,11 +238,9 @@ class UploadService {
|
|||
|
||||
if (this.fileAlreadyInCollection(file, collection)) {
|
||||
// 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();
|
||||
await sleep(TwoSecondInMillSeconds);
|
||||
// remove completed files for file progress list
|
||||
this.fileProgress.delete(rawFile.name);
|
||||
} else {
|
||||
encryptedFile = await this.encryptFile(worker, file, collection.key);
|
||||
|
||||
|
@ -262,19 +265,26 @@ class UploadService {
|
|||
|
||||
uploadFile = null;
|
||||
|
||||
this.fileProgress.delete(rawFile.name);
|
||||
this.fileProgress.set(rawFile.name, FileUploadResults.UPLOADED);
|
||||
|
||||
this.filesCompleted++;
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'file upload failed');
|
||||
this.failedFiles.push(fileWithCollection);
|
||||
// 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);
|
||||
logError(e, 'file upload failed');
|
||||
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 {
|
||||
file=null;
|
||||
encryptedFile=null;
|
||||
}
|
||||
this.uploadResult.set(rawFile.name, this.fileProgress.get(rawFile.name));
|
||||
this.fileProgress.delete(rawFile.name);
|
||||
this.updateProgressBarUI();
|
||||
|
||||
if (this.filesToBeUploaded.length > 0) {
|
||||
|
@ -290,7 +300,7 @@ class UploadService {
|
|||
}
|
||||
|
||||
private updateProgressBarUI() {
|
||||
const { setPercentComplete, setFileCounter, setFileProgress } =
|
||||
const { setPercentComplete, setFileCounter, setFileProgress, setUploadResult } =
|
||||
this.progressBarProps;
|
||||
setFileCounter({
|
||||
finished: this.filesCompleted,
|
||||
|
@ -309,6 +319,7 @@ class UploadService {
|
|||
}
|
||||
setPercentComplete(percentComplete);
|
||||
setFileProgress(this.fileProgress);
|
||||
setUploadResult(this.uploadResult);
|
||||
}
|
||||
|
||||
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 { SetLoading } from 'pages/gallery';
|
||||
import { getData, LS_KEYS } from './storage/localStorage';
|
||||
import { SUBSCRIPTION_VERIFICATION_ERROR } from './common/errorUtil';
|
||||
import { CustomError } from './common/errorUtil';
|
||||
|
||||
const STRIPE = 'stripe';
|
||||
|
||||
|
@ -123,7 +123,7 @@ export async function updateSubscription(
|
|||
close: { text: constants.CANCEL },
|
||||
});
|
||||
break;
|
||||
case SUBSCRIPTION_VERIFICATION_ERROR:
|
||||
case CustomError.SUBSCRIPTION_VERIFICATION_ERROR:
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.SUBSCRIPTION_VERIFICATION_FAILED,
|
||||
|
@ -239,7 +239,7 @@ export async function checkSubscriptionPurchase(
|
|||
} catch (e) {
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: SUBSCRIPTION_VERIFICATION_ERROR,
|
||||
content: CustomError.SUBSCRIPTION_VERIFICATION_ERROR,
|
||||
close: {},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,41 +1,40 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
|
||||
export const errorCodes = {
|
||||
ERR_STORAGE_LIMIT_EXCEEDED: '426',
|
||||
ERR_NO_ACTIVE_SUBSCRIPTION: '402',
|
||||
ERR_NO_INTERNET_CONNECTION: '1',
|
||||
ERR_SESSION_EXPIRED: '401',
|
||||
ERR_KEY_MISSING: '2',
|
||||
ERR_FORBIDDEN: '403',
|
||||
export const ServerErrorCodes = {
|
||||
SESSION_EXPIRED: '401',
|
||||
NO_ACTIVE_SUBSCRIPTION: '402',
|
||||
FORBIDDEN: '403',
|
||||
STORAGE_LIMIT_EXCEEDED: '426',
|
||||
};
|
||||
|
||||
|
||||
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) {
|
||||
let errorMessage = null;
|
||||
let parsedMessage = null;
|
||||
if (error?.status) {
|
||||
const errorCode = error.status.toString();
|
||||
switch (errorCode) {
|
||||
case errorCodes.ERR_NO_ACTIVE_SUBSCRIPTION:
|
||||
errorMessage = constants.SUBSCRIPTION_EXPIRED;
|
||||
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
|
||||
parsedMessage = constants.SUBSCRIPTION_EXPIRED;
|
||||
break;
|
||||
case errorCodes.ERR_STORAGE_LIMIT_EXCEEDED:
|
||||
errorMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
||||
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
|
||||
parsedMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
||||
break;
|
||||
case errorCodes.ERR_NO_INTERNET_CONNECTION:
|
||||
errorMessage = constants.NO_INTERNET_CONNECTION;
|
||||
break;
|
||||
case errorCodes.ERR_SESSION_EXPIRED:
|
||||
errorMessage = constants.SESSION_EXPIRED_MESSAGE;
|
||||
case ServerErrorCodes.SESSION_EXPIRED:
|
||||
parsedMessage = constants.SESSION_EXPIRED_MESSAGE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (errorMessage) {
|
||||
return { parsedError: new Error(errorMessage), parsed: true };
|
||||
if (parsedMessage) {
|
||||
return { parsedError: new Error(parsedMessage), parsed: true };
|
||||
} else {
|
||||
return ({
|
||||
parsedError: new Error(`${constants.UNKNOWN_ERROR} ${error}`), parsed: false,
|
||||
|
@ -48,6 +47,6 @@ export function handleError(error) {
|
|||
if (parsed) {
|
||||
throw parsedError;
|
||||
} 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';
|
||||
|
||||
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];
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { B64EncryptionResult } from 'services/upload/uploadService';
|
|||
import CryptoWorker from 'utils/crypto';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||
import { errorCodes } from './errorUtil';
|
||||
import { CustomError } from './errorUtil';
|
||||
|
||||
export const getActualKey = async () => {
|
||||
try {
|
||||
|
@ -18,7 +18,7 @@ export const getActualKey = async () => {
|
|||
);
|
||||
return key;
|
||||
} 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',
|
||||
1: 'reading google metadata files',
|
||||
2: (fileCounter) => `${fileCounter.finished} / ${fileCounter.total} files backed up`,
|
||||
3: 'backup complete!',
|
||||
3: 'backup complete',
|
||||
},
|
||||
UPLOADING_FILES: 'file upload',
|
||||
FAILED_UPLOAD_FILE_LIST: 'upload failed for following files',
|
||||
UNSUPPORTED_FILE_LIST: 'unsupported files',
|
||||
FILE_UPLOAD_PROGRESS: (name, progress) => (
|
||||
FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded',
|
||||
FILE_UPLOAD_PROGRESS: (name:string, progress:number) => (
|
||||
<div id={name}>
|
||||
<strong>{name}</strong>
|
||||
{' - '}
|
||||
|
@ -105,7 +104,7 @@ const englishConstants = {
|
|||
case -2:
|
||||
return 'already uploaded, skipping...';
|
||||
case -3:
|
||||
return 'unsupported file format, ignored';
|
||||
return ',unsupported file format, skipping....';
|
||||
default:
|
||||
return `${progress}%`;
|
||||
}
|
||||
|
@ -452,7 +451,30 @@ const englishConstants = {
|
|||
SEND_OTT: 'send otp',
|
||||
EMAIl_ALREADY_OWNED: 'email already taken',
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue