-
-
Notifications
You must be signed in to change notification settings - Fork 828
Chat export parameter customisation #7647
Changes from 7 commits
77078df
def7439
3146317
63f3559
74ccf3d
6998302
63d9785
02dbc60
4118e3e
00f0495
506900c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |
limitations under the License. | ||
*/ | ||
|
||
import React, { useRef, useState } from "react"; | ||
import React, { useRef, useState, Dispatch, SetStateAction } from "react"; | ||
import { Room } from "matrix-js-sdk/src"; | ||
import { logger } from "matrix-js-sdk/src/logger"; | ||
|
||
|
@@ -39,18 +39,74 @@ import { useStateCallback } from "../../../hooks/useStateCallback"; | |
import Exporter from "../../../utils/exportUtils/Exporter"; | ||
import Spinner from "../elements/Spinner"; | ||
import InfoDialog from "./InfoDialog"; | ||
import ChatExport from "../../../customisations/ChatExport"; | ||
|
||
interface IProps extends IDialogProps { | ||
room: Room; | ||
} | ||
|
||
const validateNumberInRange = (min: number, max: number) => (value?: string | number) => { | ||
const parsedSize = parseInt(value as string, 10); | ||
return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); | ||
}; | ||
|
||
interface ExportConfig { | ||
exportFormat: ExportFormat; | ||
exportType: ExportType; | ||
numberOfMessages: number; | ||
sizeLimit: number; | ||
includeAttachments: boolean; | ||
setExportFormat?: Dispatch<SetStateAction<ExportFormat>>; | ||
setExportType?: Dispatch<SetStateAction<ExportType>>; | ||
setAttachments?: Dispatch<SetStateAction<boolean>>; | ||
setNumberOfMessages?: Dispatch<SetStateAction<number>>; | ||
setSizeLimit?: Dispatch<SetStateAction<number>>; | ||
} | ||
|
||
/** | ||
* Set up form state using "forceRoomExportParameters" or defaults | ||
* Form fields configured in ForceRoomExportParameters are not allowed to be edited | ||
* Only return change handlers for editable values | ||
*/ | ||
const useExportFormState = (): ExportConfig => { | ||
const config = ChatExport.getForceChatExportParameters(); | ||
|
||
const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html); | ||
const [exportType, setExportType] = useState(config.range || ExportType.Timeline); | ||
const [includeAttachments, setAttachments] = | ||
useState(config.includeAttachments !== undefined && config.includeAttachments); | ||
const [numberOfMessages, setNumberOfMessages] = useState<number>(config.numberOfMessages || 100); | ||
const [sizeLimit, setSizeLimit] = useState<number | null>(config.sizeMb || 8); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the benefit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
|
||
return { | ||
exportFormat, | ||
exportType, | ||
includeAttachments, | ||
numberOfMessages, | ||
sizeLimit, | ||
setExportFormat: !config.format ? setExportFormat : undefined, | ||
setExportType: !config.range ? setExportType : undefined, | ||
setNumberOfMessages: !config.numberOfMessages ? setNumberOfMessages : undefined, | ||
setSizeLimit: !config.sizeMb ? setSizeLimit : undefined, | ||
setAttachments: config.includeAttachments === undefined ? setAttachments : undefined, | ||
}; | ||
}; | ||
|
||
const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | ||
const [exportFormat, setExportFormat] = useState(ExportFormat.Html); | ||
const [exportType, setExportType] = useState(ExportType.Timeline); | ||
const [includeAttachments, setAttachments] = useState(false); | ||
const { | ||
exportFormat, | ||
exportType, | ||
includeAttachments, | ||
numberOfMessages, | ||
sizeLimit, | ||
setExportFormat, | ||
setExportType, | ||
setNumberOfMessages, | ||
setSizeLimit, | ||
setAttachments, | ||
} = useExportFormState(); | ||
|
||
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 [exportProgressText, setExportProgressText] = useState(_t("Processing...")); | ||
|
@@ -110,9 +166,10 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | |
}; | ||
|
||
const onExportClick = async () => { | ||
const isValidSize = await sizeLimitRef.current.validate({ | ||
const isValidSize = !setSizeLimit || (await sizeLimitRef.current.validate({ | ||
focused: false, | ||
}); | ||
})); | ||
|
||
if (!isValidSize) { | ||
sizeLimitRef.current.validate({ focused: true }); | ||
return; | ||
|
@@ -146,12 +203,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | |
}, | ||
}, { | ||
key: "number", | ||
test: ({ value }) => { | ||
const parsedSize = parseFloat(value); | ||
const min = 1; | ||
const max = 2000; | ||
return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max); | ||
}, | ||
test: ({ value }) => validateNumberInRange(1, 2000)(value), | ||
invalid: () => { | ||
const min = 1; | ||
const max = 2000; | ||
|
@@ -186,13 +238,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | |
}, | ||
}, { | ||
key: "number", | ||
test: ({ value }) => { | ||
const parsedSize = parseFloat(value); | ||
const min = 1; | ||
const max = 10 ** 8; | ||
if (isNaN(parsedSize)) return false; | ||
return !(min > parsedSize || parsedSize > max); | ||
}, | ||
test: ({ value }) => validateNumberInRange(1, 10 ** 8)(value), | ||
invalid: () => { | ||
const min = 1; | ||
const max = 10 ** 8; | ||
|
@@ -236,7 +282,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | |
}); | ||
|
||
let messageCount = null; | ||
if (exportType === ExportType.LastNMessages) { | ||
if (exportType === ExportType.LastNMessages && setNumberOfMessages) { | ||
messageCount = ( | ||
<Field | ||
id="message-count" | ||
|
@@ -319,61 +365,74 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { | |
) } | ||
</p> : null } | ||
|
||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Format") } | ||
</span> | ||
|
||
<div className="mx_ExportDialog_options"> | ||
<StyledRadioGroup | ||
name="exportFormat" | ||
value={exportFormat} | ||
onChange={(key) => setExportFormat(ExportFormat[key])} | ||
definitions={exportFormatOptions} | ||
/> | ||
{ !!setExportFormat && <> | ||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Format") } | ||
</span> | ||
|
||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Messages") } | ||
</span> | ||
|
||
<Field | ||
id="export-type" | ||
element="select" | ||
value={exportType} | ||
onChange={(e) => { | ||
setExportType(ExportType[e.target.value]); | ||
}} | ||
> | ||
{ exportTypeOptions } | ||
</Field> | ||
{ messageCount } | ||
|
||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Size Limit") } | ||
</span> | ||
|
||
<Field | ||
id="size-limit" | ||
type="number" | ||
autoComplete="off" | ||
onValidate={onValidateSize} | ||
element="input" | ||
ref={sizeLimitRef} | ||
value={sizeLimit.toString()} | ||
postfixComponent={sizePostFix} | ||
onChange={(e) => setSizeLimit(parseInt(e.target.value))} | ||
/> | ||
<StyledRadioGroup | ||
name="exportFormat" | ||
value={exportFormat} | ||
onChange={(key) => setExportFormat(ExportFormat[key])} | ||
definitions={exportFormatOptions} | ||
/> | ||
</> } | ||
|
||
{ | ||
!!setExportType && <> | ||
|
||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Messages") } | ||
</span> | ||
|
||
<Field | ||
id="export-type" | ||
element="select" | ||
value={exportType} | ||
onChange={(e) => { | ||
setExportType(ExportType[e.target.value]); | ||
}} | ||
> | ||
{ exportTypeOptions } | ||
</Field> | ||
{ messageCount } | ||
</> | ||
} | ||
|
||
{ setSizeLimit && <> | ||
<span className="mx_ExportDialog_subheading"> | ||
{ _t("Size Limit") } | ||
</span> | ||
|
||
<Field | ||
id="size-limit" | ||
type="number" | ||
autoComplete="off" | ||
onValidate={onValidateSize} | ||
element="input" | ||
ref={sizeLimitRef} | ||
value={sizeLimit.toString()} | ||
postfixComponent={sizePostFix} | ||
onChange={(e) => setSizeLimit(parseInt(e.target.value))} | ||
/> | ||
</> } | ||
|
||
{ setAttachments && <> | ||
<StyledCheckbox | ||
className="mx_ExportDialog_attachments-checkbox" | ||
id="include-attachments" | ||
checked={includeAttachments} | ||
onChange={(e) => | ||
setAttachments( | ||
(e.target as HTMLInputElement).checked, | ||
) | ||
} | ||
> | ||
{ _t("Include Attachments") } | ||
</StyledCheckbox> | ||
</> } | ||
|
||
<StyledCheckbox | ||
id="include-attachments" | ||
checked={includeAttachments} | ||
onChange={(e) => | ||
setAttachments( | ||
(e.target as HTMLInputElement).checked, | ||
) | ||
} | ||
> | ||
{ _t("Include Attachments") } | ||
</StyledCheckbox> | ||
</div> | ||
{ isExporting ? ( | ||
<div data-test-id='export-progress' className="mx_ExportDialog_progress"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
Copyright 2022 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import { ExportFormat, ExportType } from "../utils/exportUtils/exportUtils"; | ||
|
||
export type ForceChatExportParameters = { | ||
format?: ExportFormat; | ||
range?: ExportType; | ||
// must be < 10**8 | ||
numberOfMessages?: number; | ||
includeAttachments?: boolean; | ||
// must be > 0 and < 100000 | ||
sizeMb?: number; | ||
}; | ||
|
||
/** | ||
* Force parameters in room chat export | ||
* fields returned here are forced | ||
* and not allowed to be edited in the chat export form | ||
*/ | ||
const getForceChatExportParameters = (): ForceChatExportParameters => { | ||
return {}; | ||
}; | ||
|
||
// This interface summarises all available customisation points and also marks | ||
// them all as optional. This allows customisers to only define and export the | ||
// customisations they need while still maintaining type safety. | ||
export interface IChatExportCustomisations { | ||
getForceChatExportParameters?: typeof getForceChatExportParameters; | ||
} | ||
|
||
// A real customisation module will define and export one or more of the | ||
// customisation points that make up `IChatExportCustomisations`. | ||
export default { | ||
getForceChatExportParameters, | ||
} as IChatExportCustomisations; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,7 +48,7 @@ export default abstract class Exporter { | |
protected setProgressText: React.Dispatch<React.SetStateAction<string>>, | ||
) { | ||
if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB | ||
exportOptions.maxSize > 2000 * 1024 * 1024|| // More than ~ 2 GB | ||
exportOptions.maxSize > 100000 * 1024 * 1024 || // More than 100 GB | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given that it has to be loaded into RAM before downloading, I doubt this is a sane change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, my thinking was that it's basically a 'use at your own risk' config, but we should probably still stop people from crashing their browser. Not sure what's the highest possible reasonable limit - 16gb? |
||
exportOptions.numberOfMessages > 10**8 | ||
) { | ||
throw new Error("Invalid export options"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be a nice shared util in /utils/