fix build
This commit is contained in:
parent
a6652c105a
commit
d65f6cb494
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { Button, Col, Form, FormControl } from 'react-bootstrap';
|
import { Button, Col, Form, FormControl } from 'react-bootstrap';
|
||||||
|
@ -13,10 +12,10 @@ import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||||
|
|
||||||
interface formValues {
|
interface formValues {
|
||||||
email: string;
|
email: string;
|
||||||
ott?:string;
|
ott?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmailRow =styled.div`
|
const EmailRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
@ -26,15 +25,15 @@ const EmailRow =styled.div`
|
||||||
color: #fff;
|
color: #fff;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props{
|
interface Props {
|
||||||
showMessage:(value:boolean)=>void;
|
showMessage: (value: boolean) => void;
|
||||||
setEmail:(email:string)=>void;
|
setEmail: (email: string) => void;
|
||||||
}
|
}
|
||||||
function ChangeEmailForm(props:Props) {
|
function ChangeEmailForm(props: Props) {
|
||||||
const [loading, setLoading]=useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [ottInputVisible, setShowOttInputVisibility]=useState(false);
|
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
|
||||||
const emailInputElement = useRef(null);
|
const emailInputElement = useRef(null);
|
||||||
const ottInputRef=useRef(null);
|
const ottInputRef = useRef(null);
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -43,14 +42,16 @@ function ChangeEmailForm(props:Props) {
|
||||||
}, 250);
|
}, 250);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if (!ottInputVisible) {
|
if (!ottInputVisible) {
|
||||||
props.showMessage(false);
|
props.showMessage(false);
|
||||||
}
|
}
|
||||||
}, [ottInputVisible]);
|
}, [ottInputVisible]);
|
||||||
|
|
||||||
const requestOTT= async( { email }: formValues,
|
const requestOTT = async (
|
||||||
{ setFieldError }: FormikHelpers<formValues>)=>{
|
{ email }: formValues,
|
||||||
|
{ setFieldError }: FormikHelpers<formValues>,
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await getOTTForEmailChange(email);
|
await getOTTForEmailChange(email);
|
||||||
|
@ -66,14 +67,18 @@ function ChangeEmailForm(props:Props) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const requestEmailChange = async (
|
||||||
const requestEmailChange= async( { email, ott }: formValues,
|
{ email, ott }: formValues,
|
||||||
{ setFieldError }: FormikHelpers<formValues>)=>{
|
{ setFieldError }: FormikHelpers<formValues>,
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await changeEmail(email, ott);
|
await changeEmail(email, ott);
|
||||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
|
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) {
|
||||||
setFieldError('ott', `${constants.INCORRECT_CODE}`);
|
setFieldError('ott', `${constants.INCORRECT_CODE}`);
|
||||||
|
@ -91,17 +96,10 @@ function ChangeEmailForm(props:Props) {
|
||||||
})}
|
})}
|
||||||
validateOnChange={false}
|
validateOnChange={false}
|
||||||
validateOnBlur={false}
|
validateOnBlur={false}
|
||||||
onSubmit={!ottInputVisible?requestOTT:requestEmailChange}
|
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}>
|
||||||
>
|
{({ values, errors, touched, handleChange, handleSubmit }) => (
|
||||||
{({
|
|
||||||
values,
|
|
||||||
errors,
|
|
||||||
touched,
|
|
||||||
handleChange,
|
|
||||||
handleSubmit,
|
|
||||||
}) => (
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
{!ottInputVisible ?
|
{!ottInputVisible ? (
|
||||||
<Form.Group controlId="formBasicEmail">
|
<Form.Group controlId="formBasicEmail">
|
||||||
<Form.Control
|
<Form.Control
|
||||||
ref={emailInputElement}
|
ref={emailInputElement}
|
||||||
|
@ -118,14 +116,17 @@ function ChangeEmailForm(props:Props) {
|
||||||
<FormControl.Feedback type="invalid">
|
<FormControl.Feedback type="invalid">
|
||||||
{errors.email}
|
{errors.email}
|
||||||
</FormControl.Feedback>
|
</FormControl.Feedback>
|
||||||
</Form.Group> :
|
</Form.Group>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<EmailRow>
|
<EmailRow>
|
||||||
<Col xs="8">
|
<Col xs="8">{values.email}</Col>
|
||||||
{values.email}
|
<Col xs="4">
|
||||||
</Col>
|
<Button
|
||||||
<Col xs ="4" >
|
variant="link"
|
||||||
<Button variant="link" onClick={()=>setShowOttInputVisibility(false)}>
|
onClick={() =>
|
||||||
|
setShowOttInputVisibility(false)
|
||||||
|
}>
|
||||||
{constants.CHANGE}
|
{constants.CHANGE}
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -146,14 +147,23 @@ function ChangeEmailForm(props:Props) {
|
||||||
{errors.ott}
|
{errors.ott}
|
||||||
</FormControl.Feedback>
|
</FormControl.Feedback>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</>}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
buttonText={!ottInputVisible?constants.SEND_OTT:constants.VERIFY}
|
buttonText={
|
||||||
|
!ottInputVisible
|
||||||
|
? constants.SEND_OTT
|
||||||
|
: constants.VERIFY
|
||||||
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button block variant="link" className="text-center" onClick={router.back}>
|
<Button
|
||||||
|
block
|
||||||
|
variant="link"
|
||||||
|
className="text-center"
|
||||||
|
onClick={router.back}>
|
||||||
{constants.GO_BACK}
|
{constants.GO_BACK}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -6,14 +6,13 @@ import constants from 'utils/strings/constants';
|
||||||
import { Label, Row, Value } from './Container';
|
import { Label, Row, Value } from './Container';
|
||||||
import { ComfySpan } from './ExportInProgress';
|
import { ComfySpan } from './ExportInProgress';
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean;
|
||||||
onHide: () => void
|
onHide: () => void;
|
||||||
exportFolder: string
|
exportFolder: string;
|
||||||
exportSize: string
|
exportSize: string;
|
||||||
lastExportTime: number
|
lastExportTime: number;
|
||||||
exportStats: ExportStats
|
exportStats: ExportStats;
|
||||||
updateExportFolder: (newFolder: string) => void;
|
updateExportFolder: (newFolder: string) => void;
|
||||||
exportFiles: () => void;
|
exportFiles: () => void;
|
||||||
retryFailed: () => void;
|
retryFailed: () => void;
|
||||||
|
@ -23,30 +22,69 @@ export default function ExportFinished(props: Props) {
|
||||||
const totalFiles = props.exportStats.failed + props.exportStats.success;
|
const totalFiles = props.exportStats.failed + props.exportStats.success;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ borderBottom: '1px solid #444', marginBottom: '20px', padding: '0 5%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid #444',
|
||||||
|
marginBottom: '20px',
|
||||||
|
padding: '0 5%',
|
||||||
|
}}>
|
||||||
<Row>
|
<Row>
|
||||||
<Label width="40%">{constants.LAST_EXPORT_TIME}</Label>
|
<Label width="40%">{constants.LAST_EXPORT_TIME}</Label>
|
||||||
<Value width="60%">{formatDateTime(props.lastExportTime)}</Value>
|
<Value width="60%">
|
||||||
</Row>
|
{formatDateTime(props.lastExportTime)}
|
||||||
<Row>
|
|
||||||
<Label width="60%">{constants.SUCCESSFULLY_EXPORTED_FILES}</Label>
|
|
||||||
<Value width="35%"><ComfySpan>{props.exportStats.success} / {totalFiles}</ComfySpan></Value>
|
|
||||||
</Row>
|
|
||||||
{props.exportStats.failed>0 &&
|
|
||||||
<Row>
|
|
||||||
<Label width="60%">{constants.FAILED_EXPORTED_FILES}</Label>
|
|
||||||
<Value width="35%">
|
|
||||||
<ComfySpan>{props.exportStats.failed} / {totalFiles}</ComfySpan>
|
|
||||||
</Value>
|
</Value>
|
||||||
</Row>}
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Label width="60%">
|
||||||
|
{constants.SUCCESSFULLY_EXPORTED_FILES}
|
||||||
|
</Label>
|
||||||
|
<Value width="35%">
|
||||||
|
<ComfySpan>
|
||||||
|
{props.exportStats.success} / {totalFiles}
|
||||||
|
</ComfySpan>
|
||||||
|
</Value>
|
||||||
|
</Row>
|
||||||
|
{props.exportStats.failed > 0 && (
|
||||||
|
<Row>
|
||||||
|
<Label width="60%">
|
||||||
|
{constants.FAILED_EXPORTED_FILES}
|
||||||
|
</Label>
|
||||||
|
<Value width="35%">
|
||||||
|
<ComfySpan>
|
||||||
|
{props.exportStats.failed} / {totalFiles}
|
||||||
|
</ComfySpan>
|
||||||
|
</Value>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-around' }}>
|
<div
|
||||||
<Button block variant={'outline-secondary'} onClick={props.onHide}>{constants.CLOSE}</Button>
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant={'outline-secondary'}
|
||||||
|
onClick={props.onHide}>
|
||||||
|
{constants.CLOSE}
|
||||||
|
</Button>
|
||||||
<div style={{ width: '30px' }} />
|
<div style={{ width: '30px' }} />
|
||||||
{props.exportStats.failed !== 0 ?
|
{props.exportStats.failed !== 0 ? (
|
||||||
<Button block variant={'outline-danger'} onClick={props.retryFailed}>{constants.RETRY_EXPORT_}</Button> :
|
<Button
|
||||||
<Button block variant={'outline-success'} onClick={props.exportFiles}>{constants.EXPORT_AGAIN}</Button>
|
block
|
||||||
}
|
variant={'outline-danger'}
|
||||||
|
onClick={props.retryFailed}>
|
||||||
|
{constants.RETRY_EXPORT_}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant={'outline-success'}
|
||||||
|
onClick={props.exportFiles}>
|
||||||
|
{constants.EXPORT_AGAIN}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,42 +5,83 @@ import styled from 'styled-components';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
export const ComfySpan = styled.span`
|
export const ComfySpan = styled.span`
|
||||||
word-spacing:1rem;
|
word-spacing: 1rem;
|
||||||
color:#ddd;
|
color: #ddd;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean;
|
||||||
onHide: () => void
|
onHide: () => void;
|
||||||
exportFolder: string
|
exportFolder: string;
|
||||||
exportSize: string
|
exportSize: string;
|
||||||
exportStage: ExportStage
|
exportStage: ExportStage;
|
||||||
exportProgress: ExportProgress
|
exportProgress: ExportProgress;
|
||||||
resumeExport: () => void;
|
resumeExport: () => void;
|
||||||
cancelExport: () => void
|
cancelExport: () => void;
|
||||||
pauseExport: () => void;
|
pauseExport: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExportInProgress(props: Props) {
|
export default function ExportInProgress(props: Props) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: '30px', padding: '0 5%', display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '30px',
|
||||||
|
padding: '0 5%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}>
|
||||||
<div style={{ marginBottom: '10px' }}>
|
<div style={{ marginBottom: '10px' }}>
|
||||||
<ComfySpan> {props.exportProgress.current} / {props.exportProgress.total} </ComfySpan> <span style={{ marginLeft: '10px' }}> files exported {props.exportStage === ExportStage.PAUSED && `(paused)`}</span>
|
<ComfySpan>
|
||||||
|
{' '}
|
||||||
|
{props.exportProgress.current} /{' '}
|
||||||
|
{props.exportProgress.total}{' '}
|
||||||
|
</ComfySpan>{' '}
|
||||||
|
<span style={{ marginLeft: '10px' }}>
|
||||||
|
{' '}
|
||||||
|
files exported{' '}
|
||||||
|
{props.exportStage === ExportStage.PAUSED && `(paused)`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: '100%', marginBottom: '30px' }}>
|
<div style={{ width: '100%', marginBottom: '30px' }}>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
now={Math.round(props.exportProgress.current * 100 / props.exportProgress.total)}
|
now={Math.round(
|
||||||
|
(props.exportProgress.current * 100) /
|
||||||
|
props.exportProgress.total,
|
||||||
|
)}
|
||||||
animated={!(props.exportStage === ExportStage.PAUSED)}
|
animated={!(props.exportStage === ExportStage.PAUSED)}
|
||||||
variant="upload-progress-bar"
|
variant="upload-progress-bar"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-around' }}>
|
<div
|
||||||
{props.exportStage === ExportStage.PAUSED ?
|
style={{
|
||||||
<Button block variant={'outline-secondary'} onClick={props.resumeExport}>{constants.RESUME}</Button> :
|
width: '100%',
|
||||||
<Button block variant={'outline-secondary'} onClick={props.pauseExport}>{constants.PAUSE}</Button>
|
display: 'flex',
|
||||||
}
|
justifyContent: 'space-around',
|
||||||
|
}}>
|
||||||
|
{props.exportStage === ExportStage.PAUSED ? (
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant={'outline-secondary'}
|
||||||
|
onClick={props.resumeExport}>
|
||||||
|
{constants.RESUME}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant={'outline-secondary'}
|
||||||
|
onClick={props.pauseExport}>
|
||||||
|
{constants.PAUSE}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<div style={{ width: '30px' }} />
|
<div style={{ width: '30px' }} />
|
||||||
<Button block variant={'outline-danger'} onClick={props.cancelExport}>{constants.CANCEL}</Button>
|
<Button
|
||||||
|
block
|
||||||
|
variant={'outline-danger'}
|
||||||
|
onClick={props.cancelExport}>
|
||||||
|
{constants.CANCEL}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
import exportService, { ExportProgress, ExportStage, ExportStats, ExportType } from 'services/exportService';
|
import exportService, {
|
||||||
|
ExportProgress,
|
||||||
|
ExportStage,
|
||||||
|
ExportStats,
|
||||||
|
ExportType,
|
||||||
|
} from 'services/exportService';
|
||||||
import { getLocalFiles } from 'services/fileService';
|
import { getLocalFiles } from 'services/fileService';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { sleep } from 'utils/common';
|
import { sleep } from 'utils/common';
|
||||||
|
@ -18,38 +23,44 @@ import MessageDialog from './MessageDialog';
|
||||||
|
|
||||||
const FolderIconWrapper = styled.div`
|
const FolderIconWrapper = styled.div`
|
||||||
width: 15%;
|
width: 15%;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
border: 1px solid #444;
|
border: 1px solid #444;
|
||||||
border-radius:15%;
|
border-radius: 15%;
|
||||||
&:hover{
|
&:hover {
|
||||||
background-color:#444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ExportFolderPathContainer =styled.span`
|
const ExportFolderPathContainer = styled.span`
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
|
||||||
/* Beginning of string */
|
/* Beginning of string */
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean;
|
||||||
onHide: () => void
|
onHide: () => void;
|
||||||
usage: string
|
usage: string;
|
||||||
}
|
}
|
||||||
export default function ExportModal(props: Props) {
|
export default function ExportModal(props: Props) {
|
||||||
const [exportStage, setExportStage] = useState(ExportStage.INIT);
|
const [exportStage, setExportStage] = useState(ExportStage.INIT);
|
||||||
const [exportFolder, setExportFolder] = useState('');
|
const [exportFolder, setExportFolder] = useState('');
|
||||||
const [exportSize, setExportSize] = useState('');
|
const [exportSize, setExportSize] = useState('');
|
||||||
const [exportProgress, setExportProgress] = useState<ExportProgress>({ current: 0, total: 0 });
|
const [exportProgress, setExportProgress] = useState<ExportProgress>({
|
||||||
const [exportStats, setExportStats] = useState<ExportStats>({ failed: 0, success: 0 });
|
current: 0,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const [exportStats, setExportStats] = useState<ExportStats>({
|
||||||
|
failed: 0,
|
||||||
|
success: 0,
|
||||||
|
});
|
||||||
const [lastExportTime, setLastExportTime] = useState(0);
|
const [lastExportTime, setLastExportTime] = useState(0);
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
|
@ -64,7 +75,9 @@ export default function ExportModal(props: Props) {
|
||||||
exportService.ElectronAPIs.registerStopExportListener(stopExport);
|
exportService.ElectronAPIs.registerStopExportListener(stopExport);
|
||||||
exportService.ElectronAPIs.registerPauseExportListener(pauseExport);
|
exportService.ElectronAPIs.registerPauseExportListener(pauseExport);
|
||||||
exportService.ElectronAPIs.registerResumeExportListener(resumeExport);
|
exportService.ElectronAPIs.registerResumeExportListener(resumeExport);
|
||||||
exportService.ElectronAPIs.registerRetryFailedExportListener(retryFailedExport);
|
exportService.ElectronAPIs.registerRetryFailedExportListener(
|
||||||
|
retryFailedExport,
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -76,7 +89,10 @@ export default function ExportModal(props: Props) {
|
||||||
setExportStage(exportInfo?.stage ?? ExportStage.INIT);
|
setExportStage(exportInfo?.stage ?? ExportStage.INIT);
|
||||||
setLastExportTime(exportInfo?.lastAttemptTimestamp);
|
setLastExportTime(exportInfo?.lastAttemptTimestamp);
|
||||||
setExportProgress(exportInfo?.progress ?? { current: 0, total: 0 });
|
setExportProgress(exportInfo?.progress ?? { current: 0, total: 0 });
|
||||||
setExportStats({ success: exportInfo?.exportedFiles?.length ?? 0, failed: exportInfo?.failedFiles?.length ?? 0 });
|
setExportStats({
|
||||||
|
success: exportInfo?.exportedFiles?.length ?? 0,
|
||||||
|
failed: exportInfo?.failedFiles?.length ?? 0,
|
||||||
|
});
|
||||||
if (exportInfo?.stage === ExportStage.INPROGRESS) {
|
if (exportInfo?.stage === ExportStage.INPROGRESS) {
|
||||||
resumeExport();
|
resumeExport();
|
||||||
}
|
}
|
||||||
|
@ -96,10 +112,21 @@ export default function ExportModal(props: Props) {
|
||||||
const failedFilesCnt = exportRecord.failedFiles.length;
|
const failedFilesCnt = exportRecord.failedFiles.length;
|
||||||
const syncedFilesCnt = localFiles.length;
|
const syncedFilesCnt = localFiles.length;
|
||||||
if (syncedFilesCnt > exportedFileCnt + failedFilesCnt) {
|
if (syncedFilesCnt > exportedFileCnt + failedFilesCnt) {
|
||||||
updateExportProgress({ current: exportedFileCnt + failedFilesCnt, total: syncedFilesCnt });
|
updateExportProgress({
|
||||||
const exportFileUIDs = new Set([...exportRecord.exportedFiles, ...exportRecord.failedFiles]);
|
current: exportedFileCnt + failedFilesCnt,
|
||||||
const unExportedFiles = localFiles.filter((file) => !exportFileUIDs.has(getFileUID(file)));
|
total: syncedFilesCnt,
|
||||||
exportService.addFilesQueuedRecord(exportFolder, unExportedFiles);
|
});
|
||||||
|
const exportFileUIDs = new Set([
|
||||||
|
...exportRecord.exportedFiles,
|
||||||
|
...exportRecord.failedFiles,
|
||||||
|
]);
|
||||||
|
const unExportedFiles = localFiles.filter(
|
||||||
|
(file) => !exportFileUIDs.has(getFileUID(file)),
|
||||||
|
);
|
||||||
|
exportService.addFilesQueuedRecord(
|
||||||
|
exportFolder,
|
||||||
|
unExportedFiles,
|
||||||
|
);
|
||||||
updateExportStage(ExportStage.PAUSED);
|
updateExportStage(ExportStage.PAUSED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +134,6 @@ export default function ExportModal(props: Props) {
|
||||||
main();
|
main();
|
||||||
}, [props.show]);
|
}, [props.show]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setExportSize(props.usage);
|
setExportSize(props.usage);
|
||||||
}, [props.usage]);
|
}, [props.usage]);
|
||||||
|
@ -162,7 +188,10 @@ export default function ExportModal(props: Props) {
|
||||||
const startExport = async () => {
|
const startExport = async () => {
|
||||||
await preExportRun();
|
await preExportRun();
|
||||||
updateExportProgress({ current: 0, total: 0 });
|
updateExportProgress({ current: 0, total: 0 });
|
||||||
const { paused } = await exportService.exportFiles(updateExportProgress, ExportType.NEW);
|
const { paused } = await exportService.exportFiles(
|
||||||
|
updateExportProgress,
|
||||||
|
ExportType.NEW,
|
||||||
|
);
|
||||||
await postExportRun(paused);
|
await postExportRun(paused);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -184,13 +213,15 @@ export default function ExportModal(props: Props) {
|
||||||
const pausedStageProgress = exportRecord.progress;
|
const pausedStageProgress = exportRecord.progress;
|
||||||
setExportProgress(pausedStageProgress);
|
setExportProgress(pausedStageProgress);
|
||||||
|
|
||||||
const updateExportStatsWithOffset = ((progress: ExportProgress) => updateExportProgress(
|
const updateExportStatsWithOffset = (progress: ExportProgress) =>
|
||||||
{
|
updateExportProgress({
|
||||||
current: pausedStageProgress.current + progress.current,
|
current: pausedStageProgress.current + progress.current,
|
||||||
total: pausedStageProgress.current + progress.total,
|
total: pausedStageProgress.current + progress.total,
|
||||||
},
|
});
|
||||||
));
|
const { paused } = await exportService.exportFiles(
|
||||||
const { paused } = await exportService.exportFiles(updateExportStatsWithOffset, ExportType.PENDING);
|
updateExportStatsWithOffset,
|
||||||
|
ExportType.PENDING,
|
||||||
|
);
|
||||||
|
|
||||||
await postExportRun(paused);
|
await postExportRun(paused);
|
||||||
};
|
};
|
||||||
|
@ -199,7 +230,10 @@ export default function ExportModal(props: Props) {
|
||||||
await preExportRun();
|
await preExportRun();
|
||||||
updateExportProgress({ current: 0, total: exportStats.failed });
|
updateExportProgress({ current: 0, total: exportStats.failed });
|
||||||
|
|
||||||
const { paused } = await exportService.exportFiles(updateExportProgress, ExportType.RETRY_FAILED);
|
const { paused } = await exportService.exportFiles(
|
||||||
|
updateExportProgress,
|
||||||
|
ExportType.RETRY_FAILED,
|
||||||
|
);
|
||||||
await postExportRun(paused);
|
await postExportRun(paused);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,7 +258,8 @@ export default function ExportModal(props: Props) {
|
||||||
switch (exportStage) {
|
switch (exportStage) {
|
||||||
case ExportStage.INIT:
|
case ExportStage.INIT:
|
||||||
return (
|
return (
|
||||||
<ExportInit {...props}
|
<ExportInit
|
||||||
|
{...props}
|
||||||
exportFolder={exportFolder}
|
exportFolder={exportFolder}
|
||||||
exportSize={exportSize}
|
exportSize={exportSize}
|
||||||
updateExportFolder={updateExportFolder}
|
updateExportFolder={updateExportFolder}
|
||||||
|
@ -235,7 +270,8 @@ export default function ExportModal(props: Props) {
|
||||||
case ExportStage.INPROGRESS:
|
case ExportStage.INPROGRESS:
|
||||||
case ExportStage.PAUSED:
|
case ExportStage.PAUSED:
|
||||||
return (
|
return (
|
||||||
<ExportInProgress {...props}
|
<ExportInProgress
|
||||||
|
{...props}
|
||||||
exportFolder={exportFolder}
|
exportFolder={exportFolder}
|
||||||
exportSize={exportSize}
|
exportSize={exportSize}
|
||||||
exportStage={exportStage}
|
exportStage={exportStage}
|
||||||
|
@ -259,7 +295,8 @@ export default function ExportModal(props: Props) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
default: return (<></>);
|
default:
|
||||||
|
return <></>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,34 +306,50 @@ export default function ExportModal(props: Props) {
|
||||||
onHide={props.onHide}
|
onHide={props.onHide}
|
||||||
attributes={{
|
attributes={{
|
||||||
title: constants.EXPORT_DATA,
|
title: constants.EXPORT_DATA,
|
||||||
}}
|
}}>
|
||||||
>
|
<div
|
||||||
<div style={{ borderBottom: '1px solid #444', marginBottom: '20px', padding: '0 5%', width: '450px' }}>
|
style={{
|
||||||
|
borderBottom: '1px solid #444',
|
||||||
|
marginBottom: '20px',
|
||||||
|
padding: '0 5%',
|
||||||
|
width: '450px',
|
||||||
|
}}>
|
||||||
<Row>
|
<Row>
|
||||||
<Label width="40%">{constants.DESTINATION}</Label>
|
<Label width="40%">{constants.DESTINATION}</Label>
|
||||||
<Value width="60%">
|
<Value width="60%">
|
||||||
{!exportFolder ?
|
{!exportFolder ? (
|
||||||
(<Button variant={'outline-success'} size={'sm'} onClick={selectExportDirectory}>{constants.SELECT_FOLDER}</Button>) :
|
<Button
|
||||||
(<>
|
variant={'outline-success'}
|
||||||
|
size={'sm'}
|
||||||
|
onClick={selectExportDirectory}>
|
||||||
|
{constants.SELECT_FOLDER}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{/* <span style={{ overflow: 'hidden', direction: 'rtl', height: '1.5rem', width: '90%', whiteSpace: 'nowrap' }}> */}
|
{/* <span style={{ overflow: 'hidden', direction: 'rtl', height: '1.5rem', width: '90%', whiteSpace: 'nowrap' }}> */}
|
||||||
<ExportFolderPathContainer>
|
<ExportFolderPathContainer>
|
||||||
{exportFolder}
|
{exportFolder}
|
||||||
</ExportFolderPathContainer>
|
</ExportFolderPathContainer>
|
||||||
{/* </span> */}
|
{/* </span> */}
|
||||||
{(exportStage === ExportStage.FINISHED || exportStage === ExportStage.INIT) && (
|
{(exportStage === ExportStage.FINISHED ||
|
||||||
<FolderIconWrapper onClick={selectExportDirectory} >
|
exportStage === ExportStage.INIT) && (
|
||||||
|
<FolderIconWrapper
|
||||||
|
onClick={selectExportDirectory}>
|
||||||
<FolderIcon />
|
<FolderIcon />
|
||||||
</FolderIconWrapper>
|
</FolderIconWrapper>
|
||||||
)}
|
)}
|
||||||
</>)
|
</>
|
||||||
}
|
)}
|
||||||
</Value>
|
</Value>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Label width="40%">{constants.TOTAL_EXPORT_SIZE} </Label><Value width="60%">{exportSize ? `${exportSize}` : <InProgressIcon />}</Value>
|
<Label width="40%">{constants.TOTAL_EXPORT_SIZE} </Label>
|
||||||
|
<Value width="60%">
|
||||||
|
{exportSize ? `${exportSize}` : <InProgressIcon />}
|
||||||
|
</Value>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
<ExportDynamicState />
|
<ExportDynamicState />
|
||||||
</MessageDialog >
|
</MessageDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,12 @@ import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
|
||||||
import { SetDialogMessage } from './MessageDialog';
|
import { SetDialogMessage } from './MessageDialog';
|
||||||
import { CustomError } 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,
|
||||||
IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS, SPACE_BTW_DATES,
|
DATE_CONTAINER_HEIGHT,
|
||||||
|
IMAGE_CONTAINER_MAX_HEIGHT,
|
||||||
|
IMAGE_CONTAINER_MAX_WIDTH,
|
||||||
|
MIN_COLUMNS,
|
||||||
|
SPACE_BTW_DATES,
|
||||||
} from 'types';
|
} from 'types';
|
||||||
|
|
||||||
const NO_OF_PAGES = 2;
|
const NO_OF_PAGES = 2;
|
||||||
|
@ -68,21 +72,24 @@ const getTemplateColumns = (columns: number, groups?: number[]): string => {
|
||||||
if (sum < columns) {
|
if (sum < columns) {
|
||||||
groups[groups.length - 1] += columns - sum;
|
groups[groups.length - 1] += columns - sum;
|
||||||
}
|
}
|
||||||
return groups.map((x) => `repeat(${x}, 1fr)`).join(` ${SPACE_BTW_DATES}px `);
|
return groups
|
||||||
|
.map((x) => `repeat(${x}, 1fr)`)
|
||||||
|
.join(` ${SPACE_BTW_DATES}px `);
|
||||||
} else {
|
} else {
|
||||||
return `repeat(${columns}, 1fr)`;
|
return `repeat(${columns}, 1fr)`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListContainer = styled.div<{ columns: number, groups?: number[] }>`
|
const ListContainer = styled.div<{ columns: number; groups?: number[] }>`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: ${({ columns, groups }) => getTemplateColumns(columns, groups)};
|
grid-template-columns: ${({ columns, groups }) =>
|
||||||
|
getTemplateColumns(columns, groups)};
|
||||||
grid-column-gap: ${GAP_BTW_TILES}px;
|
grid-column-gap: ${GAP_BTW_TILES}px;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
@media(max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
|
@media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -139,7 +146,7 @@ interface Props {
|
||||||
search: Search;
|
search: Search;
|
||||||
setSearchStats: setSearchStats;
|
setSearchStats: setSearchStats;
|
||||||
deleted?: number[];
|
deleted?: number[];
|
||||||
setDialogMessage: SetDialogMessage
|
setDialogMessage: SetDialogMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PhotoFrame = ({
|
const PhotoFrame = ({
|
||||||
|
@ -303,14 +310,13 @@ const PhotoFrame = ({
|
||||||
video.preload = 'metadata';
|
video.preload = 'metadata';
|
||||||
video.src = url;
|
video.src = url;
|
||||||
video.currentTime = 3;
|
video.currentTime = 3;
|
||||||
const t = setTimeout(
|
const t = setTimeout(() => {
|
||||||
() => {
|
reject(
|
||||||
reject(
|
Error(
|
||||||
Error(`${CustomError.VIDEO_PLAYBACK_FAILED} err: wait time exceeded`),
|
`${CustomError.VIDEO_PLAYBACK_FAILED} err: wait time exceeded`,
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
WAIT_FOR_VIDEO_PLAYBACK,
|
}, WAIT_FOR_VIDEO_PLAYBACK);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
item.html = `
|
item.html = `
|
||||||
<video width="320" height="240" controls>
|
<video width="320" height="240" controls>
|
||||||
|
@ -332,7 +338,8 @@ const PhotoFrame = ({
|
||||||
};
|
};
|
||||||
setDialogMessage({
|
setDialogMessage({
|
||||||
title: constants.VIDEO_PLAYBACK_FAILED,
|
title: constants.VIDEO_PLAYBACK_FAILED,
|
||||||
content: constants.VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD,
|
content:
|
||||||
|
constants.VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD,
|
||||||
staticBackdrop: true,
|
staticBackdrop: true,
|
||||||
proceed: {
|
proceed: {
|
||||||
text: constants.DOWNLOAD,
|
text: constants.DOWNLOAD,
|
||||||
|
@ -397,11 +404,10 @@ const PhotoFrame = ({
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSameDay = (first, second) => (
|
const isSameDay = (first, second) =>
|
||||||
first.getFullYear() === second.getFullYear() &&
|
first.getFullYear() === second.getFullYear() &&
|
||||||
first.getMonth() === second.getMonth() &&
|
first.getMonth() === second.getMonth() &&
|
||||||
first.getDate() === second.getDate()
|
first.getDate() === second.getDate();
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks and merge multiple dates into a single row.
|
* Checks and merge multiple dates into a single row.
|
||||||
|
@ -410,7 +416,10 @@ const PhotoFrame = ({
|
||||||
* @param columns
|
* @param columns
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const mergeTimeStampList = (items: TimeStampListItem[], columns: number): TimeStampListItem[] => {
|
const mergeTimeStampList = (
|
||||||
|
items: TimeStampListItem[],
|
||||||
|
columns: number,
|
||||||
|
): TimeStampListItem[] => {
|
||||||
const newList: TimeStampListItem[] = [];
|
const newList: TimeStampListItem[] = [];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let newIndex = 0;
|
let newIndex = 0;
|
||||||
|
@ -423,12 +432,18 @@ const PhotoFrame = ({
|
||||||
// we can add more items to the same list.
|
// we can add more items to the same list.
|
||||||
if (newList[newIndex]) {
|
if (newList[newIndex]) {
|
||||||
// Check if items can be added to same list
|
// Check if items can be added to same list
|
||||||
if (newList[newIndex + 1].items.length + items[index + 1].items.length <= columns) {
|
if (
|
||||||
|
newList[newIndex + 1].items.length +
|
||||||
|
items[index + 1].items.length <=
|
||||||
|
columns
|
||||||
|
) {
|
||||||
newList[newIndex].dates.push({
|
newList[newIndex].dates.push({
|
||||||
date: currItem.date,
|
date: currItem.date,
|
||||||
span: items[index + 1].items.length,
|
span: items[index + 1].items.length,
|
||||||
});
|
});
|
||||||
newList[newIndex + 1].items = newList[newIndex + 1].items.concat(items[index + 1].items);
|
newList[newIndex + 1].items = newList[
|
||||||
|
newIndex + 1
|
||||||
|
].items.concat(items[index + 1].items);
|
||||||
index += 2;
|
index += 2;
|
||||||
} else {
|
} else {
|
||||||
// Adding items would exceed the number of columns.
|
// Adding items would exceed the number of columns.
|
||||||
|
@ -441,10 +456,12 @@ const PhotoFrame = ({
|
||||||
newList.push({
|
newList.push({
|
||||||
...currItem,
|
...currItem,
|
||||||
date: null,
|
date: null,
|
||||||
dates: [{
|
dates: [
|
||||||
date: currItem.date,
|
{
|
||||||
span: items[index + 1].items.length,
|
date: currItem.date,
|
||||||
}],
|
span: items[index + 1].items.length,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
newList.push(items[index + 1]);
|
newList.push(items[index + 1]);
|
||||||
index += 2;
|
index += 2;
|
||||||
|
@ -474,7 +491,7 @@ const PhotoFrame = ({
|
||||||
<>
|
<>
|
||||||
{!isFirstLoad && files.length === 0 && !searchMode ? (
|
{!isFirstLoad && files.length === 0 && !searchMode ? (
|
||||||
<EmptyScreen>
|
<EmptyScreen>
|
||||||
<img height={150} src='/images/gallery.png' />
|
<img height={150} src="/images/gallery.png" />
|
||||||
<Button
|
<Button
|
||||||
variant="outline-success"
|
variant="outline-success"
|
||||||
onClick={openFileUploader}
|
onClick={openFileUploader}
|
||||||
|
@ -484,8 +501,7 @@ const PhotoFrame = ({
|
||||||
paddingRight: '32px',
|
paddingRight: '32px',
|
||||||
paddingTop: '12px',
|
paddingTop: '12px',
|
||||||
paddingBottom: '12px',
|
paddingBottom: '12px',
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{constants.UPLOAD_FIRST_PHOTO}
|
{constants.UPLOAD_FIRST_PHOTO}
|
||||||
</Button>
|
</Button>
|
||||||
</EmptyScreen>
|
</EmptyScreen>
|
||||||
|
@ -493,7 +509,9 @@ const PhotoFrame = ({
|
||||||
<Container>
|
<Container>
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({ height, width }) => {
|
{({ height, width }) => {
|
||||||
let columns = Math.floor(width / IMAGE_CONTAINER_MAX_WIDTH);
|
let columns = Math.floor(
|
||||||
|
width / IMAGE_CONTAINER_MAX_WIDTH,
|
||||||
|
);
|
||||||
let listItemHeight = IMAGE_CONTAINER_MAX_HEIGHT;
|
let listItemHeight = IMAGE_CONTAINER_MAX_HEIGHT;
|
||||||
let skipMerge = false;
|
let skipMerge = false;
|
||||||
if (columns < MIN_COLUMNS) {
|
if (columns < MIN_COLUMNS) {
|
||||||
|
@ -506,27 +524,36 @@ const PhotoFrame = ({
|
||||||
let listItemIndex = 0;
|
let listItemIndex = 0;
|
||||||
let currentDate = -1;
|
let currentDate = -1;
|
||||||
filteredData.forEach((item, index) => {
|
filteredData.forEach((item, index) => {
|
||||||
if (!isSameDay(new Date(item.metadata.creationTime / 1000), new Date(currentDate))) {
|
if (
|
||||||
currentDate = item.metadata.creationTime / 1000;
|
!isSameDay(
|
||||||
const dateTimeFormat = new Intl.DateTimeFormat('en-IN', {
|
new Date(
|
||||||
weekday: 'short',
|
item.metadata.creationTime / 1000,
|
||||||
year: 'numeric',
|
),
|
||||||
month: 'short',
|
new Date(currentDate),
|
||||||
day: 'numeric',
|
)
|
||||||
});
|
) {
|
||||||
|
currentDate =
|
||||||
|
item.metadata.creationTime / 1000;
|
||||||
|
const dateTimeFormat =
|
||||||
|
new Intl.DateTimeFormat('en-IN', {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
});
|
||||||
timeStampList.push({
|
timeStampList.push({
|
||||||
itemType: ITEM_TYPE.TIME,
|
itemType: ITEM_TYPE.TIME,
|
||||||
date: isSameDay(
|
date: isSameDay(
|
||||||
new Date(currentDate),
|
new Date(currentDate),
|
||||||
new Date(),
|
new Date(),
|
||||||
) ?
|
)
|
||||||
'Today' :
|
? 'Today'
|
||||||
isSameDay(
|
: isSameDay(
|
||||||
new Date(currentDate),
|
new Date(currentDate),
|
||||||
new Date(Date.now() - A_DAY),
|
new Date(Date.now() - A_DAY),
|
||||||
) ?
|
)
|
||||||
'Yesterday' :
|
? 'Yesterday'
|
||||||
dateTimeFormat.format(
|
: dateTimeFormat.format(
|
||||||
currentDate,
|
currentDate,
|
||||||
),
|
),
|
||||||
id: currentDate.toString(),
|
id: currentDate.toString(),
|
||||||
|
@ -553,7 +580,10 @@ const PhotoFrame = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!skipMerge) {
|
if (!skipMerge) {
|
||||||
timeStampList = mergeTimeStampList(timeStampList, columns);
|
timeStampList = mergeTimeStampList(
|
||||||
|
timeStampList,
|
||||||
|
columns,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getItemSize = (index) => {
|
const getItemSize = (index) => {
|
||||||
|
@ -567,68 +597,89 @@ const PhotoFrame = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const photoFrameHeight=(()=>{
|
const photoFrameHeight = (() => {
|
||||||
let sum=0;
|
let sum = 0;
|
||||||
for (let i=0; i<timeStampList.length; i++) {
|
for (let i = 0; i < timeStampList.length; i++) {
|
||||||
sum+=getItemSize(i);
|
sum += getItemSize(i);
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
})();
|
})();
|
||||||
files.length < 30 && !searchMode &&
|
files.length < 30 &&
|
||||||
|
!searchMode &&
|
||||||
timeStampList.push({
|
timeStampList.push({
|
||||||
itemType: ITEM_TYPE.BANNER,
|
itemType: ITEM_TYPE.BANNER,
|
||||||
banner: (
|
banner: (
|
||||||
<BannerContainer span={columns}>
|
<BannerContainer span={columns}>
|
||||||
<p>{constants.INSTALL_MOBILE_APP()}</p>
|
<p>
|
||||||
|
{constants.INSTALL_MOBILE_APP()}
|
||||||
|
</p>
|
||||||
</BannerContainer>
|
</BannerContainer>
|
||||||
),
|
),
|
||||||
id: 'install-banner',
|
id: 'install-banner',
|
||||||
height: Math.max(48, height-photoFrameHeight),
|
height: Math.max(
|
||||||
|
48,
|
||||||
|
height - photoFrameHeight,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
const extraRowsToRender = Math.ceil(
|
const extraRowsToRender = Math.ceil(
|
||||||
(NO_OF_PAGES * height) / IMAGE_CONTAINER_MAX_HEIGHT,
|
(NO_OF_PAGES * height) /
|
||||||
|
IMAGE_CONTAINER_MAX_HEIGHT,
|
||||||
);
|
);
|
||||||
|
|
||||||
const generateKey = (index) => {
|
const generateKey = (index) => {
|
||||||
switch (timeStampList[index].itemType) {
|
switch (timeStampList[index].itemType) {
|
||||||
case ITEM_TYPE.TILE:
|
case ITEM_TYPE.TILE:
|
||||||
return `${timeStampList[index].items[0].id}-${timeStampList[index].items.slice(-1)[0].id}`;
|
return `${
|
||||||
|
timeStampList[index].items[0].id
|
||||||
|
}-${
|
||||||
|
timeStampList[index].items.slice(
|
||||||
|
-1,
|
||||||
|
)[0].id
|
||||||
|
}`;
|
||||||
default:
|
default:
|
||||||
return `${timeStampList[index].id}-${index}`;
|
return `${timeStampList[index].id}-${index}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderListItem = (
|
||||||
const renderListItem = (listItem: TimeStampListItem) => {
|
listItem: TimeStampListItem,
|
||||||
|
) => {
|
||||||
switch (listItem.itemType) {
|
switch (listItem.itemType) {
|
||||||
case ITEM_TYPE.TIME:
|
case ITEM_TYPE.TIME:
|
||||||
return listItem.dates ?
|
return listItem.dates ? (
|
||||||
listItem.dates.map((item) => (
|
listItem.dates.map((item) => (
|
||||||
<>
|
<>
|
||||||
<DateContainer key={item.date} span={item.span}>
|
<DateContainer
|
||||||
|
key={item.date}
|
||||||
|
span={item.span}>
|
||||||
{item.date}
|
{item.date}
|
||||||
</DateContainer>
|
</DateContainer>
|
||||||
<div />
|
<div />
|
||||||
</>
|
</>
|
||||||
)) :
|
))
|
||||||
(
|
) : (
|
||||||
<DateContainer span={columns}>
|
<DateContainer span={columns}>
|
||||||
{listItem.date}
|
{listItem.date}
|
||||||
</DateContainer>
|
</DateContainer>
|
||||||
);
|
);
|
||||||
case ITEM_TYPE.BANNER:
|
case ITEM_TYPE.BANNER:
|
||||||
return listItem.banner;
|
return listItem.banner;
|
||||||
default:
|
default: {
|
||||||
{
|
const ret = listItem.items.map(
|
||||||
const ret = (listItem.items.map(
|
(item, idx) =>
|
||||||
(item, idx) => getThumbnail(
|
getThumbnail(
|
||||||
filteredData,
|
filteredData,
|
||||||
listItem.itemStartIndex + idx,
|
listItem.itemStartIndex +
|
||||||
),
|
idx,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
if (listItem.groups) {
|
if (listItem.groups) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (let i = 0; i < listItem.groups.length - 1; i++) {
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < listItem.groups.length - 1;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
sum = sum + listItem.groups[i];
|
sum = sum + listItem.groups[i];
|
||||||
ret.splice(sum, 0, <div />);
|
ret.splice(sum, 0, <div />);
|
||||||
sum += 1;
|
sum += 1;
|
||||||
|
@ -648,12 +699,17 @@ const PhotoFrame = ({
|
||||||
width={width}
|
width={width}
|
||||||
itemCount={timeStampList.length}
|
itemCount={timeStampList.length}
|
||||||
itemKey={generateKey}
|
itemKey={generateKey}
|
||||||
overscanCount={extraRowsToRender}
|
overscanCount={extraRowsToRender}>
|
||||||
>
|
|
||||||
{({ index, style }) => (
|
{({ index, style }) => (
|
||||||
<ListItem style={style}>
|
<ListItem style={style}>
|
||||||
<ListContainer columns={columns} groups={timeStampList[index].groups}>
|
<ListContainer
|
||||||
{renderListItem(timeStampList[index])}
|
columns={columns}
|
||||||
|
groups={
|
||||||
|
timeStampList[index].groups
|
||||||
|
}>
|
||||||
|
{renderListItem(
|
||||||
|
timeStampList[index],
|
||||||
|
)}
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { Subscription } from 'services/billingService';
|
||||||
interface Props {
|
interface Props {
|
||||||
collections: Collection[];
|
collections: Collection[];
|
||||||
setDialogMessage: SetDialogMessage;
|
setDialogMessage: SetDialogMessage;
|
||||||
setLoading: SetLoading,
|
setLoading: SetLoading;
|
||||||
showPlanSelectorModal: () => void;
|
showPlanSelectorModal: () => void;
|
||||||
}
|
}
|
||||||
export default function Sidebar(props: Props) {
|
export default function Sidebar(props: Props) {
|
||||||
|
@ -56,18 +56,23 @@ export default function Sidebar(props: Props) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const userDetails=await getUserDetails();
|
const userDetails = await getUserDetails();
|
||||||
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.USER, {
|
||||||
|
...getData(LS_KEYS.USER),
|
||||||
|
email: userDetails.email,
|
||||||
|
});
|
||||||
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
|
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
|
||||||
};
|
};
|
||||||
main();
|
main();
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
function openFeedbackURL() {
|
function openFeedbackURL() {
|
||||||
const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(getToken())}`;
|
const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(
|
||||||
|
getToken(),
|
||||||
|
)}`;
|
||||||
const win = window.open(feedbackURL, '_blank');
|
const win = window.open(feedbackURL, '_blank');
|
||||||
win.focus();
|
win.focus();
|
||||||
}
|
}
|
||||||
|
@ -108,9 +113,13 @@ export default function Sidebar(props: Props) {
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onStateChange={(state) => setIsOpen(state.isOpen)}
|
onStateChange={(state) => setIsOpen(state.isOpen)}
|
||||||
itemListElement="div"
|
itemListElement="div">
|
||||||
>
|
<div
|
||||||
<div style={{ display: 'flex', outline: 'none', textAlign: 'center' }}>
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
outline: 'none',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}>
|
||||||
<LogoImage
|
<LogoImage
|
||||||
style={{ height: '24px', padding: '3px' }}
|
style={{ height: '24px', padding: '3px' }}
|
||||||
alt="logo"
|
alt="logo"
|
||||||
|
@ -122,11 +131,16 @@ export default function Sidebar(props: Props) {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
color: 'rgb(45, 194, 98)',
|
color: 'rgb(45, 194, 98)',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{user?.email}
|
{user?.email}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, overflow: 'auto', outline: 'none', paddingTop: '0' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'auto',
|
||||||
|
outline: 'none',
|
||||||
|
paddingTop: '0',
|
||||||
|
}}>
|
||||||
<div style={{ outline: 'none' }}>
|
<div style={{ outline: 'none' }}>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<h5 style={{ margin: '4px 0 12px 2px' }}>
|
<h5 style={{ margin: '4px 0 12px 2px' }}>
|
||||||
|
@ -155,11 +169,10 @@ export default function Sidebar(props: Props) {
|
||||||
variant="outline-success"
|
variant="outline-success"
|
||||||
block
|
block
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onManageClick}
|
onClick={onManageClick}>
|
||||||
>
|
{isSubscribed(subscription)
|
||||||
{isSubscribed(subscription) ?
|
? constants.MANAGE
|
||||||
constants.MANAGE :
|
: constants.SUBSCRIBE}
|
||||||
constants.SUBSCRIBE}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,7 +185,9 @@ export default function Sidebar(props: Props) {
|
||||||
{usage ? (
|
{usage ? (
|
||||||
constants.USAGE_INFO(
|
constants.USAGE_INFO(
|
||||||
usage,
|
usage,
|
||||||
Number(convertBytesToGBs(subscription?.storage)),
|
Number(
|
||||||
|
convertBytesToGBs(subscription?.storage),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
@ -197,29 +212,28 @@ export default function Sidebar(props: Props) {
|
||||||
/>
|
/>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
style={{ marginTop: '30px' }}
|
style={{ marginTop: '30px' }}
|
||||||
onClick={openFeedbackURL}
|
onClick={openFeedbackURL}>
|
||||||
>
|
|
||||||
{constants.REQUEST_FEATURE}
|
{constants.REQUEST_FEATURE}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
style={{ marginTop: '30px' }}
|
style={{ marginTop: '30px' }}
|
||||||
onClick={openSupportMail}
|
onClick={openSupportMail}>
|
||||||
>
|
|
||||||
{constants.SUPPORT}
|
{constants.SUPPORT}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<>
|
<>
|
||||||
<RecoveryKeyModal
|
<RecoveryKeyModal
|
||||||
show={recoverModalView}
|
show={recoverModalView}
|
||||||
onHide={() => setRecoveryModalView(false)}
|
onHide={() => setRecoveryModalView(false)}
|
||||||
somethingWentWrong={() => props.setDialogMessage({
|
somethingWentWrong={() =>
|
||||||
title: constants.RECOVER_KEY_GENERATION_FAILED,
|
props.setDialogMessage({
|
||||||
close: { variant: 'danger' },
|
title: constants.RECOVER_KEY_GENERATION_FAILED,
|
||||||
})}
|
close: { variant: 'danger' },
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
style={{ marginTop: '30px' }}
|
style={{ marginTop: '30px' }}
|
||||||
onClick={() => setRecoveryModalView(true)}
|
onClick={() => setRecoveryModalView(true)}>
|
||||||
>
|
|
||||||
{constants.DOWNLOAD_RECOVERY_KEY}
|
{constants.DOWNLOAD_RECOVERY_KEY}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</>
|
</>
|
||||||
|
@ -233,8 +247,7 @@ export default function Sidebar(props: Props) {
|
||||||
/>
|
/>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
style={{ marginTop: '30px' }}
|
style={{ marginTop: '30px' }}
|
||||||
onClick={() => setTwoFactorModalView(true)}
|
onClick={() => setTwoFactorModalView(true)}>
|
||||||
>
|
|
||||||
{constants.TWO_FACTOR}
|
{constants.TWO_FACTOR}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</>
|
</>
|
||||||
|
@ -243,8 +256,7 @@ export default function Sidebar(props: Props) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||||
router.push('change-password');
|
router.push('change-password');
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{constants.CHANGE_PASSWORD}
|
{constants.CHANGE_PASSWORD}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
|
@ -252,18 +264,24 @@ export default function Sidebar(props: Props) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||||
router.push('change-email');
|
router.push('change-email');
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{constants.UPDATE_EMAIL}
|
{constants.UPDATE_EMAIL}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<>
|
<>
|
||||||
<ExportModal show={exportModalView} onHide={() => setExportModalView(false)} usage={usage} />
|
<ExportModal
|
||||||
<LinkButton style={{ marginTop: '30px' }} onClick={exportFiles}>
|
show={exportModalView}
|
||||||
|
onHide={() => setExportModalView(false)}
|
||||||
|
usage={usage}
|
||||||
|
/>
|
||||||
|
<LinkButton
|
||||||
|
style={{ marginTop: '30px' }}
|
||||||
|
onClick={exportFiles}>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
{constants.EXPORT}<div style={{ width: '20px' }} />
|
{constants.EXPORT}
|
||||||
{exportService.isExportInProgress() &&
|
<div style={{ width: '20px' }} />
|
||||||
|
{exportService.isExportInProgress() && (
|
||||||
<InProgressIcon />
|
<InProgressIcon />
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</>
|
</>
|
||||||
|
@ -278,18 +296,19 @@ export default function Sidebar(props: Props) {
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="danger"
|
variant="danger"
|
||||||
style={{ marginTop: '30px' }}
|
style={{ marginTop: '30px' }}
|
||||||
onClick={() => props.setDialogMessage({
|
onClick={() =>
|
||||||
title: `${constants.CONFIRM} ${constants.LOGOUT}`,
|
props.setDialogMessage({
|
||||||
content: constants.LOGOUT_MESSAGE,
|
title: `${constants.CONFIRM} ${constants.LOGOUT}`,
|
||||||
staticBackdrop: true,
|
content: constants.LOGOUT_MESSAGE,
|
||||||
proceed: {
|
staticBackdrop: true,
|
||||||
text: constants.LOGOUT,
|
proceed: {
|
||||||
action: logoutUser,
|
text: constants.LOGOUT,
|
||||||
variant: 'danger',
|
action: logoutUser,
|
||||||
},
|
variant: 'danger',
|
||||||
close: { text: constants.CANCEL },
|
},
|
||||||
})}
|
close: { text: constants.CANCEL },
|
||||||
>
|
})
|
||||||
|
}>
|
||||||
{constants.LOGOUT}
|
{constants.LOGOUT}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<div
|
<div
|
||||||
|
@ -299,6 +318,6 @@ export default function Sidebar(props: Props) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Menu >
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface Props {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
setDialogMessage: SetDialogMessage;
|
setDialogMessage: SetDialogMessage;
|
||||||
setLoading: SetLoading
|
setLoading: SetLoading;
|
||||||
closeSidebar: () => void;
|
closeSidebar: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +26,16 @@ function TwoFactorModal(props: Props) {
|
||||||
if (!props.show) {
|
if (!props.show) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isTwoFactorEnabled = getData(LS_KEYS.USER).isTwoFactorEnabled ?? false;
|
const isTwoFactorEnabled =
|
||||||
|
getData(LS_KEYS.USER).isTwoFactorEnabled ?? false;
|
||||||
setTwoFactorStatus(isTwoFactorEnabled);
|
setTwoFactorStatus(isTwoFactorEnabled);
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const isTwoFactorEnabled = await getTwoFactorStatus();
|
const isTwoFactorEnabled = await getTwoFactorStatus();
|
||||||
setTwoFactorStatus(isTwoFactorEnabled);
|
setTwoFactorStatus(isTwoFactorEnabled);
|
||||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), isTwoFactorEnabled: false });
|
setData(LS_KEYS.USER, {
|
||||||
|
...getData(LS_KEYS.USER),
|
||||||
|
isTwoFactorEnabled: false,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
main();
|
main();
|
||||||
}, [props.show]);
|
}, [props.show]);
|
||||||
|
@ -51,12 +55,21 @@ function TwoFactorModal(props: Props) {
|
||||||
const twoFactorDisable = async () => {
|
const twoFactorDisable = async () => {
|
||||||
try {
|
try {
|
||||||
await disableTwoFactor();
|
await disableTwoFactor();
|
||||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), isTwoFactorEnabled: false });
|
setData(LS_KEYS.USER, {
|
||||||
|
...getData(LS_KEYS.USER),
|
||||||
|
isTwoFactorEnabled: false,
|
||||||
|
});
|
||||||
props.onHide();
|
props.onHide();
|
||||||
props.closeSidebar();
|
props.closeSidebar();
|
||||||
appContext.setDisappearingFlashMessage({ message: constants.TWO_FACTOR_DISABLE_SUCCESS, severity: 'info' });
|
appContext.setDisappearingFlashMessage({
|
||||||
|
message: constants.TWO_FACTOR_DISABLE_SUCCESS,
|
||||||
|
severity: 'info',
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appContext.setDisappearingFlashMessage({ message: constants.TWO_FACTOR_DISABLE_FAILED, severity: 'danger' });
|
appContext.setDisappearingFlashMessage({
|
||||||
|
message: constants.TWO_FACTOR_DISABLE_FAILED,
|
||||||
|
severity: 'danger',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const warnTwoFactorReconfigure = async () => {
|
const warnTwoFactorReconfigure = async () => {
|
||||||
|
@ -82,38 +95,60 @@ function TwoFactorModal(props: Props) {
|
||||||
attributes={{
|
attributes={{
|
||||||
title: constants.TWO_FACTOR_AUTHENTICATION,
|
title: constants.TWO_FACTOR_AUTHENTICATION,
|
||||||
staticBackdrop: true,
|
staticBackdrop: true,
|
||||||
}}
|
}}>
|
||||||
|
<div
|
||||||
>
|
{...(!isTwoFactorEnabled
|
||||||
<div {...(!isTwoFactorEnabled ? { style: { padding: '10px 10px 30px 10px' } } : { style: { padding: '10px' } })}>
|
? { style: { padding: '10px 10px 30px 10px' } }
|
||||||
{
|
: { style: { padding: '10px' } })}>
|
||||||
isTwoFactorEnabled ?
|
{isTwoFactorEnabled ? (
|
||||||
<>
|
<>
|
||||||
<Row>
|
<Row>
|
||||||
<Label>{constants.UPDATE_TWO_FACTOR_HINT}</Label>
|
<Label>{constants.UPDATE_TWO_FACTOR_HINT}</Label>
|
||||||
<Value>
|
<Value>
|
||||||
<Button variant={'outline-success'} onClick={warnTwoFactorReconfigure}>{constants.RECONFIGURE}</Button>
|
<Button
|
||||||
</Value>
|
variant={'outline-success'}
|
||||||
</Row>
|
onClick={warnTwoFactorReconfigure}>
|
||||||
<Row>
|
{constants.RECONFIGURE}
|
||||||
<Label>{constants.DISABLE_TWO_FACTOR_HINT} </Label>
|
</Button>
|
||||||
<Value>
|
</Value>
|
||||||
<Button variant={'outline-danger'} onClick={warnTwoFactorDisable}>{constants.DISABLE}</Button>
|
</Row>
|
||||||
</Value>
|
<Row>
|
||||||
</Row>
|
<Label>{constants.DISABLE_TWO_FACTOR_HINT} </Label>
|
||||||
|
<Value>
|
||||||
</> : (
|
<Button
|
||||||
<DeadCenter>
|
variant={'outline-danger'}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="36px" viewBox="0 0 24 24" width="36px" fill="#000000"><g fill="none"><path d="M0 0h24v24H0V0z" /><path d="M0 0h24v24H0V0z" opacity=".87" /></g><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z" /></svg>
|
onClick={warnTwoFactorDisable}>
|
||||||
<p />
|
{constants.DISABLE}
|
||||||
<p>{constants.TWO_FACTOR_INFO}</p>
|
</Button>
|
||||||
<div style={{ height: '10px' }} />
|
</Value>
|
||||||
<Button variant="outline-success" onClick={() => router.push('/two-factor/setup')}>{constants.ENABLE_TWO_FACTOR}</Button>
|
</Row>
|
||||||
</DeadCenter>
|
</>
|
||||||
)
|
) : (
|
||||||
}
|
<DeadCenter>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="36px"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="36px"
|
||||||
|
fill="#000000">
|
||||||
|
<g fill="none">
|
||||||
|
<path d="M0 0h24v24H0V0z" />
|
||||||
|
<path d="M0 0h24v24H0V0z" opacity=".87" />
|
||||||
|
</g>
|
||||||
|
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z" />
|
||||||
|
</svg>
|
||||||
|
<p />
|
||||||
|
<p>{constants.TWO_FACTOR_INFO}</p>
|
||||||
|
<div style={{ height: '10px' }} />
|
||||||
|
<Button
|
||||||
|
variant="outline-success"
|
||||||
|
onClick={() => router.push('/two-factor/setup')}>
|
||||||
|
{constants.ENABLE_TWO_FACTOR}
|
||||||
|
</Button>
|
||||||
|
</DeadCenter>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</MessageDialog >
|
</MessageDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default TwoFactorModal;
|
export default TwoFactorModal;
|
||||||
|
|
|
@ -79,19 +79,24 @@ function PlanSelector(props: Props) {
|
||||||
const [plans, setPlans] = useState(null);
|
const [plans, setPlans] = useState(null);
|
||||||
const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR);
|
const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR);
|
||||||
const togglePeriod = () => {
|
const togglePeriod = () => {
|
||||||
setPlanPeriod((prevPeriod) => (prevPeriod === PLAN_PERIOD.MONTH ?
|
setPlanPeriod((prevPeriod) =>
|
||||||
PLAN_PERIOD.YEAR :
|
prevPeriod === PLAN_PERIOD.MONTH
|
||||||
PLAN_PERIOD.MONTH));
|
? PLAN_PERIOD.YEAR
|
||||||
|
: PLAN_PERIOD.MONTH,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ( props.modalView) {
|
if (props.modalView) {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
props.setLoading(true);
|
props.setLoading(true);
|
||||||
let plans=await billingService.getPlans();
|
let plans = await billingService.getPlans();
|
||||||
|
|
||||||
const planNotListed= plans.filter((plan)=>isUserSubscribedPlan(plan, subscription)).length===0;
|
const planNotListed =
|
||||||
|
plans.filter((plan) =>
|
||||||
|
isUserSubscribedPlan(plan, subscription),
|
||||||
|
).length === 0;
|
||||||
if (!isOnFreePlan(subscription) && planNotListed) {
|
if (!isOnFreePlan(subscription) && planNotListed) {
|
||||||
plans=[planForSubscription(subscription), ...plans];
|
plans = [planForSubscription(subscription), ...plans];
|
||||||
}
|
}
|
||||||
setPlans(plans);
|
setPlans(plans);
|
||||||
props.setLoading(false);
|
props.setLoading(false);
|
||||||
|
@ -154,8 +159,7 @@ function PlanSelector(props: Props) {
|
||||||
key={plan.stripeID}
|
key={plan.stripeID}
|
||||||
className="subscription-plan-selector"
|
className="subscription-plan-selector"
|
||||||
selected={isUserSubscribedPlan(plan, subscription)}
|
selected={isUserSubscribedPlan(plan, subscription)}
|
||||||
onClick={async () => (await onPlanSelect(plan))}
|
onClick={async () => await onPlanSelect(plan)}>
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
@ -163,8 +167,7 @@ function PlanSelector(props: Props) {
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
fontSize: '40px',
|
fontSize: '40px',
|
||||||
lineHeight: '40px',
|
lineHeight: '40px',
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{convertBytesToGBs(plan.storage, 0)}
|
{convertBytesToGBs(plan.storage, 0)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -172,24 +175,30 @@ function PlanSelector(props: Props) {
|
||||||
color: '#858585',
|
color: '#858585',
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{' '}
|
{' '}
|
||||||
GB
|
GB
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="bold-text"
|
className="bold-text"
|
||||||
style={{ color: '#aaa', lineHeight: '36px', fontSize: '20px' }}
|
style={{
|
||||||
>
|
color: '#aaa',
|
||||||
|
lineHeight: '36px',
|
||||||
|
fontSize: '20px',
|
||||||
|
}}>
|
||||||
{`${plan.price} / ${plan.period}`}
|
{`${plan.price} / ${plan.period}`}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline-success"
|
variant="outline-success"
|
||||||
block
|
block
|
||||||
style={{ marginTop: '20px', fontSize: '14px', display: 'flex', justifyContent: 'center' }}
|
style={{
|
||||||
disabled={isUserSubscribedPlan(plan, subscription)}
|
marginTop: '20px',
|
||||||
>
|
fontSize: '14px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
disabled={isUserSubscribedPlan(plan, subscription)}>
|
||||||
{constants.CHOOSE_PLAN_BTN}
|
{constants.CHOOSE_PLAN_BTN}
|
||||||
<ArrowEast style={{ marginLeft: '5px' }} />
|
<ArrowEast style={{ marginLeft: '5px' }} />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -202,20 +211,18 @@ function PlanSelector(props: Props) {
|
||||||
size="xl"
|
size="xl"
|
||||||
centered
|
centered
|
||||||
backdrop={hasPaidSubscription(subscription) ? 'true' : 'static'}
|
backdrop={hasPaidSubscription(subscription) ? 'true' : 'static'}
|
||||||
contentClassName="plan-selector-modal-content"
|
contentClassName="plan-selector-modal-content">
|
||||||
>
|
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title
|
<Modal.Title
|
||||||
style={{
|
style={{
|
||||||
marginLeft: '12px',
|
marginLeft: '12px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<span>
|
<span>
|
||||||
{hasPaidSubscription(subscription) ?
|
{hasPaidSubscription(subscription)
|
||||||
constants.MANAGE_PLAN :
|
? constants.MANAGE_PLAN
|
||||||
constants.CHOOSE_PLAN}
|
: constants.CHOOSE_PLAN}
|
||||||
</span>
|
</span>
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
|
@ -224,22 +231,23 @@ function PlanSelector(props: Props) {
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<span
|
<span
|
||||||
className="bold-text"
|
className="bold-text"
|
||||||
style={{ fontSize: '16px' }}
|
style={{ fontSize: '16px' }}>
|
||||||
>
|
|
||||||
{constants.MONTHLY}
|
{constants.MONTHLY}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Form.Switch
|
<Form.Switch
|
||||||
checked={planPeriod === PLAN_PERIOD.YEAR}
|
checked={planPeriod === PLAN_PERIOD.YEAR}
|
||||||
id="plan-period-toggler"
|
id="plan-period-toggler"
|
||||||
style={{ margin: '-4px 0 20px 15px', fontSize: '10px' }}
|
style={{
|
||||||
|
margin: '-4px 0 20px 15px',
|
||||||
|
fontSize: '10px',
|
||||||
|
}}
|
||||||
className="custom-switch-md"
|
className="custom-switch-md"
|
||||||
onChange={togglePeriod}
|
onChange={togglePeriod}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="bold-text"
|
className="bold-text"
|
||||||
style={{ fontSize: '16px' }}
|
style={{ fontSize: '16px' }}>
|
||||||
>
|
|
||||||
{constants.YEARLY}
|
{constants.YEARLY}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -251,8 +259,7 @@ function PlanSelector(props: Props) {
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
minHeight: '212px',
|
minHeight: '212px',
|
||||||
margin: '5px 0',
|
margin: '5px 0',
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{plans && PlanIcons}
|
{plans && PlanIcons}
|
||||||
</div>
|
</div>
|
||||||
<DeadCenter style={{ marginBottom: '30px' }}>
|
<DeadCenter style={{ marginBottom: '30px' }}>
|
||||||
|
@ -261,55 +268,55 @@ function PlanSelector(props: Props) {
|
||||||
{isSubscriptionCancelled(subscription) ? (
|
{isSubscriptionCancelled(subscription) ? (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="success"
|
variant="success"
|
||||||
onClick={() => props.setDialogMessage({
|
onClick={() =>
|
||||||
title:
|
props.setDialogMessage({
|
||||||
constants.CONFIRM_ACTIVATE_SUBSCRIPTION,
|
title: constants.CONFIRM_ACTIVATE_SUBSCRIPTION,
|
||||||
content: constants.ACTIVATE_SUBSCRIPTION_MESSAGE(
|
content:
|
||||||
subscription.expiryTime,
|
constants.ACTIVATE_SUBSCRIPTION_MESSAGE(
|
||||||
),
|
subscription.expiryTime,
|
||||||
staticBackdrop: true,
|
),
|
||||||
proceed: {
|
staticBackdrop: true,
|
||||||
text:
|
proceed: {
|
||||||
constants.ACTIVATE_SUBSCRIPTION,
|
text: constants.ACTIVATE_SUBSCRIPTION,
|
||||||
action: activateSubscription.bind(
|
action: activateSubscription.bind(
|
||||||
null,
|
null,
|
||||||
props.setDialogMessage,
|
props.setDialogMessage,
|
||||||
props.closeModal,
|
props.closeModal,
|
||||||
props.setLoading,
|
props.setLoading,
|
||||||
),
|
),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
},
|
},
|
||||||
close: {
|
close: {
|
||||||
text: constants.CANCEL,
|
text: constants.CANCEL,
|
||||||
},
|
},
|
||||||
})}
|
})
|
||||||
>
|
}>
|
||||||
{constants.ACTIVATE_SUBSCRIPTION}
|
{constants.ACTIVATE_SUBSCRIPTION}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
) : (
|
) : (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={() => props.setDialogMessage({
|
onClick={() =>
|
||||||
title:
|
props.setDialogMessage({
|
||||||
constants.CONFIRM_CANCEL_SUBSCRIPTION,
|
title: constants.CONFIRM_CANCEL_SUBSCRIPTION,
|
||||||
content: constants.CANCEL_SUBSCRIPTION_MESSAGE(),
|
content:
|
||||||
staticBackdrop: true,
|
constants.CANCEL_SUBSCRIPTION_MESSAGE(),
|
||||||
proceed: {
|
staticBackdrop: true,
|
||||||
text:
|
proceed: {
|
||||||
constants.CANCEL_SUBSCRIPTION,
|
text: constants.CANCEL_SUBSCRIPTION,
|
||||||
action: cancelSubscription.bind(
|
action: cancelSubscription.bind(
|
||||||
null,
|
null,
|
||||||
props.setDialogMessage,
|
props.setDialogMessage,
|
||||||
props.closeModal,
|
props.closeModal,
|
||||||
props.setLoading,
|
props.setLoading,
|
||||||
),
|
),
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
},
|
},
|
||||||
close: {
|
close: {
|
||||||
text: constants.CANCEL,
|
text: constants.CANCEL,
|
||||||
},
|
},
|
||||||
})}
|
})
|
||||||
>
|
}>
|
||||||
{constants.CANCEL_SUBSCRIPTION}
|
{constants.CANCEL_SUBSCRIPTION}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
)}
|
)}
|
||||||
|
@ -320,8 +327,7 @@ function PlanSelector(props: Props) {
|
||||||
props.setDialogMessage,
|
props.setDialogMessage,
|
||||||
props.setLoading,
|
props.setLoading,
|
||||||
)}
|
)}
|
||||||
style={{ marginTop: '20px' }}
|
style={{ marginTop: '20px' }}>
|
||||||
>
|
|
||||||
{constants.MANAGEMENT_PORTAL}
|
{constants.MANAGEMENT_PORTAL}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</>
|
</>
|
||||||
|
@ -329,11 +335,13 @@ function PlanSelector(props: Props) {
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={props.closeModal}
|
onClick={props.closeModal}
|
||||||
style={{ color: 'rgb(121, 121, 121)', marginTop: '20px' }}
|
style={{
|
||||||
>
|
color: 'rgb(121, 121, 121)',
|
||||||
{isOnFreePlan(subscription) ?
|
marginTop: '20px',
|
||||||
constants.SKIP :
|
}}>
|
||||||
constants.CLOSE}
|
{isOnFreePlan(subscription)
|
||||||
|
? constants.SKIP
|
||||||
|
: constants.CLOSE}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
)}
|
)}
|
||||||
</DeadCenter>
|
</DeadCenter>
|
||||||
|
|
|
@ -397,22 +397,22 @@ export interface BannerMessage {
|
||||||
variant: string;
|
variant: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type AppContextType = {
|
type AppContextType = {
|
||||||
showNavBar: (show: boolean) => void;
|
showNavBar: (show: boolean) => void;
|
||||||
sharedFiles: File[];
|
sharedFiles: File[];
|
||||||
resetSharedFiles: () => void;
|
resetSharedFiles: () => void;
|
||||||
setDisappearingFlashMessage: (message: FlashMessage) => void;
|
setDisappearingFlashMessage: (message: FlashMessage) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface FlashMessage {
|
export interface FlashMessage {
|
||||||
message: string;
|
message: string;
|
||||||
severity: string
|
severity: string;
|
||||||
}
|
}
|
||||||
export const AppContext = createContext<AppContextType>(null);
|
export const AppContext = createContext<AppContextType>(null);
|
||||||
|
|
||||||
const redirectMap = {
|
const redirectMap = {
|
||||||
roadmap: (token: string) => `${getEndpoint()}/users/roadmap?token=${encodeURIComponent(token)}`,
|
roadmap: (token: string) =>
|
||||||
|
`${getEndpoint()}/users/roadmap?token=${encodeURIComponent(token)}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function App({ Component, err }) {
|
export default function App({ Component, err }) {
|
||||||
|
@ -486,7 +486,9 @@ export default function App({ Component, err }) {
|
||||||
if (redirectName) {
|
if (redirectName) {
|
||||||
const user = getData(LS_KEYS.USER);
|
const user = getData(LS_KEYS.USER);
|
||||||
if (user?.token) {
|
if (user?.token) {
|
||||||
window.location.href = redirectMap[redirectName](user.token);
|
window.location.href = redirectMap[redirectName](
|
||||||
|
user.token,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -513,24 +515,27 @@ export default function App({ Component, err }) {
|
||||||
<Head>
|
<Head>
|
||||||
<title>{constants.TITLE}</title>
|
<title>{constants.TITLE}</title>
|
||||||
{/* Cloudflare Web Analytics */}
|
{/* Cloudflare Web Analytics */}
|
||||||
{pageRootURL?.hostname && (pageRootURL.hostname === 'photos.ente.io' ?
|
{pageRootURL?.hostname &&
|
||||||
<script
|
(pageRootURL.hostname === 'photos.ente.io' ? (
|
||||||
defer
|
<script
|
||||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
|
||||||
data-cf-beacon='{"token": "6a388287b59c439cb2070f78cc89dde1"}'
|
|
||||||
/> : pageRootURL.hostname === 'web.ente.io' ?
|
|
||||||
< script
|
|
||||||
defer
|
defer
|
||||||
src='https://static.cloudflareinsights.com/beacon.min.js'
|
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||||
data-cf-beacon='{"token": "dfde128b7bb34a618ad34a08f1ba7609"}' /> :
|
data-cf-beacon='{"token": "6a388287b59c439cb2070f78cc89dde1"}'
|
||||||
|
/>
|
||||||
|
) : pageRootURL.hostname === 'web.ente.io' ? (
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||||
|
data-cf-beacon='{"token": "dfde128b7bb34a618ad34a08f1ba7609"}'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
console.warn('Web analytics is disabled')
|
console.warn('Web analytics is disabled')
|
||||||
)
|
))}
|
||||||
}
|
|
||||||
{/* End Cloudflare Web Analytics */}
|
{/* End Cloudflare Web Analytics */}
|
||||||
</Head>
|
</Head>
|
||||||
<GlobalStyles />
|
<GlobalStyles />
|
||||||
{
|
{showNavbar && (
|
||||||
showNavbar && <Navbar>
|
<Navbar>
|
||||||
<FlexContainer>
|
<FlexContainer>
|
||||||
<LogoImage
|
<LogoImage
|
||||||
style={{ height: '24px', padding: '3px' }}
|
style={{ height: '24px', padding: '3px' }}
|
||||||
|
@ -539,21 +544,33 @@ export default function App({ Component, err }) {
|
||||||
/>
|
/>
|
||||||
</FlexContainer>
|
</FlexContainer>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
}
|
)}
|
||||||
<MessageContainer>{offline && constants.OFFLINE_MSG}</MessageContainer>
|
<MessageContainer>
|
||||||
{
|
{offline && constants.OFFLINE_MSG}
|
||||||
sharedFiles &&
|
</MessageContainer>
|
||||||
(router.pathname === '/gallery' ?
|
{sharedFiles &&
|
||||||
<MessageContainer>{constants.FILES_TO_BE_UPLOADED(sharedFiles.length)}</MessageContainer> :
|
(router.pathname === '/gallery' ? (
|
||||||
<MessageContainer>{constants.LOGIN_TO_UPLOAD_FILES(sharedFiles.length)}</MessageContainer>)
|
<MessageContainer>
|
||||||
}
|
{constants.FILES_TO_BE_UPLOADED(sharedFiles.length)}
|
||||||
{flashMessage && <FlashMessageBar flashMessage={flashMessage} onClose={() => setFlashMessage(null)} />}
|
</MessageContainer>
|
||||||
<AppContext.Provider value={{
|
) : (
|
||||||
showNavBar,
|
<MessageContainer>
|
||||||
sharedFiles,
|
{constants.LOGIN_TO_UPLOAD_FILES(sharedFiles.length)}
|
||||||
resetSharedFiles,
|
</MessageContainer>
|
||||||
setDisappearingFlashMessage,
|
))}
|
||||||
}}>
|
{flashMessage && (
|
||||||
|
<FlashMessageBar
|
||||||
|
flashMessage={flashMessage}
|
||||||
|
onClose={() => setFlashMessage(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
showNavBar,
|
||||||
|
sharedFiles,
|
||||||
|
resetSharedFiles,
|
||||||
|
setDisappearingFlashMessage,
|
||||||
|
}}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Container>
|
<Container>
|
||||||
<EnteSpinner>
|
<EnteSpinner>
|
||||||
|
|
|
@ -9,15 +9,14 @@ import EnteSpinner from 'components/EnteSpinner';
|
||||||
import ChangeEmailForm from 'components/ChangeEmail';
|
import ChangeEmailForm from 'components/ChangeEmail';
|
||||||
import EnteCard from 'components/EnteCard';
|
import EnteCard from 'components/EnteCard';
|
||||||
|
|
||||||
|
|
||||||
function ChangeEmailPage() {
|
function ChangeEmailPage() {
|
||||||
const [email, setEmail]=useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [waiting, setWaiting]=useState(true);
|
const [waiting, setWaiting] = useState(true);
|
||||||
const [showMessage, setShowMessage]=useState(false);
|
const [showMessage, setShowMessage] = useState(false);
|
||||||
const [showBigDialog, setShowBigDialog]=useState(false);
|
const [showBigDialog, setShowBigDialog] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token=getToken();
|
const token = getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
router.push('/');
|
router.push('/');
|
||||||
return;
|
return;
|
||||||
|
@ -25,39 +24,40 @@ function ChangeEmailPage() {
|
||||||
setWaiting(false);
|
setWaiting(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>{waiting ?
|
<Container>
|
||||||
<EnteSpinner>
|
{waiting ? (
|
||||||
<span className="sr-only">Loading...</span>
|
<EnteSpinner>
|
||||||
</EnteSpinner>:
|
<span className="sr-only">Loading...</span>
|
||||||
<EnteCard size={showBigDialog?'md':'sm'}>
|
</EnteSpinner>
|
||||||
<Card.Body style={{ padding: '40px 30px' }}>
|
) : (
|
||||||
<Card.Title style={{ marginBottom: '32px' }}>
|
<EnteCard size={showBigDialog ? 'md' : 'sm'}>
|
||||||
<LogoImg src="/icon.svg" />
|
<Card.Body style={{ padding: '40px 30px' }}>
|
||||||
{constants.UPDATE_EMAIL}
|
<Card.Title style={{ marginBottom: '32px' }}>
|
||||||
</Card.Title>
|
<LogoImg src="/icon.svg" />
|
||||||
<Alert
|
{constants.UPDATE_EMAIL}
|
||||||
variant="success"
|
</Card.Title>
|
||||||
show={showMessage}
|
<Alert
|
||||||
style={{ paddingBottom: 0 }}
|
variant="success"
|
||||||
transition
|
show={showMessage}
|
||||||
dismissible
|
style={{ paddingBottom: 0 }}
|
||||||
onClose={()=>setShowMessage(false)}
|
transition
|
||||||
>
|
dismissible
|
||||||
{constants.EMAIL_SENT({ email })}
|
onClose={() => setShowMessage(false)}>
|
||||||
</Alert>
|
{constants.EMAIL_SENT({ email })}
|
||||||
<ChangeEmailForm
|
</Alert>
|
||||||
showMessage={(value)=>{
|
<ChangeEmailForm
|
||||||
setShowMessage(value);
|
showMessage={(value) => {
|
||||||
setShowBigDialog(value);
|
setShowMessage(value);
|
||||||
}}
|
setShowBigDialog(value);
|
||||||
setEmail={setEmail}
|
}}
|
||||||
/>
|
setEmail={setEmail}
|
||||||
</Card.Body>
|
/>
|
||||||
</EnteCard>
|
</Card.Body>
|
||||||
}
|
</EnteCard>
|
||||||
</Container>);
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChangeEmailPage;
|
export default ChangeEmailPage;
|
||||||
|
|
|
@ -45,7 +45,8 @@ export default function Generate() {
|
||||||
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
|
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const encryptedKeyAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(key, kek.key);
|
const encryptedKeyAttributes: B64EncryptionResult =
|
||||||
|
await cryptoWorker.encryptToB64(key, kek.key);
|
||||||
const updatedKey: UpdatedKey = {
|
const updatedKey: UpdatedKey = {
|
||||||
kekSalt,
|
kekSalt,
|
||||||
encryptedKey: encryptedKeyAttributes.encryptedData,
|
encryptedKey: encryptedKeyAttributes.encryptedData,
|
||||||
|
@ -75,9 +76,9 @@ export default function Generate() {
|
||||||
callback={onSubmit}
|
callback={onSubmit}
|
||||||
buttonText={constants.CHANGE_PASSWORD}
|
buttonText={constants.CHANGE_PASSWORD}
|
||||||
back={
|
back={
|
||||||
getData(LS_KEYS.SHOW_BACK_BUTTON)?.value ?
|
getData(LS_KEYS.SHOW_BACK_BUTTON)?.value
|
||||||
redirectToGallery :
|
? redirectToGallery
|
||||||
null
|
: null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,7 @@ const Container = styled.div`
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
|
|
||||||
@media(max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -33,7 +33,7 @@ const SlideContainer = styled.div`
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
@media(max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -47,7 +47,7 @@ const DesktopBox = styled.div`
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
|
|
||||||
@media(max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -55,7 +55,7 @@ const DesktopBox = styled.div`
|
||||||
const MobileBox = styled.div`
|
const MobileBox = styled.div`
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@media(max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 40px 10px;
|
padding: 40px 10px;
|
||||||
|
@ -91,7 +91,7 @@ const Img = styled.img`
|
||||||
height: 250px;
|
height: 250px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
@media(max-width: 400px) {
|
@media (max-width: 400px) {
|
||||||
height: 180px;
|
height: 180px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -123,56 +123,73 @@ export default function LandingPage() {
|
||||||
const signUp = () => setShowLogin(false);
|
const signUp = () => setShowLogin(false);
|
||||||
const login = () => setShowLogin(true);
|
const login = () => setShowLogin(true);
|
||||||
|
|
||||||
return <Container>
|
return (
|
||||||
{loading ? <EnteSpinner /> :
|
<Container>
|
||||||
(<>
|
{loading ? (
|
||||||
<SlideContainer>
|
<EnteSpinner />
|
||||||
<UpperText>
|
) : (
|
||||||
{constants.HERO_HEADER()}
|
<>
|
||||||
</UpperText>
|
<SlideContainer>
|
||||||
<Carousel controls={false}>
|
<UpperText>{constants.HERO_HEADER()}</UpperText>
|
||||||
<Carousel.Item>
|
<Carousel controls={false}>
|
||||||
<Img src="/images/slide-1.png" />
|
<Carousel.Item>
|
||||||
<FeatureText>{constants.HERO_SLIDE_1_TITLE}</FeatureText>
|
<Img src="/images/slide-1.png" />
|
||||||
<TextContainer>{constants.HERO_SLIDE_1}</TextContainer>
|
<FeatureText>
|
||||||
</Carousel.Item>
|
{constants.HERO_SLIDE_1_TITLE}
|
||||||
<Carousel.Item>
|
</FeatureText>
|
||||||
<Img src="/images/slide-2.png" />
|
<TextContainer>
|
||||||
<FeatureText>{constants.HERO_SLIDE_2_TITLE}</FeatureText>
|
{constants.HERO_SLIDE_1}
|
||||||
<TextContainer>{constants.HERO_SLIDE_2}</TextContainer>
|
</TextContainer>
|
||||||
</Carousel.Item>
|
</Carousel.Item>
|
||||||
<Carousel.Item>
|
<Carousel.Item>
|
||||||
<Img src="/images/slide-3.png" />
|
<Img src="/images/slide-2.png" />
|
||||||
<FeatureText>{constants.HERO_SLIDE_3_TITLE}</FeatureText>
|
<FeatureText>
|
||||||
<TextContainer>{constants.HERO_SLIDE_3}</TextContainer>
|
{constants.HERO_SLIDE_2_TITLE}
|
||||||
</Carousel.Item>
|
</FeatureText>
|
||||||
</Carousel>
|
<TextContainer>
|
||||||
</SlideContainer>
|
{constants.HERO_SLIDE_2}
|
||||||
<MobileBox>
|
</TextContainer>
|
||||||
<Button
|
</Carousel.Item>
|
||||||
variant="outline-success"
|
<Carousel.Item>
|
||||||
size="lg"
|
<Img src="/images/slide-3.png" />
|
||||||
style={{ color: '#fff', padding: '10px 50px' }}
|
<FeatureText>
|
||||||
onClick={() => router.push('signup')}
|
{constants.HERO_SLIDE_3_TITLE}
|
||||||
>
|
</FeatureText>
|
||||||
{constants.SIGN_UP}
|
<TextContainer>
|
||||||
</Button>
|
{constants.HERO_SLIDE_3}
|
||||||
<br />
|
</TextContainer>
|
||||||
<Button
|
</Carousel.Item>
|
||||||
variant="link"
|
</Carousel>
|
||||||
size="lg"
|
</SlideContainer>
|
||||||
style={{ color: '#fff', padding: '10px 50px' }}
|
<MobileBox>
|
||||||
onClick={() => router.push('login')}
|
<Button
|
||||||
>
|
variant="outline-success"
|
||||||
{constants.SIGN_IN}
|
size="lg"
|
||||||
</Button>
|
style={{ color: '#fff', padding: '10px 50px' }}
|
||||||
</MobileBox>
|
onClick={() => router.push('signup')}>
|
||||||
<DesktopBox>
|
{constants.SIGN_UP}
|
||||||
<SideBox>
|
</Button>
|
||||||
{showLogin ? <Login signUp={signUp} /> : <SignUp login={login} />}
|
<br />
|
||||||
</SideBox>
|
<Button
|
||||||
</DesktopBox>
|
variant="link"
|
||||||
{blockUsage && <IncognitoWarning />}
|
size="lg"
|
||||||
</>)}
|
style={{ color: '#fff', padding: '10px 50px' }}
|
||||||
</Container>;
|
onClick={() => router.push('login')}>
|
||||||
|
{constants.SIGN_IN}
|
||||||
|
</Button>
|
||||||
|
</MobileBox>
|
||||||
|
<DesktopBox>
|
||||||
|
<SideBox>
|
||||||
|
{showLogin ? (
|
||||||
|
<Login signUp={signUp} />
|
||||||
|
) : (
|
||||||
|
<SignUp login={login} />
|
||||||
|
)}
|
||||||
|
</SideBox>
|
||||||
|
</DesktopBox>
|
||||||
|
{blockUsage && <IncognitoWarning />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,19 @@ export default function Home() {
|
||||||
router.push('/signup');
|
router.push('/signup');
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Container>{loading ?
|
return (
|
||||||
<EnteSpinner>
|
<Container>
|
||||||
<span className="sr-only">Loading...</span>
|
{loading ? (
|
||||||
</EnteSpinner>:
|
<EnteSpinner>
|
||||||
<Card style={{ minWidth: '320px' }} className="text-center">
|
<span className="sr-only">Loading...</span>
|
||||||
<Card.Body style={{ padding: '40px 30px' }}>
|
</EnteSpinner>
|
||||||
<Login signUp={register}/>
|
) : (
|
||||||
</Card.Body>
|
<Card style={{ minWidth: '320px' }} className="text-center">
|
||||||
</Card>}
|
<Card.Body style={{ padding: '40px 30px' }}>
|
||||||
</Container>;
|
<Login signUp={register} />
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import EnteSpinner from 'components/EnteSpinner';
|
||||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import SignUp from 'components/SignUp';
|
import SignUp from 'components/SignUp';
|
||||||
|
|
||||||
|
|
||||||
export default function SignUpPage() {
|
export default function SignUpPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
|
@ -29,14 +28,16 @@ export default function SignUpPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>{
|
<Container>
|
||||||
loading ? <EnteSpinner /> :
|
{loading ? (
|
||||||
|
<EnteSpinner />
|
||||||
|
) : (
|
||||||
<Card style={{ minWidth: '320px' }} className="text-center">
|
<Card style={{ minWidth: '320px' }} className="text-center">
|
||||||
<Card.Body style={{ padding: '40px 30px' }}>
|
<Card.Body style={{ padding: '40px 30px' }}>
|
||||||
<SignUp login={login} />
|
<SignUp login={login} />
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import { CodeBlock, FreeFlowText } from 'components/RecoveryKeyModal';
|
||||||
import { DeadCenter } from 'pages/gallery';
|
import { DeadCenter } from 'pages/gallery';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { Button, Card } from 'react-bootstrap';
|
import { Button, Card } from 'react-bootstrap';
|
||||||
import { enableTwoFactor, setupTwoFactor, TwoFactorSecret } from 'services/userService';
|
import {
|
||||||
|
enableTwoFactor,
|
||||||
|
setupTwoFactor,
|
||||||
|
TwoFactorSecret,
|
||||||
|
} from 'services/userService';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
|
@ -15,22 +19,25 @@ import { encryptWithRecoveryKey } from 'utils/crypto';
|
||||||
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
||||||
import { AppContext } from 'pages/_app';
|
import { AppContext } from 'pages/_app';
|
||||||
|
|
||||||
|
|
||||||
enum SetupMode {
|
enum SetupMode {
|
||||||
QR_CODE,
|
QR_CODE,
|
||||||
MANUAL_CODE,
|
MANUAL_CODE,
|
||||||
}
|
}
|
||||||
|
|
||||||
const QRCode = styled.img`
|
const QRCode = styled.img`
|
||||||
height:200px;
|
height: 200px;
|
||||||
width:200px;
|
width: 200px;
|
||||||
margin:1rem;
|
margin: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function SetupTwoFactor() {
|
export default function SetupTwoFactor() {
|
||||||
const [setupMode, setSetupMode] = useState<SetupMode>(SetupMode.QR_CODE);
|
const [setupMode, setSetupMode] = useState<SetupMode>(SetupMode.QR_CODE);
|
||||||
const [twoFactorSecret, setTwoFactorSecret] = useState<TwoFactorSecret>(null);
|
const [twoFactorSecret, setTwoFactorSecret] =
|
||||||
const [recoveryEncryptedTwoFactorSecret, setRecoveryEncryptedTwoFactorSecret] = useState<B64EncryptionResult>(null);
|
useState<TwoFactorSecret>(null);
|
||||||
|
const [
|
||||||
|
recoveryEncryptedTwoFactorSecret,
|
||||||
|
setRecoveryEncryptedTwoFactorSecret,
|
||||||
|
] = useState<B64EncryptionResult>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,11 +47,17 @@ export default function SetupTwoFactor() {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
try {
|
try {
|
||||||
const twoFactorSecret = await setupTwoFactor();
|
const twoFactorSecret = await setupTwoFactor();
|
||||||
const recoveryEncryptedTwoFactorSecret = await encryptWithRecoveryKey(twoFactorSecret.secretCode);
|
const recoveryEncryptedTwoFactorSecret =
|
||||||
|
await encryptWithRecoveryKey(twoFactorSecret.secretCode);
|
||||||
setTwoFactorSecret(twoFactorSecret);
|
setTwoFactorSecret(twoFactorSecret);
|
||||||
setRecoveryEncryptedTwoFactorSecret(recoveryEncryptedTwoFactorSecret);
|
setRecoveryEncryptedTwoFactorSecret(
|
||||||
|
recoveryEncryptedTwoFactorSecret,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appContext.setDisappearingFlashMessage({ message: constants.TWO_FACTOR_SETUP_FAILED, severity: 'danger' });
|
appContext.setDisappearingFlashMessage({
|
||||||
|
message: constants.TWO_FACTOR_SETUP_FAILED,
|
||||||
|
severity: 'danger',
|
||||||
|
});
|
||||||
router.push('/gallery');
|
router.push('/gallery');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -52,8 +65,14 @@ export default function SetupTwoFactor() {
|
||||||
}, []);
|
}, []);
|
||||||
const onSubmit = async (otp: string) => {
|
const onSubmit = async (otp: string) => {
|
||||||
await enableTwoFactor(otp, recoveryEncryptedTwoFactorSecret);
|
await enableTwoFactor(otp, recoveryEncryptedTwoFactorSecret);
|
||||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), isTwoFactorEnabled: true });
|
setData(LS_KEYS.USER, {
|
||||||
appContext.setDisappearingFlashMessage({ message: constants.TWO_FACTOR_SETUP_SUCCESS, severity: 'info' });
|
...getData(LS_KEYS.USER),
|
||||||
|
isTwoFactorEnabled: true,
|
||||||
|
});
|
||||||
|
appContext.setDisappearingFlashMessage({
|
||||||
|
message: constants.TWO_FACTOR_SETUP_SUCCESS,
|
||||||
|
severity: 'info',
|
||||||
|
});
|
||||||
router.push('/gallery');
|
router.push('/gallery');
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
@ -62,35 +81,67 @@ export default function SetupTwoFactor() {
|
||||||
<Card.Body style={{ padding: '40px 30px', minHeight: '400px' }}>
|
<Card.Body style={{ padding: '40px 30px', minHeight: '400px' }}>
|
||||||
<DeadCenter>
|
<DeadCenter>
|
||||||
<Card.Title style={{ marginBottom: '32px' }}>
|
<Card.Title style={{ marginBottom: '32px' }}>
|
||||||
<LogoImg src='/icon.svg' />
|
<LogoImg src="/icon.svg" />
|
||||||
{constants.TWO_FACTOR}
|
{constants.TWO_FACTOR}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
{setupMode === SetupMode.QR_CODE ? (
|
{setupMode === SetupMode.QR_CODE ? (
|
||||||
<>
|
<>
|
||||||
<p>{constants.TWO_FACTOR_QR_INSTRUCTION}</p>
|
<p>{constants.TWO_FACTOR_QR_INSTRUCTION}</p>
|
||||||
<DeadCenter>
|
<DeadCenter>
|
||||||
{!twoFactorSecret ? <div style={{ height: '200px', width: '200px', margin: '1rem', display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #aaa' }}><EnteSpinner /></div> :
|
{!twoFactorSecret ? (
|
||||||
<QRCode src={`data:image/png;base64,${twoFactorSecret.qrCode}`} />
|
<div
|
||||||
}
|
style={{
|
||||||
<Button block variant="link" onClick={() => setSetupMode(SetupMode.MANUAL_CODE)}>
|
height: '200px',
|
||||||
|
width: '200px',
|
||||||
|
margin: '1rem',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
border: '1px solid #aaa',
|
||||||
|
}}>
|
||||||
|
<EnteSpinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<QRCode
|
||||||
|
src={`data:image/png;base64,${twoFactorSecret.qrCode}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant="link"
|
||||||
|
onClick={() =>
|
||||||
|
setSetupMode(SetupMode.MANUAL_CODE)
|
||||||
|
}>
|
||||||
{constants.ENTER_CODE_MANUALLY}
|
{constants.ENTER_CODE_MANUALLY}
|
||||||
</Button>
|
</Button>
|
||||||
</DeadCenter>
|
</DeadCenter>
|
||||||
</>
|
</>
|
||||||
) : (<>
|
) : (
|
||||||
<p>{constants.TWO_FACTOR_MANUAL_CODE_INSTRUCTION}</p>
|
<>
|
||||||
<CodeBlock height={100}>
|
<p>
|
||||||
{!twoFactorSecret ? <EnteSpinner /> : (
|
{
|
||||||
<FreeFlowText>
|
constants.TWO_FACTOR_MANUAL_CODE_INSTRUCTION
|
||||||
{twoFactorSecret.secretCode}
|
}
|
||||||
</FreeFlowText>
|
</p>
|
||||||
|
<CodeBlock height={100}>
|
||||||
)}
|
{!twoFactorSecret ? (
|
||||||
</CodeBlock>
|
<EnteSpinner />
|
||||||
<Button block variant="link" style={{ marginBottom: '1rem' }} onClick={() => setSetupMode(SetupMode.QR_CODE)}>
|
) : (
|
||||||
{constants.SCAN_QR_CODE}
|
<FreeFlowText>
|
||||||
</Button>
|
{twoFactorSecret.secretCode}
|
||||||
</>
|
</FreeFlowText>
|
||||||
|
)}
|
||||||
|
</CodeBlock>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
variant="link"
|
||||||
|
style={{ marginBottom: '1rem' }}
|
||||||
|
onClick={() =>
|
||||||
|
setSetupMode(SetupMode.QR_CODE)
|
||||||
|
}>
|
||||||
|
{constants.SCAN_QR_CODE}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -99,13 +150,20 @@ export default function SetupTwoFactor() {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<VerifyTwoFactor onSubmit={onSubmit} back={router.back} buttonText={constants.ENABLE} />
|
<VerifyTwoFactor
|
||||||
<Button style={{ marginTop: '16px' }} variant="link-danger" onClick={router.back}>
|
onSubmit={onSubmit}
|
||||||
|
back={router.back}
|
||||||
|
buttonText={constants.ENABLE}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
style={{ marginTop: '16px' }}
|
||||||
|
variant="link-danger"
|
||||||
|
onClick={router.back}>
|
||||||
{constants.GO_BACK}
|
{constants.GO_BACK}
|
||||||
</Button>
|
</Button>
|
||||||
</DeadCenter>
|
</DeadCenter>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Container >
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import { retryAsyncFunction, runningInBrowser } from 'utils/common';
|
import { runningInBrowser } from 'utils/common';
|
||||||
import { getExportPendingFiles, getExportFailedFiles, getFilesUploadedAfterLastExport, getFileUID, dedupe, getGoogleLikeMetadataFile } from 'utils/export';
|
import {
|
||||||
|
getExportPendingFiles,
|
||||||
|
getExportFailedFiles,
|
||||||
|
getFilesUploadedAfterLastExport,
|
||||||
|
getFileUID,
|
||||||
|
dedupe,
|
||||||
|
getGoogleLikeMetadataFile,
|
||||||
|
} from 'utils/export';
|
||||||
|
import { retryAsyncFunction } from 'utils/network';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { Collection, getLocalCollections } from './collectionService';
|
import { Collection, getLocalCollections } from './collectionService';
|
||||||
|
@ -16,7 +24,7 @@ export interface ExportStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExportRecord {
|
export interface ExportRecord {
|
||||||
stage: ExportStage
|
stage: ExportStage;
|
||||||
lastAttemptTimestamp: number;
|
lastAttemptTimestamp: number;
|
||||||
progress: ExportProgress;
|
progress: ExportProgress;
|
||||||
queuedFiles: string[];
|
queuedFiles: string[];
|
||||||
|
@ -27,7 +35,7 @@ export enum ExportStage {
|
||||||
INIT,
|
INIT,
|
||||||
INPROGRESS,
|
INPROGRESS,
|
||||||
PAUSED,
|
PAUSED,
|
||||||
FINISHED
|
FINISHED,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExportNotification {
|
enum ExportNotification {
|
||||||
|
@ -37,26 +45,26 @@ enum ExportNotification {
|
||||||
FAILED = 'export failed',
|
FAILED = 'export failed',
|
||||||
ABORT = 'export aborted',
|
ABORT = 'export aborted',
|
||||||
PAUSE = 'export paused',
|
PAUSE = 'export paused',
|
||||||
UP_TO_DATE = `no new files to export`
|
UP_TO_DATE = `no new files to export`,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RecordType {
|
enum RecordType {
|
||||||
SUCCESS = 'success',
|
SUCCESS = 'success',
|
||||||
FAILED = 'failed'
|
FAILED = 'failed',
|
||||||
}
|
}
|
||||||
export enum ExportType {
|
export enum ExportType {
|
||||||
NEW,
|
NEW,
|
||||||
PENDING,
|
PENDING,
|
||||||
RETRY_FAILED
|
RETRY_FAILED,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExportRecordFileName='export_status.json';
|
const ExportRecordFileName = 'export_status.json';
|
||||||
const MetadataFolderName='metadata';
|
const MetadataFolderName = 'metadata';
|
||||||
|
|
||||||
class ExportService {
|
class ExportService {
|
||||||
ElectronAPIs: any;
|
ElectronAPIs: any;
|
||||||
|
|
||||||
private exportInProgress: Promise<{ paused: boolean; }> = null;
|
private exportInProgress: Promise<{ paused: boolean }> = null;
|
||||||
private recordUpdateInProgress = Promise.resolve();
|
private recordUpdateInProgress = Promise.resolve();
|
||||||
private stopExport: boolean = false;
|
private stopExport: boolean = false;
|
||||||
private pauseExport: boolean = false;
|
private pauseExport: boolean = false;
|
||||||
|
@ -73,7 +81,10 @@ class ExportService {
|
||||||
pauseRunningExport() {
|
pauseRunningExport() {
|
||||||
this.pauseExport = true;
|
this.pauseExport = true;
|
||||||
}
|
}
|
||||||
async exportFiles(updateProgress: (progress: ExportProgress) => void, exportType: ExportType) {
|
async exportFiles(
|
||||||
|
updateProgress: (progress: ExportProgress) => void,
|
||||||
|
exportType: ExportType,
|
||||||
|
) {
|
||||||
if (this.exportInProgress) {
|
if (this.exportInProgress) {
|
||||||
this.ElectronAPIs.sendNotification(ExportNotification.IN_PROGRESS);
|
this.ElectronAPIs.sendNotification(ExportNotification.IN_PROGRESS);
|
||||||
return this.exportInProgress;
|
return this.exportInProgress;
|
||||||
|
@ -90,22 +101,37 @@ class ExportService {
|
||||||
const exportRecord = await this.getExportRecord(exportDir);
|
const exportRecord = await this.getExportRecord(exportDir);
|
||||||
|
|
||||||
if (exportType === ExportType.NEW) {
|
if (exportType === ExportType.NEW) {
|
||||||
filesToExport = await getFilesUploadedAfterLastExport(allFiles, exportRecord);
|
filesToExport = await getFilesUploadedAfterLastExport(
|
||||||
|
allFiles,
|
||||||
|
exportRecord,
|
||||||
|
);
|
||||||
} else if (exportType === ExportType.RETRY_FAILED) {
|
} else if (exportType === ExportType.RETRY_FAILED) {
|
||||||
filesToExport = await getExportFailedFiles(allFiles, exportRecord);
|
filesToExport = await getExportFailedFiles(allFiles, exportRecord);
|
||||||
} else {
|
} else {
|
||||||
filesToExport = await getExportPendingFiles(allFiles, exportRecord);
|
filesToExport = await getExportPendingFiles(allFiles, exportRecord);
|
||||||
}
|
}
|
||||||
this.exportInProgress = this.fileExporter(filesToExport, collections, updateProgress, exportDir);
|
this.exportInProgress = this.fileExporter(
|
||||||
|
filesToExport,
|
||||||
|
collections,
|
||||||
|
updateProgress,
|
||||||
|
exportDir,
|
||||||
|
);
|
||||||
const resp = await this.exportInProgress;
|
const resp = await this.exportInProgress;
|
||||||
this.exportInProgress = null;
|
this.exportInProgress = null;
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fileExporter(files: File[], collections: Collection[], updateProgress: (progress: ExportProgress,) => void, dir: string): Promise<{ paused: boolean }> {
|
async fileExporter(
|
||||||
|
files: File[],
|
||||||
|
collections: Collection[],
|
||||||
|
updateProgress: (progress: ExportProgress) => void,
|
||||||
|
dir: string,
|
||||||
|
): Promise<{ paused: boolean }> {
|
||||||
try {
|
try {
|
||||||
if (!files?.length) {
|
if (!files?.length) {
|
||||||
this.ElectronAPIs.sendNotification(ExportNotification.UP_TO_DATE);
|
this.ElectronAPIs.sendNotification(
|
||||||
|
ExportNotification.UP_TO_DATE,
|
||||||
|
);
|
||||||
return { paused: false };
|
return { paused: false };
|
||||||
}
|
}
|
||||||
this.stopExport = false;
|
this.stopExport = false;
|
||||||
|
@ -114,17 +140,19 @@ class ExportService {
|
||||||
const failedFileCount = 0;
|
const failedFileCount = 0;
|
||||||
|
|
||||||
this.ElectronAPIs.showOnTray({
|
this.ElectronAPIs.showOnTray({
|
||||||
export_progress:
|
export_progress: `0 / ${files.length} files exported`,
|
||||||
`0 / ${files.length} files exported`,
|
|
||||||
});
|
});
|
||||||
updateProgress({
|
updateProgress({
|
||||||
current: 0, total: files.length,
|
current: 0,
|
||||||
|
total: files.length,
|
||||||
});
|
});
|
||||||
this.ElectronAPIs.sendNotification(ExportNotification.START);
|
this.ElectronAPIs.sendNotification(ExportNotification.START);
|
||||||
|
|
||||||
const collectionIDMap = new Map<number, string>();
|
const collectionIDMap = new Map<number, string>();
|
||||||
for (const collection of collections) {
|
for (const collection of collections) {
|
||||||
const collectionFolderPath = `${dir}/${collection.id}_${this.sanitizeName(collection.name)}`;
|
const collectionFolderPath = `${dir}/${
|
||||||
|
collection.id
|
||||||
|
}_${this.sanitizeName(collection.name)}`;
|
||||||
await this.ElectronAPIs.checkExistsAndCreateCollectionDir(
|
await this.ElectronAPIs.checkExistsAndCreateCollectionDir(
|
||||||
collectionFolderPath,
|
collectionFolderPath,
|
||||||
);
|
);
|
||||||
|
@ -137,8 +165,7 @@ class ExportService {
|
||||||
if (this.stopExport || this.pauseExport) {
|
if (this.stopExport || this.pauseExport) {
|
||||||
if (this.pauseExport) {
|
if (this.pauseExport) {
|
||||||
this.ElectronAPIs.showOnTray({
|
this.ElectronAPIs.showOnTray({
|
||||||
export_progress:
|
export_progress: `${index} / ${files.length} files exported (paused)`,
|
||||||
`${index} / ${files.length} files exported (paused)`,
|
|
||||||
paused: true,
|
paused: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -147,39 +174,42 @@ class ExportService {
|
||||||
const collectionPath = collectionIDMap.get(file.collectionID);
|
const collectionPath = collectionIDMap.get(file.collectionID);
|
||||||
try {
|
try {
|
||||||
await this.downloadAndSave(file, collectionPath);
|
await this.downloadAndSave(file, collectionPath);
|
||||||
await this.addFileExportRecord(dir, file, RecordType.SUCCESS);
|
await this.addFileExportRecord(
|
||||||
|
dir,
|
||||||
|
file,
|
||||||
|
RecordType.SUCCESS,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.addFileExportRecord(dir, file, RecordType.FAILED);
|
await this.addFileExportRecord(
|
||||||
logError(e, 'download and save failed for file during export');
|
dir,
|
||||||
|
file,
|
||||||
|
RecordType.FAILED,
|
||||||
|
);
|
||||||
|
logError(
|
||||||
|
e,
|
||||||
|
'download and save failed for file during export',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.ElectronAPIs.showOnTray({
|
this.ElectronAPIs.showOnTray({
|
||||||
export_progress:
|
export_progress: `${index + 1} / ${
|
||||||
`${index + 1} / ${files.length} files exported`,
|
files.length
|
||||||
|
} files exported`,
|
||||||
});
|
});
|
||||||
updateProgress({ current: index + 1, total: files.length });
|
updateProgress({ current: index + 1, total: files.length });
|
||||||
}
|
}
|
||||||
if (this.stopExport) {
|
if (this.stopExport) {
|
||||||
this.ElectronAPIs.sendNotification(
|
this.ElectronAPIs.sendNotification(ExportNotification.ABORT);
|
||||||
ExportNotification.ABORT,
|
|
||||||
);
|
|
||||||
this.ElectronAPIs.showOnTray();
|
this.ElectronAPIs.showOnTray();
|
||||||
} else if (this.pauseExport) {
|
} else if (this.pauseExport) {
|
||||||
this.ElectronAPIs.sendNotification(
|
this.ElectronAPIs.sendNotification(ExportNotification.PAUSE);
|
||||||
ExportNotification.PAUSE,
|
|
||||||
);
|
|
||||||
return { paused: true };
|
return { paused: true };
|
||||||
} else if (failedFileCount > 0) {
|
} else if (failedFileCount > 0) {
|
||||||
this.ElectronAPIs.sendNotification(
|
this.ElectronAPIs.sendNotification(ExportNotification.FAILED);
|
||||||
ExportNotification.FAILED,
|
|
||||||
);
|
|
||||||
this.ElectronAPIs.showOnTray({
|
this.ElectronAPIs.showOnTray({
|
||||||
retry_export:
|
retry_export: `export failed - retry export`,
|
||||||
`export failed - retry export`,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.ElectronAPIs.sendNotification(
|
this.ElectronAPIs.sendNotification(ExportNotification.FINISH);
|
||||||
ExportNotification.FINISH,
|
|
||||||
);
|
|
||||||
this.ElectronAPIs.showOnTray();
|
this.ElectronAPIs.showOnTray();
|
||||||
}
|
}
|
||||||
return { paused: false };
|
return { paused: false };
|
||||||
|
@ -196,13 +226,18 @@ class ExportService {
|
||||||
async addFileExportRecord(folder: string, file: File, type: RecordType) {
|
async addFileExportRecord(folder: string, file: File, type: RecordType) {
|
||||||
const fileUID = getFileUID(file);
|
const fileUID = getFileUID(file);
|
||||||
const exportRecord = await this.getExportRecord(folder);
|
const exportRecord = await this.getExportRecord(folder);
|
||||||
exportRecord.queuedFiles = exportRecord.queuedFiles.filter((queuedFilesUID) => queuedFilesUID !== fileUID);
|
exportRecord.queuedFiles = exportRecord.queuedFiles.filter(
|
||||||
|
(queuedFilesUID) => queuedFilesUID !== fileUID,
|
||||||
|
);
|
||||||
if (type === RecordType.SUCCESS) {
|
if (type === RecordType.SUCCESS) {
|
||||||
if (!exportRecord.exportedFiles) {
|
if (!exportRecord.exportedFiles) {
|
||||||
exportRecord.exportedFiles = [];
|
exportRecord.exportedFiles = [];
|
||||||
}
|
}
|
||||||
exportRecord.exportedFiles.push(fileUID);
|
exportRecord.exportedFiles.push(fileUID);
|
||||||
exportRecord.failedFiles && (exportRecord.failedFiles = exportRecord.failedFiles.filter((FailedFileUID) => FailedFileUID !== fileUID));
|
exportRecord.failedFiles &&
|
||||||
|
(exportRecord.failedFiles = exportRecord.failedFiles.filter(
|
||||||
|
(FailedFileUID) => FailedFileUID !== fileUID,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
if (!exportRecord.failedFiles) {
|
if (!exportRecord.failedFiles) {
|
||||||
exportRecord.failedFiles = [];
|
exportRecord.failedFiles = [];
|
||||||
|
@ -226,7 +261,10 @@ class ExportService {
|
||||||
}
|
}
|
||||||
const exportRecord = await this.getExportRecord(folder);
|
const exportRecord = await this.getExportRecord(folder);
|
||||||
const newRecord = { ...exportRecord, ...newData };
|
const newRecord = { ...exportRecord, ...newData };
|
||||||
await this.ElectronAPIs.setExportRecord(`${folder}/${ExportRecordFileName}`, JSON.stringify(newRecord, null, 2));
|
await this.ElectronAPIs.setExportRecord(
|
||||||
|
`${folder}/${ExportRecordFileName}`,
|
||||||
|
JSON.stringify(newRecord, null, 2),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e, 'error updating Export Record');
|
logError(e, 'error updating Export Record');
|
||||||
}
|
}
|
||||||
|
@ -239,7 +277,9 @@ class ExportService {
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
folder = getData(LS_KEYS.EXPORT)?.folder;
|
folder = getData(LS_KEYS.EXPORT)?.folder;
|
||||||
}
|
}
|
||||||
const recordFile = await this.ElectronAPIs.getExportRecord(`${folder}/${ExportRecordFileName}`);
|
const recordFile = await this.ElectronAPIs.getExportRecord(
|
||||||
|
`${folder}/${ExportRecordFileName}`,
|
||||||
|
);
|
||||||
if (recordFile) {
|
if (recordFile) {
|
||||||
return JSON.parse(recordFile);
|
return JSON.parse(recordFile);
|
||||||
} else {
|
} else {
|
||||||
|
@ -250,12 +290,15 @@ class ExportService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadAndSave(file: File, collectionPath:string) {
|
async downloadAndSave(file: File, collectionPath: string) {
|
||||||
const uid = `${file.id}_${this.sanitizeName(
|
const uid = `${file.id}_${this.sanitizeName(file.metadata.title)}`;
|
||||||
file.metadata.title,
|
const fileStream = await retryAsyncFunction(() =>
|
||||||
)}`;
|
downloadManager.downloadFile(file),
|
||||||
const fileStream = await retryAsyncFunction(()=>downloadManager.downloadFile(file));
|
);
|
||||||
this.ElectronAPIs.saveStreamToDisk(`${collectionPath}/${uid}`, fileStream);
|
this.ElectronAPIs.saveStreamToDisk(
|
||||||
|
`${collectionPath}/${uid}`,
|
||||||
|
fileStream,
|
||||||
|
);
|
||||||
this.ElectronAPIs.saveFileToDisk(
|
this.ElectronAPIs.saveFileToDisk(
|
||||||
`${collectionPath}/${MetadataFolderName}/${uid}.json`,
|
`${collectionPath}/${MetadataFolderName}/${uid}.json`,
|
||||||
getGoogleLikeMetadataFile(uid, file.metadata),
|
getGoogleLikeMetadataFile(uid, file.metadata),
|
||||||
|
@ -268,6 +311,6 @@ class ExportService {
|
||||||
|
|
||||||
isExportInProgress = () => {
|
isExportInProgress = () => {
|
||||||
return this.exportInProgress !== null;
|
return this.exportInProgress !== null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export default new ExportService();
|
export default new ExportService();
|
||||||
|
|
|
@ -30,8 +30,11 @@ async function encryptFileStream(worker, fileData: DataStream) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encryptFiledata(worker, filedata:Uint8Array | DataStream) :Promise<EncryptionResult> {
|
export async function encryptFiledata(
|
||||||
return isDataStream(filedata) ?
|
worker,
|
||||||
await encryptFileStream(worker, filedata) :
|
filedata: Uint8Array | DataStream,
|
||||||
await worker.encryptFile(filedata);
|
): Promise<EncryptionResult> {
|
||||||
|
return isDataStream(filedata)
|
||||||
|
? await encryptFileStream(worker, filedata)
|
||||||
|
: await worker.encryptFile(filedata);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ import HTTPService from 'services/HTTPService';
|
||||||
import { getEndpoint } from 'utils/common/apiUtil';
|
import { getEndpoint } from 'utils/common/apiUtil';
|
||||||
import { getToken } from 'utils/common/key';
|
import { getToken } from 'utils/common/key';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { MultipartUploadURLs, UploadFile, UploadURL } from './uploadService';
|
import { UploadFile, UploadURL } from './uploadService';
|
||||||
import { File } from '../fileService';
|
import { File } from '../fileService';
|
||||||
import { CustomError } from 'utils/common/errorUtil';
|
import { CustomError } from 'utils/common/errorUtil';
|
||||||
import { retryAsyncFunction } from 'utils/network';
|
import { retryAsyncFunction } from 'utils/network';
|
||||||
|
import { MultipartUploadURLs } from './s3Service';
|
||||||
|
|
||||||
const ENDPOINT = getEndpoint();
|
const ENDPOINT = getEndpoint();
|
||||||
const MAX_URL_REQUESTS = 50;
|
const MAX_URL_REQUESTS = 50;
|
||||||
|
@ -116,7 +117,7 @@ class NetworkClient {
|
||||||
filePart,
|
filePart,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
progressTracker(),
|
progressTracker,
|
||||||
);
|
);
|
||||||
if (!resp?.headers?.etag) {
|
if (!resp?.headers?.etag) {
|
||||||
const err = Error(CustomError.ETAG_MISSING);
|
const err = Error(CustomError.ETAG_MISSING);
|
||||||
|
|
|
@ -29,7 +29,8 @@ export async function decryptChaCha(
|
||||||
header,
|
header,
|
||||||
await fromB64(key),
|
await fromB64(key),
|
||||||
);
|
);
|
||||||
const decryptionChunkSize = ENCRYPTION_CHUNK_SIZE +
|
const decryptionChunkSize =
|
||||||
|
ENCRYPTION_CHUNK_SIZE +
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
||||||
let bytesRead = 0;
|
let bytesRead = 0;
|
||||||
const decryptedData = [];
|
const decryptedData = [];
|
||||||
|
@ -59,7 +60,8 @@ export async function initChunkDecryption(header: Uint8Array, key: Uint8Array) {
|
||||||
header,
|
header,
|
||||||
key,
|
key,
|
||||||
);
|
);
|
||||||
const decryptionChunkSize = ENCRYPTION_CHUNK_SIZE +
|
const decryptionChunkSize =
|
||||||
|
ENCRYPTION_CHUNK_SIZE +
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
|
||||||
const tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
const tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
||||||
return { pullState, decryptionChunkSize, tag };
|
return { pullState, decryptionChunkSize, tag };
|
||||||
|
@ -78,12 +80,11 @@ export async function decryptChunk(data: Uint8Array, pullState: StateAddress) {
|
||||||
export async function encryptChaChaOneShot(data: Uint8Array, key?: string) {
|
export async function encryptChaChaOneShot(data: Uint8Array, key?: string) {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
|
|
||||||
const uintkey: Uint8Array = key ?
|
const uintkey: Uint8Array = key
|
||||||
await fromB64(key) :
|
? await fromB64(key)
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
||||||
const initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(
|
const initPushResult =
|
||||||
uintkey,
|
sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
|
||||||
);
|
|
||||||
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
||||||
|
|
||||||
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
|
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
|
||||||
|
@ -104,13 +105,12 @@ export async function encryptChaChaOneShot(data: Uint8Array, key?: string) {
|
||||||
export async function encryptChaCha(data: Uint8Array, key?: string) {
|
export async function encryptChaCha(data: Uint8Array, key?: string) {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
|
|
||||||
const uintkey: Uint8Array = key ?
|
const uintkey: Uint8Array = key
|
||||||
await fromB64(key) :
|
? await fromB64(key)
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
||||||
|
|
||||||
const initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(
|
const initPushResult =
|
||||||
uintkey,
|
sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
|
||||||
);
|
|
||||||
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
||||||
let bytesRead = 0;
|
let bytesRead = 0;
|
||||||
let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
||||||
|
@ -148,9 +148,8 @@ export async function encryptChaCha(data: Uint8Array, key?: string) {
|
||||||
export async function initChunkEncryption() {
|
export async function initChunkEncryption() {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
|
||||||
const initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(
|
const initPushResult =
|
||||||
key,
|
sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
|
||||||
);
|
|
||||||
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
const [pushState, header] = [initPushResult.state, initPushResult.header];
|
||||||
return {
|
return {
|
||||||
key: await toB64(key),
|
key: await toB64(key),
|
||||||
|
@ -165,9 +164,9 @@ export async function encryptFileChunk(
|
||||||
) {
|
) {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
|
|
||||||
const tag = finalChunk ?
|
const tag = finalChunk
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL :
|
? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
|
||||||
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
: sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
|
||||||
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
|
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
|
||||||
pushState,
|
pushState,
|
||||||
data,
|
data,
|
||||||
|
|
Loading…
Reference in a new issue