import React, { useRef, useState } from "react"; import { Room } from "matrix-js-sdk/src"; import { _t } from "../../../languageHandler"; import { IDialogProps } from "./IDialogProps"; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; import Field from "../elements/Field"; import StyledRadioGroup from "../elements/StyledRadioGroup"; import StyledCheckbox from "../elements/StyledCheckbox"; import { exportFormats, exportTypes, textForFormat, textForType, } from "../../../utils/exportUtils/exportUtils"; import { IFieldState, IValidationResult } from "../elements/Validation"; import HTMLExporter from "../../../utils/exportUtils/HtmlExport"; import JSONExporter from "../../../utils/exportUtils/JSONExport"; import PlainTextExporter from "../../../utils/exportUtils/PlainTextExport"; import { useStateCallback } from "../../../hooks/useStateCallback"; interface IProps extends IDialogProps { room: Room; } const ExportDialog: React.FC = ({ room, onFinished }) => { const [exportFormat, setExportFormat] = useState(exportFormats.HTML); const [exportType, setExportType] = useState(exportTypes.TIMELINE); const [includeAttachments, setAttachments] = useState(false); const [isExporting, setExporting] = useState(false); const [numberOfMessages, setNumberOfMessages] = useState(100); const [sizeLimit, setSizeLimit] = useState(8); const sizeLimitRef = useRef(); const messageCountRef = useRef(); const [displayCancel, setCancelWarning] = useState(false); const [exportCancelled, setExportCancelled] = useState(false); const [exportSuccessful, setExportSuccessful] = useState(false); const [Exporter, setExporter] = useStateCallback( null, async (Exporter: HTMLExporter | PlainTextExporter | JSONExporter) => { await Exporter?.export().then(() => { if (!exportCancelled) setExportSuccessful(true); }); }, ); const startExport = async () => { const exportOptions = { numberOfMessages, attachmentsIncluded: includeAttachments, maxSize: sizeLimit * 1024 * 1024, }; switch (exportFormat) { case exportFormats.HTML: setExporter( new HTMLExporter( room, exportTypes[exportType], exportOptions, ), ); break; case exportFormats.JSON: setExporter( new JSONExporter( room, exportTypes[exportType], exportOptions, ), ); break; case exportFormats.PLAIN_TEXT: setExporter( new PlainTextExporter( room, exportTypes[exportType], exportOptions, ), ); break; default: console.error("Unknown export format"); return; } }; const onExportClick = async () => { const isValidSize = await sizeLimitRef.current.validate({ focused: false, }); if (!isValidSize) { sizeLimitRef.current.validate({ focused: true }); return; } if (exportType === exportTypes.LAST_N_MESSAGES) { const isValidNumberOfMessages = await messageCountRef.current.validate({ focused: false }); if (!isValidNumberOfMessages) { messageCountRef.current.validate({ focused: true }); return; } } setExporting(true); await startExport(); }; const onValidateSize = async ({ value, }: Pick): Promise => { const parsedSize = parseFloat(value); const min = 1; const max = 4000; if (isNaN(parsedSize)) { return { valid: false, feedback: _t("Size must be a number") }; } if (!(min <= parsedSize && parsedSize <= max)) { return { valid: false, feedback: _t( "Size can only be between %(min)s MB and %(max)s MB", { min, max }, ), }; } return { valid: true, feedback: _t("Enter size between %(min)s MB and %(max)s MB", { min, max, }), }; }; const onValidateNumberOfMessages = async ({ value, }: Pick): Promise => { const parsedSize = parseFloat(value); const min = 1; const max = 10 ** 8; if (isNaN(parsedSize)) { return { valid: false, feedback: _t("Number of messages must be a number"), }; } if (!(min <= parsedSize && parsedSize <= max)) { return { valid: false, feedback: _t( "Number of messages can only be between %(min)s and %(max)s", { min, max }, ), }; } return { valid: true, feedback: _t("Enter a number between %(min)s and %(max)s", { min, max, }), }; }; const onCancel = async () => { if (isExporting) setCancelWarning(true); else onFinished(false); }; const confirmCanel = async () => { await Exporter?.cancelExport().then(() => { setExportCancelled(true); setExporting(false); setExporter(null); }); }; const exportFormatOptions = Object.keys(exportFormats).map((format) => ({ value: format, label: textForFormat(format), })); const exportTypeOptions = Object.keys(exportTypes).map((type) => { return ( ); }); let MessageCount = null; if (exportType === exportTypes.LAST_N_MESSAGES) { MessageCount = ( { setNumberOfMessages(parseInt(e.target.value)); }} /> ); } const sizePostFix = {_t("MB")}; const ExportCancelWarning = (

{" "} {_t( "Are you sure you want to stop exporting your data? If you do, you'll need to start over.", )}{" "}

setCancelWarning(false)} onPrimaryButtonClick={confirmCanel} />
); const ExportSettings = (

{_t( "Select from the options below to export chats from your timeline", )}

{_t("Format")} setExportFormat(exportFormats[key])} definitions={exportFormatOptions} /> {_t("Messages")} { setExportType(exportTypes[e.target.value]); }} > {exportTypeOptions} {MessageCount} {_t("Size Limit")} setSizeLimit(parseInt(e.target.value))} /> setAttachments((e.target as HTMLInputElement).checked) } > {_t("Include Attachments")} onFinished(false)} />
); const ExportSuccessful = (

{_t("Your messages were successfully exported")}

); const ExportCancelSuccess = (

{_t("The export was cancelled successfully")}

); const ExportProgress = ( ); let componentToDisplay: JSX.Element; if (exportCancelled) componentToDisplay = ExportCancelSuccess; else if (exportSuccessful) componentToDisplay = ExportSuccessful; else if (!isExporting) componentToDisplay = ExportSettings; else if (displayCancel) componentToDisplay = ExportCancelWarning; else componentToDisplay = ExportProgress; return componentToDisplay; }; export default ExportDialog;