Refactor EventTile using the MVVM pattern - #7a (#33640)

* Make EventTileViewModel an owned root VM

* Move timestamp sub-VMs under EventTileViewModel

* Move thread-list action bar VM under EventTileViewModel

* Clean up for improved readability

* Clean up to avoid duplicate EventTile render derivations

* Avoid mutating EventTileViewModel during render

* Move EventTile child VM syncing into adapters

* Replace timestamp VM field setters with batched setProps

* Component wrappers at the end of the file

* Lazy-create EventTile child view models
This commit is contained in:
rbondesson
2026-05-29 07:56:31 +02:00
committed by GitHub
parent 0ed98941bd
commit 3aa3678c2d
6 changed files with 392 additions and 273 deletions
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
import { type EventStatus, type MatrixEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { BaseViewModel } from "@element-hq/web-shared-components";
import {
type EventTileSenderProfileState,
@@ -32,6 +33,11 @@ import {
} from "./EventTileDerivedState";
import { TimelineRenderingType } from "../../../../contexts/RoomContext";
import { type Layout } from "../../../../settings/enums/Layout";
import { MessageTimestampViewModel, type MessageTimestampViewModelProps } from "./timestamp/MessageTimestampViewModel";
import {
ThreadListActionBarViewModel,
type ThreadListActionBarViewModelProps,
} from "../../ThreadListActionBarViewModel";
/** Event-level inputs for deriving the EventTile snapshot. */
export interface EventTileEventInput {
@@ -276,7 +282,48 @@ export interface EventTileRenderState {
}
/** Derives the current EventTile snapshot from component-owned inputs. */
export class EventTileViewModel {
export class EventTileViewModel extends BaseViewModel<EventTileRenderState, EventTileViewModelProps> {
private messageTimestampViewModel?: MessageTimestampViewModel;
private linkedMessageTimestampViewModel?: MessageTimestampViewModel;
private threadListActionBarViewModel?: ThreadListActionBarViewModel;
public constructor(props: EventTileViewModelProps) {
const initialRenderState = EventTileViewModel.createRenderState(props);
super(props, initialRenderState);
}
/** Updates root EventTile inputs and refreshes the derived render state. */
public setProps(props: EventTileViewModelProps): void {
this.props = props;
this.snapshot.set(EventTileViewModel.createRenderState(props));
}
public override dispose(): void {
this.messageTimestampViewModel?.dispose();
this.linkedMessageTimestampViewModel?.dispose();
this.threadListActionBarViewModel?.dispose();
super.dispose();
}
/** Lazily creates and returns the plain timestamp child view model. */
public getMessageTimestampViewModel(props: MessageTimestampViewModelProps): MessageTimestampViewModel {
this.messageTimestampViewModel ??= new MessageTimestampViewModel(props);
return this.messageTimestampViewModel;
}
/** Lazily creates and returns the permalink timestamp child view model. */
public getLinkedMessageTimestampViewModel(props: MessageTimestampViewModelProps): MessageTimestampViewModel {
this.linkedMessageTimestampViewModel ??= new MessageTimestampViewModel(props);
return this.linkedMessageTimestampViewModel;
}
/** Lazily creates and returns the thread-list action bar child view model. */
public getThreadListActionBarViewModel(props: ThreadListActionBarViewModelProps): ThreadListActionBarViewModel {
this.threadListActionBarViewModel ??= new ThreadListActionBarViewModel(props);
return this.threadListActionBarViewModel;
}
/** Derives render-ready EventTile state from component-owned inputs. */
public static createRenderState(props: EventTileViewModelProps): EventTileRenderState {
const snapshot = EventTileViewModel.createSnapshot(props);
@@ -473,6 +520,4 @@ export class EventTileViewModel {
noBubbleEvent: display.noBubbleEvent,
});
}
private constructor() {}
}
@@ -100,8 +100,7 @@ export class MessageTimestampViewModel
};
};
private updateProps(newProps: Partial<MessageTimestampViewModelProps>): void {
const nextProps = { ...this.props, ...newProps };
private replaceProps(nextProps: MessageTimestampViewModelProps): void {
if (!objectHasDiff(this.props, nextProps)) return;
this.props = nextProps;
@@ -120,52 +119,9 @@ export class MessageTimestampViewModel
}
/**
* Update the base timestamp (milliseconds since Unix epoch).
* Replace all timestamp props in one update.
*/
public setTimestamp(ts: number): void {
this.updateProps({ ts });
}
/**
* Update the optional received timestamp (milliseconds since Unix epoch).
*/
public setReceivedTimestamp(receivedTs?: number): void {
this.updateProps({ receivedTs });
}
/**
* Update display formatting options for the rendered timestamp.
*/
public setDisplayOptions(options: {
showTwelveHour?: boolean;
showFullDate?: boolean;
showSeconds?: boolean;
showRelative?: boolean;
}): void {
this.updateProps(options);
}
/**
* Enable or disable the tooltip rendering.
*/
public setTooltipInhibited(inhibitTooltip?: boolean): void {
this.updateProps({ inhibitTooltip });
}
/**
* Update the optional href for link rendering.
*/
public setHref(href?: string): void {
this.updateProps({ href });
}
/**
* Update click and context-menu handlers for the rendered element.
*/
public setHandlers(handlers: {
onClick?: MouseEventHandler<HTMLElement>;
onContextMenu?: MouseEventHandler<HTMLElement>;
}): void {
this.updateProps(handlers);
public setProps(props: MessageTimestampViewModelProps): void {
this.replaceProps(props);
}
}