fix build

This commit is contained in:
Abhinav-grd 2021-08-11 13:30:59 +05:30
parent a6652c105a
commit d65f6cb494
19 changed files with 1034 additions and 629 deletions

View file

@ -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>

View file

@ -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>
</> </>
); );

View file

@ -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>
</> </>

View file

@ -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>
); );
} }

View file

@ -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>
)} )}

View file

@ -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>
); );
} }

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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
} }
/> />
); );

View file

@ -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>
);
} }

View file

@ -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>
);
} }

View file

@ -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>
); );
} }

View file

@ -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>
); );
} }

View file

@ -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();

View file

@ -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);
} }

View file

@ -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);

View file

@ -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,