ente/src/components/ExportModal.tsx

279 lines
10 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-13 13:39:31 +00:00
import exportService, { 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('');
2021-07-07 07:45:55 +00:00
const [exportStats, setExportStats] = useState<ExportStats>({ current: 0, total: 0, failed: 0 });
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?.time);
setExportStats(exportInfo?.stats ?? { current: 0, total: 0, failed: 0 });
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 updateExportStats = (newStats: ExportStats) => {
2021-07-09 03:48:19 +00:00
setExportStats(newStats);
2021-07-13 13:39:31 +00:00
exportService.updateExportRecord({ stats: newStats });
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);
2021-07-09 03:48:19 +00:00
updateExportStats({ current: 0, total: 0, failed: 0 });
2021-07-14 07:21:32 +00:00
const { paused } = await exportService.exportFiles(updateExportStats);
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);
updateExportStats({ current: 0, total: 0, failed: 0 });
const finished = await exportService.retryFailedFiles(updateExportStats);
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 pausedStageStats = exportRecord.stats;
updateExportStage(ExportStage.INPROGRESS);
updateExportStats(pausedStageStats);
const updateExportStatsWithOffset = ((stats: ExportStats) => updateExportStats(
{
current: pausedStageStats.current + stats.current,
total: pausedStageStats.current + stats.total,
failed: pausedStageStats.failed + stats.failed,
},
));
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;
const total = failed + success;
setExportStats({ current: 0, total, failed, success });
};
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.time) {
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);
2021-07-13 13:39:31 +00:00
updateExportStats({ current: 0, total: exportStats.failed, failed: 0 });
2021-07-14 04:33:28 +00:00
await exportService.retryFailedFiles(updateExportStats);
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}
2021-07-07 09:20:59 +00:00
exportStats={exportStats}
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
}