1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-11-16 06:42:26 +03:00
Files
matrix-react-sdk/src/components/views/dialogs/ExportDialog.tsx
2021-06-29 10:13:38 +05:30

372 lines
12 KiB
TypeScript

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<IProps> = ({ 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<number>(100);
const [sizeLimit, setSizeLimit] = useState<number | null>(8);
const sizeLimitRef = useRef<Field>();
const messageCountRef = useRef<Field>();
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<IFieldState, "value">): Promise<IValidationResult> => {
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<IFieldState, "value">): Promise<IValidationResult> => {
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 (
<option key={type} value={type}>
{textForType(type)}
</option>
);
});
let MessageCount = null;
if (exportType === exportTypes.LAST_N_MESSAGES) {
MessageCount = (
<Field
element="input"
type="number"
value={numberOfMessages}
ref={messageCountRef}
onValidate={onValidateNumberOfMessages}
label={_t("Number of messages")}
onChange={(e) => {
setNumberOfMessages(parseInt(e.target.value));
}}
/>
);
}
const sizePostFix = <span title={_t("MB")}>{_t("MB")}</span>;
const ExportCancelWarning = (
<BaseDialog
title={_t("Warning")}
className="mx_ExportDialog"
contentId="mx_Dialog_content"
onFinished={onFinished}
fixedWidth={true}
>
<p>
{" "}
{_t(
"Are you sure you want to stop exporting your data? If you do, you'll need to start over.",
)}{" "}
</p>
<DialogButtons
primaryButton={_t("Abort export process")}
primaryButtonClass="danger"
hasCancel={true}
cancelButton={_t("Continue")}
onCancel={() => setCancelWarning(false)}
onPrimaryButtonClick={confirmCanel}
/>
</BaseDialog>
);
const ExportSettings = (
<BaseDialog
title={_t("Export Chat")}
className="mx_ExportDialog"
contentId="mx_Dialog_content"
hasCancel={true}
onFinished={onFinished}
fixedWidth={true}
>
<p>
{_t(
"Select from the options below to export chats from your timeline",
)}
</p>
<span className="mx_ExportDialog_subheading">{_t("Format")}</span>
<StyledRadioGroup
name="feedbackRating"
value={exportFormat}
onChange={(key) => setExportFormat(exportFormats[key])}
definitions={exportFormatOptions}
/>
<span className="mx_ExportDialog_subheading">{_t("Messages")}</span>
<Field
element="select"
value={exportType}
onChange={(e) => {
setExportType(exportTypes[e.target.value]);
}}
>
{exportTypeOptions}
</Field>
{MessageCount}
<span className="mx_ExportDialog_subheading">
{_t("Size Limit")}
</span>
<Field
type="number"
autoComplete="off"
onValidate={onValidateSize}
element="input"
ref={sizeLimitRef}
value={sizeLimit}
postfixComponent={sizePostFix}
onChange={(e) => setSizeLimit(parseInt(e.target.value))}
/>
<StyledCheckbox
checked={includeAttachments}
onChange={(e) =>
setAttachments((e.target as HTMLInputElement).checked)
}
>
{_t("Include Attachments")}
</StyledCheckbox>
<DialogButtons
primaryButton={_t("Export")}
onPrimaryButtonClick={onExportClick}
onCancel={() => onFinished(false)}
/>
</BaseDialog>
);
const ExportSuccessful = (
<BaseDialog
title={_t("Export Successful")}
className="mx_ExportDialog"
contentId="mx_Dialog_content"
onFinished={onFinished}
fixedWidth={true}
>
<p> {_t("Your messages were successfully exported")} </p>
<DialogButtons
primaryButton={_t("Okay")}
hasCancel={false}
onPrimaryButtonClick={onFinished}
/>
</BaseDialog>
);
const ExportCancelSuccess = (
<BaseDialog
title={_t("Export Cancelled")}
className="mx_ExportDialog"
contentId="mx_Dialog_content"
onFinished={onFinished}
fixedWidth={true}
>
<p> {_t("The export was cancelled successfully")} </p>
<DialogButtons
primaryButton={_t("Okay")}
hasCancel={false}
onPrimaryButtonClick={onFinished}
/>
</BaseDialog>
);
const ExportProgress = (
<BaseDialog
title={_t("Exporting your data...")}
className="mx_ExportDialog"
contentId="mx_Dialog_content"
onFinished={onFinished}
fixedWidth={true}
>
<DialogButtons
primaryButton={_t("Cancel")}
primaryButtonClass="danger"
hasCancel={false}
onPrimaryButtonClick={onCancel}
/>
</BaseDialog>
);
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;