diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 840f2c79f1..d2d207eec3 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -59,11 +59,30 @@ export interface IOperableEventTile { getEventTileOps(): IEventTileOps; } +const baseBodyTypes = new Map([ + [MsgType.Text, TextualBody], + [MsgType.Notice, TextualBody], + [MsgType.Emote, TextualBody], + [MsgType.Image, MImageBody], + [MsgType.File, MFileBody], + [MsgType.Audio, MVoiceOrAudioBody], + [MsgType.Video, MVideoBody], +]); +const baseEvTypes = new Map>>([ + [EventType.Sticker, MStickerBody], + [M_POLL_START.name, MPollBody], + [M_POLL_START.altName, MPollBody], + [M_BEACON_INFO.name, MBeaconBody], + [M_BEACON_INFO.altName, MBeaconBody], +]); + export default class MessageEvent extends React.Component implements IMediaBody, IOperableEventTile { private body: React.RefObject = createRef(); private mediaHelper: MediaEventHelper; + private bodyTypes = new Map(baseBodyTypes.entries()); + private evTypes = new Map>>(baseEvTypes.entries()); - static contextType = MatrixClientContext; + public static contextType = MatrixClientContext; public context!: React.ContextType; public constructor(props: IProps, context: React.ContextType) { @@ -72,6 +91,8 @@ export default class MessageEvent extends React.Component implements IMe if (MediaEventHelper.isEligible(this.props.mxEvent)) { this.mediaHelper = new MediaEventHelper(this.props.mxEvent); } + + this.updateComponentMaps(); } public componentDidMount(): void { @@ -88,32 +109,20 @@ export default class MessageEvent extends React.Component implements IMe this.mediaHelper?.destroy(); this.mediaHelper = new MediaEventHelper(this.props.mxEvent); } + + this.updateComponentMaps(); } - private get bodyTypes(): Record { - return { - [MsgType.Text]: TextualBody, - [MsgType.Notice]: TextualBody, - [MsgType.Emote]: TextualBody, - [MsgType.Image]: MImageBody, - [MsgType.File]: MFileBody, - [MsgType.Audio]: MVoiceOrAudioBody, - [MsgType.Video]: MVideoBody, + private updateComponentMaps() { + this.bodyTypes = new Map(baseBodyTypes.entries()); + for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) { + this.bodyTypes.set(bodyType, bodyComponent); + } - ...(this.props.overrideBodyTypes || {}), - }; - } - - private get evTypes(): Record>> { - return { - [EventType.Sticker]: MStickerBody, - [M_POLL_START.name]: MPollBody, - [M_POLL_START.altName]: MPollBody, - [M_BEACON_INFO.name]: MBeaconBody, - [M_BEACON_INFO.altName]: MBeaconBody, - - ...(this.props.overrideEventTypes || {}), - }; + this.evTypes = new Map>>(baseEvTypes.entries()); + for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) { + this.evTypes.set(evType, evComponent); + } } public getEventTileOps = () => { @@ -143,13 +152,13 @@ export default class MessageEvent extends React.Component implements IMe let BodyType: React.ComponentType> | ReactAnyComponent = RedactedBody; if (!this.props.mxEvent.isRedacted()) { // only resolve BodyType if event is not redacted - if (type && this.evTypes[type]) { - BodyType = this.evTypes[type]; - } else if (msgtype && this.bodyTypes[msgtype]) { - BodyType = this.bodyTypes[msgtype]; + if (type && this.evTypes.has(type)) { + BodyType = this.evTypes.get(type); + } else if (msgtype && this.bodyTypes.has(msgtype)) { + BodyType = this.bodyTypes.get(msgtype); } else if (content.url) { // Fallback to MFileBody if there's a content URL - BodyType = this.bodyTypes[MsgType.File]; + BodyType = this.bodyTypes.get(MsgType.File); } else { // Fallback to UnknownBody otherwise if not redacted BodyType = UnknownBody; diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index 88982b373f..a12807de47 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -67,7 +67,6 @@ export interface EventTileTypeProps { type FactoryProps = Omit; type Factory = (ref: Optional>, props: X) => JSX.Element; -type FactoryMap = Record; const MessageEventFactory: Factory = (ref, props) => ; const KeyVerificationConclFactory: Factory = (ref, props) => ; @@ -82,40 +81,40 @@ const HiddenEventFactory: Factory = (ref, props) => ; export const JSONEventFactory: Factory = (ref, props) => ; -const EVENT_TILE_TYPES: FactoryMap = { - [EventType.RoomMessage]: MessageEventFactory, // note that verification requests are handled in pickFactory() - [EventType.Sticker]: MessageEventFactory, - [M_POLL_START.name]: MessageEventFactory, - [M_POLL_START.altName]: MessageEventFactory, - [EventType.KeyVerificationCancel]: KeyVerificationConclFactory, - [EventType.KeyVerificationDone]: KeyVerificationConclFactory, - [EventType.CallInvite]: CallEventFactory, // note that this requires a special factory type -}; +const EVENT_TILE_TYPES = new Map([ + [EventType.RoomMessage, MessageEventFactory], // note that verification requests are handled in pickFactory() + [EventType.Sticker, MessageEventFactory], + [M_POLL_START.name, MessageEventFactory], + [M_POLL_START.altName, MessageEventFactory], + [EventType.KeyVerificationCancel, KeyVerificationConclFactory], + [EventType.KeyVerificationDone, KeyVerificationConclFactory], + [EventType.CallInvite, CallEventFactory], // note that this requires a special factory type +]); -const STATE_EVENT_TILE_TYPES: FactoryMap = { - [EventType.RoomEncryption]: (ref, props) => , - [EventType.RoomCanonicalAlias]: TextualEventFactory, - [EventType.RoomCreate]: (ref, props) => , - [EventType.RoomMember]: TextualEventFactory, - [EventType.RoomName]: TextualEventFactory, - [EventType.RoomAvatar]: (ref, props) => , - [EventType.RoomThirdPartyInvite]: TextualEventFactory, - [EventType.RoomHistoryVisibility]: TextualEventFactory, - [EventType.RoomTopic]: TextualEventFactory, - [EventType.RoomPowerLevels]: TextualEventFactory, - [EventType.RoomPinnedEvents]: TextualEventFactory, - [EventType.RoomServerAcl]: TextualEventFactory, +const STATE_EVENT_TILE_TYPES = new Map([ + [EventType.RoomEncryption, (ref, props) => ], + [EventType.RoomCanonicalAlias, TextualEventFactory], + [EventType.RoomCreate, (ref, props) => ], + [EventType.RoomMember, TextualEventFactory], + [EventType.RoomName, TextualEventFactory], + [EventType.RoomAvatar, (ref, props) => ], + [EventType.RoomThirdPartyInvite, TextualEventFactory], + [EventType.RoomHistoryVisibility, TextualEventFactory], + [EventType.RoomTopic, TextualEventFactory], + [EventType.RoomPowerLevels, TextualEventFactory], + [EventType.RoomPinnedEvents, TextualEventFactory], + [EventType.RoomServerAcl, TextualEventFactory], // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) - 'im.vector.modular.widgets': TextualEventFactory, // note that Jitsi widgets are special in pickFactory() - [WIDGET_LAYOUT_EVENT_TYPE]: TextualEventFactory, - [EventType.RoomTombstone]: TextualEventFactory, - [EventType.RoomJoinRules]: TextualEventFactory, - [EventType.RoomGuestAccess]: TextualEventFactory, -}; + ['im.vector.modular.widgets', TextualEventFactory], // note that Jitsi widgets are special in pickFactory() + [WIDGET_LAYOUT_EVENT_TYPE, TextualEventFactory], + [EventType.RoomTombstone, TextualEventFactory], + [EventType.RoomJoinRules, TextualEventFactory], + [EventType.RoomGuestAccess, TextualEventFactory], +]); // Add all the Mjolnir stuff to the renderer too for (const evType of ALL_RULE_TYPES) { - STATE_EVENT_TILE_TYPES[evType] = TextualEventFactory; + STATE_EVENT_TILE_TYPES.set(evType, TextualEventFactory); } // These events should be recorded in the STATE_EVENT_TILE_TYPES @@ -225,11 +224,11 @@ export function pickFactory( return noEventFactoryFactory(); // improper event type to render } - if (STATE_EVENT_TILE_TYPES[evType] === TextualEventFactory && !hasText(mxEvent, showHiddenEvents)) { + if (STATE_EVENT_TILE_TYPES.get(evType) === TextualEventFactory && !hasText(mxEvent, showHiddenEvents)) { return noEventFactoryFactory(); } - return STATE_EVENT_TILE_TYPES[evType] ?? noEventFactoryFactory(); + return STATE_EVENT_TILE_TYPES.get(evType) ?? noEventFactoryFactory(); } // Blanket override for all events. The MessageEvent component handles redacted states for us. @@ -241,7 +240,7 @@ export function pickFactory( return noEventFactoryFactory(); } - return EVENT_TILE_TYPES[evType] ?? noEventFactoryFactory(); + return EVENT_TILE_TYPES.get(evType) ?? noEventFactoryFactory(); } /** @@ -391,7 +390,7 @@ export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boo if (!handler) return false; if (handler === TextualEventFactory) { return hasText(mxEvent, showHiddenEvents); - } else if (handler === STATE_EVENT_TILE_TYPES[EventType.RoomCreate]) { + } else if (handler === STATE_EVENT_TILE_TYPES.get(EventType.RoomCreate)) { return Boolean(mxEvent.getContent()['predecessor']); } else if (handler === JSONEventFactory) { return false;