Make shared components aware of layout and density settings (#33368)

* Add shared event presentation context

* Add app-web event presentation mapper

* Wire event presentation provider into app timelines

* Add Storybook controls for event layout and density

* Wire compact density through app/web event presentation provider

* Use event presentation density for URL previews

* Move TileErrorView layout to event presentation context

* Minor fix and updated snapshot

* Updated snapshots for url preview group

* Prettier fix

* Restore removed story to fix missing playwright test

* Updates after review comments

* Fix prettier issue
This commit is contained in:
rbondesson
2026-05-06 07:30:46 +02:00
committed by GitHub
parent 7e26c2d144
commit 1f71a3a3fe
23 changed files with 379 additions and 209 deletions
@@ -34,6 +34,7 @@ import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import Measured from "../views/elements/Measured";
import EmptyState from "../views/right_panel/EmptyState";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
import { EventPresentationContextProvider } from "../../utils/EventPresentationContextProvider";
interface IProps {
roomId: string;
@@ -286,15 +287,17 @@ class FilePanel extends React.Component<IProps, IState> {
>
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
<SearchWarning isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} />
<TimelinePanel
manageReadReceipts={false}
manageReadMarkers={false}
timelineSet={this.state.timelineSet}
showUrlPreview={false}
onPaginationRequest={this.onPaginationRequest}
empty={emptyState}
layout={Layout.Group}
/>
<EventPresentationContextProvider layout={Layout.Group}>
<TimelinePanel
manageReadReceipts={false}
manageReadMarkers={false}
timelineSet={this.state.timelineSet}
showUrlPreview={false}
onPaginationRequest={this.onPaginationRequest}
empty={emptyState}
layout={Layout.Group}
/>
</EventPresentationContextProvider>
</BaseCard>
</ScopedRoomContextProvider>
);
+29 -26
View File
@@ -142,6 +142,7 @@ import { type RoomViewStore } from "../../stores/RoomViewStore.tsx";
import { RoomStatusBarViewModel } from "../../viewmodels/room/RoomStatusBar.ts";
import { EncryptionEventViewModel } from "../../viewmodels/room/timeline/event-tile/EncryptionEventViewModel.ts";
import { ModuleApi } from "../../modules/Api.ts";
import { EventPresentationContextProvider } from "../../utils/EventPresentationContextProvider";
const DEBUG = false;
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
@@ -2583,32 +2584,34 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
let messagePanel: JSX.Element | undefined;
if (!isRoomEncryptionLoading) {
messagePanel = (
<TimelinePanel
ref={this.gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={!this.state.isPeeking}
sendReadReceiptOnLoad={
!this.state.wasContextSwitch && this.props.enableReadReceiptsAndMarkersOnActivity
}
manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel}
highlightedEventId={highlightedEventId}
eventId={this.state.initialEventId}
eventScrollIntoView={this.state.initialEventScrollIntoView}
eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={this.onMessageListScroll}
onEventScrolledIntoView={this.resetJumpToEvent}
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
showUrlPreview={this.state.showUrlPreview}
className={this.messagePanelClassNames}
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.permalinkCreator}
showReactions={true}
layout={this.state.layout}
editState={this.state.editState}
enableReadReceiptsAndMarkersOnActivity={this.props.enableReadReceiptsAndMarkersOnActivity}
/>
<EventPresentationContextProvider layout={this.state.layout}>
<TimelinePanel
ref={this.gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={!this.state.isPeeking}
sendReadReceiptOnLoad={
!this.state.wasContextSwitch && this.props.enableReadReceiptsAndMarkersOnActivity
}
manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel}
highlightedEventId={highlightedEventId}
eventId={this.state.initialEventId}
eventScrollIntoView={this.state.initialEventScrollIntoView}
eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={this.onMessageListScroll}
onEventScrolledIntoView={this.resetJumpToEvent}
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
showUrlPreview={this.state.showUrlPreview}
className={this.messagePanelClassNames}
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.permalinkCreator}
showReactions={true}
layout={this.state.layout}
editState={this.state.editState}
enableReadReceiptsAndMarkersOnActivity={this.props.enableReadReceiptsAndMarkersOnActivity}
/>
</EventPresentationContextProvider>
);
}
@@ -51,6 +51,7 @@ import { type ComposerInsertPayload, ComposerType } from "../../dispatcher/paylo
import Heading from "../views/typography/Heading";
import { type ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
import { EventPresentationContextProvider } from "../../utils/EventPresentationContextProvider";
interface IProps {
room: Room;
@@ -388,32 +389,36 @@ export default class ThreadView extends React.Component<IProps, IState> {
);
}
const layout = this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group;
timeline = (
<>
<FileDropTarget parent={this.card.current} onFileDrop={this.onFileDrop} room={this.props.room} />
<TimelinePanel
key={this.state.thread.id}
ref={this.timelinePanel}
showReadReceipts={this.context.showReadReceipts}
manageReadReceipts={true}
manageReadMarkers={true}
sendReadReceiptOnLoad={true}
timelineSet={this.state.thread.timelineSet}
showUrlPreview={this.context.showUrlPreview}
// ThreadView doesn't support IRC layout at this time
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel"
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
editState={this.state.editState}
eventId={this.props.initialEvent?.getId()}
highlightedEventId={highlightedEventId}
eventScrollIntoView={this.props.initialEventScrollIntoView}
onEventScrolledIntoView={this.resetJumpToEvent}
/>
<EventPresentationContextProvider layout={layout}>
<TimelinePanel
key={this.state.thread.id}
ref={this.timelinePanel}
showReadReceipts={this.context.showReadReceipts}
manageReadReceipts={true}
manageReadMarkers={true}
sendReadReceiptOnLoad={true}
timelineSet={this.state.thread.timelineSet}
showUrlPreview={this.context.showUrlPreview}
// ThreadView doesn't support IRC layout at this time
layout={layout}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel"
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
editState={this.state.editState}
eventId={this.props.initialEvent?.getId()}
highlightedEventId={highlightedEventId}
eventScrollIntoView={this.props.initialEventScrollIntoView}
onEventScrolledIntoView={this.resetJumpToEvent}
/>
</EventPresentationContextProvider>
</>
);
} else {
@@ -38,6 +38,7 @@ import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPaylo
import Measured from "../elements/Measured";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { ScopedRoomContextProvider } from "../../../contexts/ScopedRoomContext.tsx";
import { EventPresentationContextProvider } from "../../../utils/EventPresentationContextProvider";
interface IProps {
room: Room;
@@ -197,6 +198,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
const myMembership = this.props.room.getMyMembership();
const showComposer = myMembership === KnownMembership.Join;
const layout = this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group;
return (
<ScopedRoomContextProvider
@@ -215,27 +217,29 @@ export default class TimelineCard extends React.Component<IProps, IState> {
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
<div className="mx_TimelineCard_timeline">
{jumpToBottom}
<TimelinePanel
ref={this.timelinePanel}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={true}
manageReadMarkers={false} // No RM support in the TimelineCard
sendReadReceiptOnLoad={true}
timelineSet={this.props.timelineSet}
showUrlPreview={this.context.showUrlPreview}
// The right panel timeline (and therefore threads) don't support IRC layout at this time
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel"
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
editState={this.state.editState}
eventId={this.state.initialEventId}
highlightedEventId={highlightedEventId}
onScroll={this.onScroll}
/>
<EventPresentationContextProvider layout={layout}>
<TimelinePanel
ref={this.timelinePanel}
showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={true}
manageReadMarkers={false} // No RM support in the TimelineCard
sendReadReceiptOnLoad={true}
timelineSet={this.props.timelineSet}
showUrlPreview={this.context.showUrlPreview}
// The right panel timeline (and therefore threads) don't support IRC layout at this time
layout={layout}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel"
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
editState={this.state.editState}
eventId={this.state.initialEventId}
highlightedEventId={highlightedEventId}
onScroll={this.onScroll}
/>
</EventPresentationContextProvider>
</div>
{isUploading && <UploadBar room={this.props.room} relation={this.props.composerRelation} />}
@@ -57,7 +57,6 @@ import {
ReactionsRowButtonView,
ReactionsRowView,
TileErrorView,
type TileErrorViewLayout,
useViewModel,
} from "@element-hq/web-shared-components";
@@ -1579,24 +1578,17 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
*/
interface EventTileErrorFallbackProps {
error: Error;
layout: Layout;
mxEvent: MatrixEvent;
}
function EventTileErrorFallback({ error, layout, mxEvent }: Readonly<EventTileErrorFallbackProps>): JSX.Element {
function EventTileErrorFallback({ error, mxEvent }: Readonly<EventTileErrorFallbackProps>): JSX.Element {
const developerMode = useSettingValue("developerMode");
const vm = useCreateAutoDisposedViewModel(
() => new TileErrorViewModel({ error, layout: layout as TileErrorViewLayout, mxEvent, developerMode }),
);
const vm = useCreateAutoDisposedViewModel(() => new TileErrorViewModel({ error, mxEvent, developerMode }));
useEffect(() => {
vm.setError(error);
}, [error, vm]);
useEffect(() => {
vm.setLayout(layout as TileErrorViewLayout);
}, [layout, vm]);
useEffect(() => {
vm.setDeveloperMode(developerMode);
}, [developerMode, vm]);
@@ -1606,7 +1598,6 @@ function EventTileErrorFallback({ error, layout, mxEvent }: Readonly<EventTileEr
interface EventTileErrorBoundaryProps {
children: ReactNode;
layout: Layout;
mxEvent: MatrixEvent;
}
@@ -1626,13 +1617,7 @@ class EventTileErrorBoundary extends React.Component<EventTileErrorBoundaryProps
public render(): ReactNode {
if (this.state.error) {
return (
<EventTileErrorFallback
error={this.state.error}
layout={this.props.layout}
mxEvent={this.props.mxEvent}
/>
);
return <EventTileErrorFallback error={this.state.error} mxEvent={this.props.mxEvent} />;
}
return this.props.children;
@@ -1642,7 +1627,7 @@ class EventTileErrorBoundary extends React.Component<EventTileErrorBoundaryProps
// Wrap all event tiles with the tile error boundary so that any throws even during construction are captured
const SafeEventTile = (props: EventTileProps): JSX.Element => {
return (
<EventTileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
<EventTileErrorBoundary mxEvent={props.mxEvent}>
<UnwrappedEventTile {...props} />
</EventTileErrorBoundary>
);