Skip to content

Commit 4f3a1a2

Browse files
authored
Refactor and Move RedactedBodyView To Shared Components (#32772)
* refactoring and creation of shared-components for reductedBodyView * move redacted message rendering to shared MVVM view * Update snapshots + fix lint errors * Remove MatrixClientPeg and use reguler react matrix client context * Stop resyncing redacted body view models with mxEvent * Fix redacted_because test fixtures for stricter event typing * Simplify redacted body client access * Watch timestamp setting in redacted body view model * Refactor redacted and decryption failure body factories into MBodyFactory * Prettier Fix * Refactor FileBody into same pattern for consitancy
1 parent d7843bb commit 4f3a1a2

File tree

30 files changed

+688
-159
lines changed

30 files changed

+688
-159
lines changed

apps/web/res/css/_components.pcss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@
237237
@import "./views/messages/_MessageActionBar.pcss";
238238
@import "./views/messages/_MjolnirBody.pcss";
239239
@import "./views/messages/_ReactionsRow.pcss";
240-
@import "./views/messages/_RedactedBody.pcss";
241240
@import "./views/messages/_RoomAvatarEvent.pcss";
242241
@import "./views/messages/_TextualEvent.pcss";
243242
@import "./views/messages/_UnknownBody.pcss";

apps/web/res/css/views/messages/_RedactedBody.pcss

Lines changed: 0 additions & 21 deletions
This file was deleted.

apps/web/src/components/views/messages/EditHistoryMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import { editBodyDiffToHtml } from "../../../utils/MessageDiffUtils";
1616
import { formatTime } from "../../../DateUtils";
1717
import { _t } from "../../../languageHandler";
1818
import Modal from "../../../Modal";
19-
import RedactedBody from "./RedactedBody";
2019
import AccessibleButton from "../elements/AccessibleButton";
2120
import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
2221
import ViewSource from "../../structures/ViewSource";
2322
import SettingsStore from "../../../settings/SettingsStore";
2423
import MatrixClientContext from "../../../contexts/MatrixClientContext";
24+
import { RedactedBodyFactory } from "./MBodyFactory";
2525

2626
function getReplacedContent(event: MatrixEvent): IContent {
2727
const originalContent = event.getOriginalContent();
@@ -151,7 +151,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
151151
const content = getReplacedContent(mxEvent);
152152
let contentContainer;
153153
if (mxEvent.isRedacted()) {
154-
contentContainer = <RedactedBody mxEvent={this.props.mxEvent} />;
154+
contentContainer = <RedactedBodyFactory mxEvent={this.props.mxEvent} />;
155155
} else {
156156
let contentElements;
157157
if (this.props.previousEdit) {

apps/web/src/components/views/messages/IBodyProps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface IBodyProps {
2525

2626
showUrlPreview?: boolean;
2727
forExport?: boolean;
28+
// Whether file-style rendering should show the info row / placeholder.
29+
showFileInfo?: boolean;
2830
maxImageHeight?: number;
2931
replacingEventId?: string;
3032
editState?: EditorStateTransfer;

apps/web/src/components/views/messages/MAudioBody.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { PlaybackManager } from "../../../audio/PlaybackManager";
2020
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
2121
import MediaProcessingError from "./shared/MediaProcessingError";
2222
import { AudioPlayerViewModel } from "../../../viewmodels/audio/AudioPlayerViewModel";
23-
import { FileBodyViewFactory, renderMBody } from "./MBodyFactory";
23+
import { FileBodyFactory, renderMBody } from "./MBodyFactory";
2424

2525
interface IState {
2626
error?: boolean;
@@ -111,7 +111,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
111111
return (
112112
<span className="mx_MAudioBody">
113113
<AudioPlayer playback={this.state.playback} mediaName={this.props.mxEvent.getContent().body} />
114-
{this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyViewFactory)}
114+
{this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyFactory)}
115115
</span>
116116
);
117117
}

apps/web/src/components/views/messages/MBodyFactory.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,28 @@ Please see LICENSE files in the repository root for full details.
77

88
import React, { type JSX, type RefObject, useContext, useEffect, useRef } from "react";
99
import { MsgType } from "matrix-js-sdk/src/matrix";
10-
import { FileBodyView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
10+
import {
11+
DecryptionFailureBodyView,
12+
FileBodyView,
13+
RedactedBodyView,
14+
useCreateAutoDisposedViewModel,
15+
} from "@element-hq/web-shared-components";
1116

1217
import { type IBodyProps } from "./IBodyProps";
1318
import RoomContext from "../../../contexts/RoomContext";
19+
import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
20+
import { DecryptionFailureBodyViewModel } from "../../../viewmodels/message-body/DecryptionFailureBodyViewModel";
1421
import { FileBodyViewModel } from "../../../viewmodels/message-body/FileBodyViewModel";
22+
import { RedactedBodyViewModel } from "../../../viewmodels/message-body/RedactedBodyViewModel";
1523

16-
interface FileBodyViewProps {
17-
/*
18-
* Whether file-style message bodies should render their info row/placeholder.
19-
* Used by file-body rendering paths (for example FileBodyViewModel via MBodyFactory).
20-
*/
21-
showFileInfo?: boolean;
22-
}
23-
24-
type MBodyComponent = React.ComponentType<IBodyProps & FileBodyViewProps>;
24+
type MBodyComponent = React.ComponentType<IBodyProps>;
2525

26-
// Adapter that binds RoomContext data and lifecycle updates to the
27-
// FileBody view model before rendering the shared view component.
28-
function FileBodyViewWrapped({
26+
export function FileBodyFactory({
2927
mxEvent,
3028
mediaEventHelper,
3129
forExport,
3230
showFileInfo,
33-
}: IBodyProps & FileBodyViewProps): JSX.Element {
31+
}: Pick<IBodyProps, "mxEvent" | "mediaEventHelper" | "forExport" | "showFileInfo">): JSX.Element {
3432
const { timelineRenderingType } = useContext(RoomContext);
3533
const refIFrame = useRef<HTMLIFrameElement>(null) as RefObject<HTMLIFrameElement>;
3634
const refLink = useRef<HTMLAnchorElement>(null) as RefObject<HTMLAnchorElement>;
@@ -61,19 +59,41 @@ function FileBodyViewWrapped({
6159
return <FileBodyView vm={vm} refIFrame={refIFrame} refLink={refLink} className="mx_MFileBody" />;
6260
}
6361

64-
// Exported for explicit fallback usage where callers want file-body rendering.
65-
export const FileBodyViewFactory: MBodyComponent = (props) => <FileBodyViewWrapped {...props} />;
62+
export function RedactedBodyFactory({ mxEvent, ref }: Pick<IBodyProps, "mxEvent" | "ref">): JSX.Element {
63+
const vm = useCreateAutoDisposedViewModel(() => new RedactedBodyViewModel({ mxEvent }));
64+
65+
useEffect(() => {
66+
vm.setEvent(mxEvent);
67+
}, [mxEvent, vm]);
68+
69+
return <RedactedBodyView vm={vm} ref={ref} className="mx_RedactedBody" />;
70+
}
71+
72+
export function DecryptionFailureBodyFactory({ mxEvent, ref }: Pick<IBodyProps, "mxEvent" | "ref">): JSX.Element {
73+
const verificationState = useContext(LocalDeviceVerificationStateContext);
74+
const vm = useCreateAutoDisposedViewModel(
75+
() =>
76+
new DecryptionFailureBodyViewModel({
77+
decryptionFailureCode: mxEvent.decryptionFailureReason,
78+
verificationState,
79+
}),
80+
);
81+
82+
useEffect(() => {
83+
vm.setDecryptionFailureCode(mxEvent.decryptionFailureReason);
84+
vm.setVerificationState(verificationState);
85+
}, [mxEvent, verificationState, vm]);
86+
87+
return <DecryptionFailureBodyView vm={vm} ref={ref} className="mx_DecryptionFailureBody mx_EventTile_content" />;
88+
}
6689

6790
// Message body factory registry.
6891
// Start small: only m.file currently routes to the new FileBodyView path.
69-
const MESSAGE_BODY_TYPES = new Map<string, MBodyComponent>([[MsgType.File, FileBodyViewFactory]]);
92+
const MESSAGE_BODY_TYPES = new Map<string, MBodyComponent>([[MsgType.File, FileBodyFactory]]);
7093

7194
// Render a body using the picked factory.
7295
// Falls back to the provided factory when msgtype has no specific handler.
73-
export function renderMBody(
74-
props: IBodyProps & FileBodyViewProps,
75-
fallbackFactory?: MBodyComponent,
76-
): JSX.Element | null {
96+
export function renderMBody(props: IBodyProps, fallbackFactory?: MBodyComponent): JSX.Element | null {
7797
const BodyType = MESSAGE_BODY_TYPES.get(props.mxEvent.getContent().msgtype as string) ?? fallbackFactory;
7898
if (!BodyType) {
7999
return null;

apps/web/src/components/views/messages/MImageBody.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { DecryptError, DownloadError } from "../../../utils/DecryptFile";
3636
import { HiddenMediaPlaceholder } from "./HiddenMediaPlaceholder";
3737
import { useMediaVisible } from "../../../hooks/useMediaVisible";
3838
import { isMimeTypeAllowed } from "../../../utils/blobs.ts";
39-
import { FileBodyViewFactory, renderMBody } from "./MBodyFactory";
39+
import { FileBodyFactory, renderMBody } from "./MBodyFactory";
4040

4141
enum Placeholder {
4242
NoImage,
@@ -651,7 +651,7 @@ export class MImageBodyInner extends React.Component<IProps, IState> {
651651
this.context.timelineRenderingType === TimelineRenderingType.Thread ||
652652
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList;
653653
if (!hasMessageActionBar) {
654-
return renderMBody({ ...this.props, showFileInfo: false }, FileBodyViewFactory);
654+
return renderMBody({ ...this.props, showFileInfo: false }, FileBodyFactory);
655655
}
656656
}
657657

@@ -664,7 +664,7 @@ export class MImageBodyInner extends React.Component<IProps, IState> {
664664
!isMimeTypeAllowed(content.info?.mimetype ?? "") &&
665665
!content.info?.thumbnail_info
666666
) {
667-
return renderMBody(this.props, FileBodyViewFactory);
667+
return renderMBody(this.props, FileBodyFactory);
668668
}
669669

670670
if (this.state.error) {

apps/web/src/components/views/messages/MVideoBody.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
2222
import MediaProcessingError from "./shared/MediaProcessingError";
2323
import { HiddenMediaPlaceholder } from "./HiddenMediaPlaceholder";
2424
import { useMediaVisible } from "../../../hooks/useMediaVisible";
25-
import { FileBodyViewFactory, renderMBody } from "./MBodyFactory";
25+
import { FileBodyFactory, renderMBody } from "./MBodyFactory";
2626

2727
interface IState {
2828
decryptedUrl: string | null;
@@ -246,7 +246,7 @@ class MVideoBodyInner extends React.PureComponent<IProps, IState> {
246246

247247
private getFileBody = (): ReactNode => {
248248
if (this.props.forExport) return null;
249-
return this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyViewFactory);
249+
return this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyFactory);
250250
};
251251

252252
public render(): React.ReactNode {

apps/web/src/components/views/messages/MVoiceMessageBody.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { isVoiceMessage } from "../../../utils/EventUtils";
1717
import { PlaybackQueue } from "../../../audio/PlaybackQueue";
1818
import { type Playback } from "../../../audio/Playback";
1919
import RoomContext from "../../../contexts/RoomContext";
20-
import { FileBodyViewFactory, renderMBody } from "./MBodyFactory";
20+
import { FileBodyFactory, renderMBody } from "./MBodyFactory";
2121

2222
export default class MVoiceMessageBody extends MAudioBody {
2323
public static contextType = RoomContext;
@@ -54,7 +54,7 @@ export default class MVoiceMessageBody extends MAudioBody {
5454
return (
5555
<span className="mx_MVoiceMessageBody">
5656
<RecordingPlayback playback={this.state.playback} />
57-
{this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyViewFactory)}
57+
{this.showFileBody && renderMBody({ ...this.props, showFileInfo: false }, FileBodyFactory)}
5858
</span>
5959
);
6060
}

apps/web/src/components/views/messages/MessageEvent.tsx

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

99
import mime from "mime";
10-
import React, { type JSX, createRef, useContext, useEffect } from "react";
10+
import React, { createRef } from "react";
1111
import { logger } from "matrix-js-sdk/src/logger";
1212
import {
1313
EventType,
@@ -18,12 +18,9 @@ import {
1818
M_POLL_START,
1919
type IContent,
2020
} from "matrix-js-sdk/src/matrix";
21-
import { useCreateAutoDisposedViewModel, DecryptionFailureBodyView } from "@element-hq/web-shared-components";
2221

23-
import { LocalDeviceVerificationStateContext } from "../../../contexts/LocalDeviceVerificationStateContext";
2422
import SettingsStore from "../../../settings/SettingsStore";
2523
import { Mjolnir } from "../../../mjolnir/Mjolnir";
26-
import RedactedBody from "./RedactedBody";
2724
import UnknownBody from "./UnknownBody";
2825
import { type IMediaBody } from "./IMediaBody";
2926
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
@@ -38,8 +35,7 @@ import MLocationBody from "./MLocationBody";
3835
import MjolnirBody from "./MjolnirBody";
3936
import MBeaconBody from "./MBeaconBody";
4037
import { type GetRelationsForEvent, type IEventTileOps } from "../rooms/EventTile";
41-
import { DecryptionFailureBodyViewModel } from "../../../viewmodels/message-body/DecryptionFailureBodyViewModel";
42-
import { FileBodyViewFactory, renderMBody } from "./MBodyFactory";
38+
import { DecryptionFailureBodyFactory, FileBodyFactory, RedactedBodyFactory, renderMBody } from "./MBodyFactory";
4339

4440
// onMessageAllowed is handled internally
4541
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
@@ -67,7 +63,7 @@ const baseBodyTypes = new Map<string, React.ComponentType<IBodyProps>>([
6763
[MsgType.Notice, TextualBody],
6864
[MsgType.Emote, TextualBody],
6965
[MsgType.Image, MImageBody],
70-
[MsgType.File, (props: IBodyProps) => renderMBody(props, FileBodyViewFactory)!],
66+
[MsgType.File, (props: IBodyProps) => renderMBody(props, FileBodyFactory)!],
7167
[MsgType.Audio, MVoiceOrAudioBody],
7268
[MsgType.Video, MVideoBody],
7369
]);
@@ -246,11 +242,11 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
246242
const content = this.props.mxEvent.getContent();
247243
const type = this.props.mxEvent.getType();
248244
const msgtype = content.msgtype;
249-
let BodyType: React.ComponentType<IBodyProps> = RedactedBody;
245+
let BodyType: React.ComponentType<IBodyProps> = RedactedBodyFactory;
250246
if (!this.props.mxEvent.isRedacted()) {
251247
// only resolve BodyType if event is not redacted
252248
if (this.props.mxEvent.isDecryptionFailure()) {
253-
BodyType = DecryptionFailureBodyWrapper;
249+
BodyType = DecryptionFailureBodyFactory;
254250
} else if (type && this.evTypes.has(type)) {
255251
BodyType = this.evTypes.get(type)!;
256252
} else if (msgtype && this.bodyTypes.has(msgtype)) {
@@ -330,22 +326,3 @@ const CaptionBody: React.FunctionComponent<IBodyProps & { WrappedBodyType: React
330326
<TextualBody {...{ ...props, ref: undefined }} />
331327
</div>
332328
);
333-
334-
/**
335-
* Bridge decryption-failure events into the view model using current local verification state.
336-
* This wrapper can be removed after MessageEvent has been changed to a function component.
337-
*/
338-
function DecryptionFailureBodyWrapper({ mxEvent, ref }: IBodyProps): JSX.Element {
339-
const verificationState = useContext(LocalDeviceVerificationStateContext);
340-
const vm = useCreateAutoDisposedViewModel(
341-
() =>
342-
new DecryptionFailureBodyViewModel({
343-
decryptionFailureCode: mxEvent.decryptionFailureReason,
344-
verificationState,
345-
}),
346-
);
347-
useEffect(() => {
348-
vm.setVerificationState(verificationState);
349-
}, [verificationState, vm]);
350-
return <DecryptionFailureBodyView vm={vm} ref={ref} className="mx_DecryptionFailureBody mx_EventTile_content" />;
351-
}

0 commit comments

Comments
 (0)