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