Skip to content

Commit dbdb23f

Browse files
authored
Notify to resize the timeline when the pinned message banner is displayed or hidden (element-hq#28654)
1 parent 5686666 commit dbdb23f

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

src/components/structures/RoomView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2372,7 +2372,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
23722372
);
23732373

23742374
const pinnedMessageBanner = (
2375-
<PinnedMessageBanner room={this.state.room} permalinkCreator={this.permalinkCreator} />
2375+
<PinnedMessageBanner
2376+
room={this.state.room}
2377+
permalinkCreator={this.permalinkCreator}
2378+
resizeNotifier={this.props.resizeNotifier}
2379+
/>
23762380
);
23772381

23782382
let messageComposer;

src/components/views/rooms/PinnedMessageBanner.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import React, { JSX, useEffect, useState } from "react";
9+
import React, { JSX, useEffect, useRef, useState } from "react";
1010
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
1111
import { Button } from "@vector-im/compound-web";
12-
import { Room } from "matrix-js-sdk/src/matrix";
12+
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
1313
import classNames from "classnames";
1414

1515
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
@@ -25,6 +25,7 @@ import { Action } from "../../../dispatcher/actions";
2525
import MessageEvent from "../messages/MessageEvent";
2626
import PosthogTrackers from "../../../PosthogTrackers.ts";
2727
import { EventPreview } from "./EventPreview.tsx";
28+
import ResizeNotifier from "../../../utils/ResizeNotifier";
2829

2930
/**
3031
* The props for the {@link PinnedMessageBanner} component.
@@ -38,12 +39,20 @@ interface PinnedMessageBannerProps {
3839
* The room where the banner is displayed
3940
*/
4041
room: Room;
42+
/**
43+
* The resize notifier to notify the timeline to resize itself when the banner is displayed or hidden.
44+
*/
45+
resizeNotifier: ResizeNotifier;
4146
}
4247

4348
/**
4449
* A banner that displays the pinned messages in a room.
4550
*/
46-
export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBannerProps): JSX.Element | null {
51+
export function PinnedMessageBanner({
52+
room,
53+
permalinkCreator,
54+
resizeNotifier,
55+
}: PinnedMessageBannerProps): JSX.Element | null {
4756
const pinnedEventIds = usePinnedEvents(room);
4857
const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds);
4958
const eventCount = pinnedEvents.length;
@@ -56,6 +65,8 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
5665
}, [eventCount]);
5766

5867
const pinnedEvent = pinnedEvents[currentEventIndex];
68+
useNotifyTimeline(pinnedEvent, resizeNotifier);
69+
5970
if (!pinnedEvent) return null;
6071

6172
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
@@ -128,6 +139,23 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
128139
);
129140
}
130141

142+
/**
143+
* When the banner is displayed or hidden, we want to notify the timeline to resize itself.
144+
* @param pinnedEvent
145+
* @param resizeNotifier
146+
*/
147+
function useNotifyTimeline(pinnedEvent: MatrixEvent | null, resizeNotifier: ResizeNotifier): void {
148+
const previousEvent = useRef<MatrixEvent | null>(null);
149+
useEffect(() => {
150+
// If we switch from a pinned message to no pinned message or the opposite, we want to resize the timeline
151+
if ((previousEvent.current && !pinnedEvent) || (!previousEvent.current && pinnedEvent)) {
152+
resizeNotifier.notifyTimelineHeightChanged();
153+
}
154+
155+
previousEvent.current = pinnedEvent;
156+
}, [pinnedEvent, resizeNotifier]);
157+
}
158+
131159
const MAX_INDICATORS = 3;
132160

133161
/**

test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelSto
2020
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
2121
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
2222
import { Action } from "../../../../../src/dispatcher/actions";
23+
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier.ts";
2324

2425
describe("<PinnedMessageBanner />", () => {
2526
const userId = "@alice:server.org";
@@ -28,10 +29,12 @@ describe("<PinnedMessageBanner />", () => {
2829
let mockClient: MatrixClient;
2930
let room: Room;
3031
let permalinkCreator: RoomPermalinkCreator;
32+
let resizeNotifier: ResizeNotifier;
3133
beforeEach(() => {
3234
mockClient = stubClient();
3335
room = new Room(roomId, mockClient, userId);
3436
permalinkCreator = new RoomPermalinkCreator(room);
37+
resizeNotifier = new ResizeNotifier();
3538
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
3639
});
3740

@@ -77,7 +80,7 @@ describe("<PinnedMessageBanner />", () => {
7780
*/
7881
function renderBanner() {
7982
return render(
80-
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
83+
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
8184
withClientContextRenderOptions(mockClient),
8285
);
8386
}
@@ -145,7 +148,9 @@ describe("<PinnedMessageBanner />", () => {
145148
event3.getId()!,
146149
]);
147150
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
148-
rerender(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
151+
rerender(
152+
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
153+
);
149154
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
150155
expect(asFragment()).toMatchSnapshot();
151156
});
@@ -206,6 +211,42 @@ describe("<PinnedMessageBanner />", () => {
206211
expect(asFragment()).toMatchSnapshot();
207212
});
208213

214+
describe("Notify the timeline to resize", () => {
215+
beforeEach(() => {
216+
jest.spyOn(resizeNotifier, "notifyTimelineHeightChanged");
217+
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
218+
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]);
219+
});
220+
221+
it("should notify the timeline to resize when we display the banner", async () => {
222+
renderBanner();
223+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
224+
// The banner is displayed, so we need to resize the timeline
225+
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
226+
227+
await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." }));
228+
await expect(screen.findByText("First pinned message")).resolves.toBeVisible();
229+
// The banner is already displayed, so we don't need to resize the timeline
230+
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
231+
});
232+
233+
it("should notify the timeline to resize when we hide the banner", async () => {
234+
const { rerender } = renderBanner();
235+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
236+
// The banner is displayed, so we need to resize the timeline
237+
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
238+
239+
// The banner has no event to display and is hidden
240+
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]);
241+
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]);
242+
rerender(
243+
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
244+
);
245+
// The timeline should be resized
246+
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(2);
247+
});
248+
});
249+
209250
describe("Right button", () => {
210251
beforeEach(() => {
211252
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
@@ -217,6 +258,8 @@ describe("<PinnedMessageBanner />", () => {
217258
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
218259

219260
renderBanner();
261+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
262+
220263
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
221264
});
222265

@@ -228,6 +271,8 @@ describe("<PinnedMessageBanner />", () => {
228271
});
229272

230273
renderBanner();
274+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
275+
231276
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
232277
});
233278

@@ -239,6 +284,8 @@ describe("<PinnedMessageBanner />", () => {
239284
});
240285

241286
renderBanner();
287+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
288+
242289
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
243290
});
244291

@@ -263,6 +310,7 @@ describe("<PinnedMessageBanner />", () => {
263310
});
264311

265312
renderBanner();
313+
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
266314
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
267315

268316
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);

0 commit comments

Comments
 (0)