ente/src/components/ExportModal.tsx

280 lines
11 KiB
TypeScript
Raw Normal View History

2021-07-13 13:39:31 +00:00
import isElectron from 'is-electron';
2021-07-07 05:31:48 +00:00
import React, { useEffect, useState } from 'react';
2021-07-07 09:20:59 +00:00
import { Button } from 'react-bootstrap';
import exportService, { ExportProgress, ExportRecord, ExportStage, ExportStats } from 'services/exportService';
2021-07-08 05:44:54 +00:00
import styled from 'styled-components';
2021-07-14 04:33:28 +00:00
import { sleep } from 'utils/common';
2021-07-07 05:31:48 +00:00
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
2021-07-07 09:20:59 +00:00
import constants from 'utils/strings/constants';
import { Label, Row, Value } from './Container';
2021-07-06 11:23:13 +00:00
import ExportFinished from './ExportFinished';
2021-07-06 09:20:05 +00:00
import ExportInit from './ExportInit';
import ExportInProgress from './ExportInProgress';
2021-07-07 09:20:59 +00:00
import FolderIcon from './icons/FolderIcon';
import InProgressIcon from './icons/InProgressIcon';
import MessageDialog from './MessageDialog';
2021-07-06 09:20:05 +00:00
2021-07-08 05:44:54 +00:00
const FolderIconWrapper = styled.div`
width: 15%;
margin-left: 10px;
cursor: pointer;
padding: 3px;
border: 1px solid #444;
2021-07-12 07:21:52 +00:00
border-radius:15%;
2021-07-08 05:44:54 +00:00
&:hover{
background-color:#444;
}
`;
2021-07-06 09:20:05 +00:00
2021-07-07 05:31:48 +00:00
interface Props {
show: boolean
onHide: () => void
2021-07-07 07:45:55 +00:00
usage: string
}
2021-07-07 05:31:48 +00:00
export default function ExportModal(props: Props) {
2021-07-08 06:42:28 +00:00
const [exportStage, setExportStage] = useState(ExportStage.INIT);
2021-07-13 13:39:31 +00:00
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 });
2021-07-07 07:45:55 +00:00
const [lastExportTime, setLastExportTime] = useState(0);
2021-07-07 09:20:59 +00:00
2021-07-07 05:31:48 +00:00
useEffect(() => {
2021-07-13 13:39:31 +00:00
if (!isElectron()) {
return;
}
setExportFolder(getData(LS_KEYS.EXPORT)?.folder);
2021-07-13 13:39:31 +00:00
2021-07-09 03:35:33 +00:00
exportService.ElectronAPIs.registerStopExportListener(stopExport);
exportService.ElectronAPIs.registerPauseExportListener(pauseExport);
exportService.ElectronAPIs.registerResumeExportListener(resumeExport);
exportService.ElectronAPIs.registerRetryFailedExportListener(retryFailedExport);
2021-07-07 05:31:48 +00:00
}, []);
2021-07-07 07:45:55 +00:00
useEffect(() => {
2021-07-13 13:39:31 +00:00
const main = async () => {
const exportInfo = await exportService.getExportRecord();
setExportStage(exportInfo?.stage ?? ExportStage.INIT);
setLastExportTime(exportInfo?.lastAttemptTimestamp);
setExportProgress(exportInfo?.progress ?? { current: 0, total: 0 });
console.log({ success: exportInfo?.exportedFiles?.length ?? 0, failed: exportInfo?.failedFiles?.length ?? 0 });
setExportStats({ success: exportInfo?.exportedFiles?.length ?? 0, failed: exportInfo?.failedFiles?.length ?? 0 });
2021-07-13 13:39:31 +00:00
if (exportInfo?.stage === ExportStage.INPROGRESS) {
startExport();
}
};
main();
}, [exportFolder]);
2021-07-07 09:20:59 +00:00
useEffect(() => {
setExportSize(props.usage);
}, [props.usage]);
const updateExportFolder = (newFolder: string) => {
2021-07-07 05:31:48 +00:00
setExportFolder(newFolder);
setData(LS_KEYS.EXPORT, { folder: newFolder });
2021-07-07 05:31:48 +00:00
};
const updateExportStage = (newStage: ExportStage) => {
2021-07-09 03:48:19 +00:00
setExportStage(newStage);
2021-07-13 13:39:31 +00:00
exportService.updateExportRecord({ stage: newStage });
2021-07-07 07:45:55 +00:00
};
const updateExportTime = (newTime: number) => {
2021-07-07 07:45:55 +00:00
setLastExportTime(newTime);
2021-07-13 13:39:31 +00:00
exportService.updateExportRecord({ time: newTime });
2021-07-07 07:45:55 +00:00
};
const updateExportProgress = (newProgress: ExportProgress) => {
setExportProgress(newProgress);
exportService.updateExportRecord({ progress: newProgress });
2021-07-09 03:48:19 +00:00
};
const startExport = async () => {
const exportFolder = getData(LS_KEYS.EXPORT)?.folder;
if (!exportFolder) {
const folderSelected = await selectExportDirectory();
if (!folderSelected) {
// no-op as select folder aborted
return;
}
}
2021-07-08 06:42:28 +00:00
updateExportStage(ExportStage.INPROGRESS);
updateExportProgress({ current: 0, total: 0 });
const { paused } = await exportService.exportFiles(updateExportProgress);
2021-07-14 07:21:32 +00:00
if (!paused) {
updateExportStage(ExportStage.FINISHED);
await sleep(100);
updateExportTime(Date.now());
}
};
const retryFailedExport = async () => {
const exportFolder = getData(LS_KEYS.EXPORT)?.folder;
if (!exportFolder) {
const folderSelected = await selectExportDirectory();
if (!folderSelected) {
// no-op as select folder aborted
return;
}
}
updateExportStage(ExportStage.INPROGRESS);
updateExportProgress({ current: 0, total: 0 });
const finished = await exportService.retryFailedFiles(updateExportProgress);
if (finished) {
updateExportStage(ExportStage.FINISHED);
await sleep(100);
updateExportTime(Date.now());
}
};
const resumeExport = async () => {
const exportFolder = getData(LS_KEYS.EXPORT)?.folder;
if (!exportFolder) {
const folderSelected = await selectExportDirectory();
if (!folderSelected) {
// no-op as select folder aborted
return;
}
}
const exportRecord = await exportService.getExportRecord();
if (exportRecord.stage !== ExportStage.PAUSED) {
// export not paused
return;
}
const pausedStageProgress = exportRecord.progress;
updateExportStage(ExportStage.INPROGRESS);
updateExportProgress(pausedStageProgress);
const updateExportStatsWithOffset = ((progress: ExportProgress) => updateExportProgress(
{
current: pausedStageProgress.current + progress.current,
total: pausedStageProgress.current + progress.total,
},
));
const finished = await exportService.exportFiles(updateExportStatsWithOffset);
if (finished) {
updateExportStage(ExportStage.FINISHED);
await sleep(100);
updateExportTime(Date.now());
}
2021-07-07 05:31:48 +00:00
};
2021-07-07 09:20:59 +00:00
const selectExportDirectory = async () => {
const newFolder = await exportService.selectExportDirectory();
if (newFolder) {
updateExportFolder(newFolder);
return true;
} else {
return false;
}
2021-07-07 09:20:59 +00:00
};
2021-07-13 13:39:31 +00:00
const revertExportStatsToLastExport = async (exportRecord: ExportRecord) => {
const failed = exportRecord?.failedFiles?.length ?? 0;
const success = exportRecord?.exportedFiles?.length ?? 0;
setExportStats({ failed, success });
2021-07-13 13:39:31 +00:00
};
const stopExport = async () => {
2021-07-09 03:35:33 +00:00
exportService.stopRunningExport();
2021-07-13 13:39:31 +00:00
const exportRecord = await exportService.getExportRecord();
await revertExportStatsToLastExport(exportRecord);
if (!exportRecord.lastAttemptTimestamp) {
2021-07-08 06:42:28 +00:00
updateExportStage(ExportStage.INIT);
} else {
updateExportStage(ExportStage.FINISHED);
}
};
const pauseExport = () => {
updateExportStage(ExportStage.PAUSED);
2021-07-09 03:35:33 +00:00
exportService.pauseRunningExport();
2021-07-08 06:42:28 +00:00
};
2021-07-12 09:15:08 +00:00
const retryFailed = async () => {
updateExportStage(ExportStage.INPROGRESS);
2021-07-14 04:33:28 +00:00
await sleep(100);
updateExportProgress({ current: 0, total: exportStats.failed });
await exportService.retryFailedFiles(updateExportProgress);
2021-07-14 04:33:28 +00:00
updateExportStage(ExportStage.FINISHED);
await sleep(100);
updateExportTime(Date.now());
2021-07-12 09:15:08 +00:00
};
2021-07-08 06:42:28 +00:00
2021-07-07 09:20:59 +00:00
const ExportDynamicState = () => {
2021-07-08 06:42:28 +00:00
switch (exportStage) {
case ExportStage.INIT:
2021-07-07 09:20:59 +00:00
return (
<ExportInit {...props}
exportFolder={exportFolder}
exportSize={exportSize}
updateExportFolder={updateExportFolder}
startExport={startExport}
2021-07-07 09:20:59 +00:00
selectExportDirectory={selectExportDirectory}
/>
);
2021-07-08 06:42:28 +00:00
case ExportStage.INPROGRESS:
case ExportStage.PAUSED:
2021-07-07 09:20:59 +00:00
return (
<ExportInProgress {...props}
exportFolder={exportFolder}
exportSize={exportSize}
2021-07-08 06:42:28 +00:00
exportStage={exportStage}
exportProgress={exportProgress}
resumeExport={resumeExport}
2021-07-09 03:35:33 +00:00
cancelExport={stopExport}
2021-07-08 06:42:28 +00:00
pauseExport={pauseExport}
2021-07-07 09:20:59 +00:00
/>
);
2021-07-08 06:42:28 +00:00
case ExportStage.FINISHED:
2021-07-07 09:20:59 +00:00
return (
<ExportFinished
{...props}
exportFolder={exportFolder}
exportSize={exportSize}
updateExportFolder={updateExportFolder}
lastExportTime={lastExportTime}
exportStats={exportStats}
exportFiles={startExport}
2021-07-12 09:15:08 +00:00
retryFailed={retryFailed}
2021-07-07 09:20:59 +00:00
/>
);
default: return (<></>);
}
};
return (
<MessageDialog
show={props.show}
onHide={props.onHide}
attributes={{
title: constants.EXPORT_DATA,
}}
>
<div style={{ borderBottom: '1px solid #444', marginBottom: '20px', padding: '0 5%' }}>
<Row>
<Label width="40%">{constants.DESTINATION}</Label>
<Value width="60%">
{!exportFolder ?
(<Button variant={'outline-success'} onClick={selectExportDirectory}>{constants.SELECT_FOLDER}</Button>) :
(<>
<span style={{ overflow: 'hidden', direction: 'rtl', height: '1.5rem', width: '90%', whiteSpace: 'nowrap' }}>
{exportFolder}
</span>
2021-07-13 13:39:31 +00:00
{(exportStage === ExportStage.FINISHED || exportStage === ExportStage.INIT) && (
<FolderIconWrapper onClick={selectExportDirectory} >
<FolderIcon />
</FolderIconWrapper>
)}
2021-07-07 09:20:59 +00:00
</>)
}
</Value>
</Row>
<Row>
<Label width="40%">{constants.TOTAL_EXPORT_SIZE} </Label><Value width="60%">{exportSize ? `${exportSize} GB` : <InProgressIcon />}</Value>
</Row>
</div>
<ExportDynamicState />
</MessageDialog >
);
2021-07-06 09:20:05 +00:00
}