ente/src/components/ExportModal.tsx

262 lines
9.5 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';
2021-07-14 09:16:00 +00:00
import exportService, { ExportProgress, ExportStage, ExportStats, ExportType } 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-14 09:16:00 +00:00
// ====================
// SIDE EFFECTS
// ====================
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-14 09:16:00 +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 });
setExportStats({ success: exportInfo?.exportedFiles?.length ?? 0, failed: exportInfo?.failedFiles?.length ?? 0 });
2021-07-13 13:39:31 +00:00
if (exportInfo?.stage === ExportStage.INPROGRESS) {
resumeExport();
2021-07-13 13:39:31 +00:00
}
};
main();
}, [exportFolder]);
2021-07-07 09:20:59 +00:00
useEffect(() => {
setExportSize(props.usage);
}, [props.usage]);
2021-07-14 09:16:00 +00:00
// =============
// STATE UPDATERS
// ==============
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
};
2021-07-14 09:16:00 +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
};
2021-07-14 09:16:00 +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
};
2021-07-14 09:16:00 +00:00
// ======================
// HELPER FUNCTIONS
// =========================
const preExportRun = 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);
2021-07-14 09:16:00 +00:00
await sleep(100);
};
const postExportRun = async (paused: Boolean) => {
2021-07-14 07:21:32 +00:00
if (!paused) {
updateExportStage(ExportStage.FINISHED);
await sleep(100);
updateExportTime(Date.now());
2021-07-14 09:16:00 +00:00
syncExportStatsWithReport();
}
};
2021-07-14 09:16:00 +00:00
const startExport = async () => {
await preExportRun();
updateExportProgress({ current: 0, total: 0 });
const { paused } = await exportService.exportFiles(updateExportProgress, ExportType.NEW);
2021-07-14 09:16:00 +00:00
await postExportRun(paused);
};
const stopExport = async () => {
exportService.stopRunningExport();
postExportRun(false);
};
2021-07-14 09:16:00 +00:00
const pauseExport = () => {
updateExportStage(ExportStage.PAUSED);
exportService.pauseRunningExport();
postExportRun(true);
2021-07-14 09:16:00 +00:00
};
const resumeExport = async () => {
const exportRecord = await exportService.getExportRecord();
2021-07-14 09:16:00 +00:00
await preExportRun();
const pausedStageProgress = exportRecord.progress;
updateExportProgress(pausedStageProgress);
2021-07-14 09:16:00 +00:00
const updateExportStatsWithOffset = ((progress: ExportProgress) => updateExportProgress(
{
current: pausedStageProgress.current + progress.current,
total: pausedStageProgress.current + progress.total,
},
));
2021-07-14 09:16:00 +00:00
const { paused } = await exportService.exportFiles(updateExportStatsWithOffset, ExportType.PENDING);
2021-07-14 09:16:00 +00:00
await postExportRun(paused);
2021-07-07 05:31:48 +00:00
};
2021-07-07 09:20:59 +00:00
2021-07-14 09:16:00 +00:00
const retryFailedExport = async () => {
await preExportRun();
updateExportProgress({ current: 0, total: exportStats.failed });
const { paused } = await exportService.exportFiles(updateExportProgress, ExportType.RETRY_FAILED);
2021-07-14 09:16:00 +00:00
await postExportRun(paused);
2021-07-07 09:20:59 +00:00
};
2021-07-14 09:16:00 +00:00
const syncExportStatsWithReport = async () => {
const exportRecord = await exportService.getExportRecord();
2021-07-13 13:39:31 +00:00
const failed = exportRecord?.failedFiles?.length ?? 0;
const success = exportRecord?.exportedFiles?.length ?? 0;
setExportStats({ failed, success });
2021-07-13 13:39:31 +00:00
};
2021-07-14 09:16:00 +00:00
const selectExportDirectory = async () => {
const newFolder = await exportService.selectExportDirectory();
if (newFolder) {
updateExportFolder(newFolder);
return true;
2021-07-08 06:42:28 +00:00
} else {
2021-07-14 09:16:00 +00:00
return false;
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-14 09:16:00 +00:00
retryFailed={retryFailedExport}
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
}