Skip to content

Commit

Permalink
Notify to resize the timeline when the pinned message banner is displ…
Browse files Browse the repository at this point in the history
…ayed or hidden
  • Loading branch information
florianduros committed Dec 5, 2024
1 parent 9bf3d22 commit 9ebc38e
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2372,7 +2372,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
);

const pinnedMessageBanner = (
<PinnedMessageBanner room={this.state.room} permalinkCreator={this.permalinkCreator} />
<PinnedMessageBanner
room={this.state.room}
permalinkCreator={this.permalinkCreator}
resizeNotifier={this.props.resizeNotifier}
/>
);

let messageComposer;
Expand Down
34 changes: 31 additions & 3 deletions src/components/views/rooms/PinnedMessageBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
* Please see LICENSE files in the repository root for full details.
*/

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

import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
Expand All @@ -25,6 +25,7 @@ import { Action } from "../../../dispatcher/actions";
import MessageEvent from "../messages/MessageEvent";
import PosthogTrackers from "../../../PosthogTrackers.ts";
import { EventPreview } from "./EventPreview.tsx";
import ResizeNotifier from "../../../utils/ResizeNotifier";

/**
* The props for the {@link PinnedMessageBanner} component.
Expand All @@ -38,12 +39,20 @@ interface PinnedMessageBannerProps {
* The room where the banner is displayed
*/
room: Room;
/**
* The resize notifier to notify the timeline to resize itself when the banner is displayed or hidden.
*/
resizeNotifier: ResizeNotifier;
}

/**
* A banner that displays the pinned messages in a room.
*/
export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBannerProps): JSX.Element | null {
export function PinnedMessageBanner({
room,
permalinkCreator,
resizeNotifier,
}: PinnedMessageBannerProps): JSX.Element | null {
const pinnedEventIds = usePinnedEvents(room);
const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds);
const eventCount = pinnedEvents.length;
Expand All @@ -56,6 +65,8 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
}, [eventCount]);

const pinnedEvent = pinnedEvents[currentEventIndex];
useNotifyTimeline(pinnedEvent, resizeNotifier);

if (!pinnedEvent) return null;

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

/**
* When the banner is displayed or hidden, we want to notify the timeline to resize itself.
* @param pinnedEvent
* @param resizeNotifier
*/
function useNotifyTimeline(pinnedEvent: MatrixEvent | null, resizeNotifier: ResizeNotifier): void {
const previousEvent = useRef<MatrixEvent | null>(null);
useEffect(() => {
// If we switch from a pinned message to no pinned message or the opposite, we want to resize the timeline
if ((previousEvent.current && !pinnedEvent) || (!previousEvent.current && pinnedEvent)) {
resizeNotifier.notifyTimelineHeightChanged();
}

previousEvent.current = pinnedEvent;
}, [pinnedEvent, resizeNotifier]);
}

const MAX_INDICATORS = 3;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelSto
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
import { Action } from "../../../../../src/dispatcher/actions";
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier.ts";

describe("<PinnedMessageBanner />", () => {
const userId = "@alice:server.org";
Expand All @@ -28,10 +29,12 @@ describe("<PinnedMessageBanner />", () => {
let mockClient: MatrixClient;
let room: Room;
let permalinkCreator: RoomPermalinkCreator;
let resizeNotifier: ResizeNotifier;
beforeEach(() => {
mockClient = stubClient();
room = new Room(roomId, mockClient, userId);
permalinkCreator = new RoomPermalinkCreator(room);
resizeNotifier = new ResizeNotifier();
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
});

Expand Down Expand Up @@ -77,7 +80,7 @@ describe("<PinnedMessageBanner />", () => {
*/
function renderBanner() {
return render(
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
withClientContextRenderOptions(mockClient),
);
}
Expand Down Expand Up @@ -145,7 +148,9 @@ describe("<PinnedMessageBanner />", () => {
event3.getId()!,
]);
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
rerender(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
rerender(
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
);
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
expect(asFragment()).toMatchSnapshot();
});
Expand Down Expand Up @@ -206,6 +211,42 @@ describe("<PinnedMessageBanner />", () => {
expect(asFragment()).toMatchSnapshot();
});

describe("Notify the timeline to resize", () => {
beforeEach(() => {
jest.spyOn(resizeNotifier, "notifyTimelineHeightChanged");
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]);
});

it("should notify the timeline to resize when we display the banner", async () => {
renderBanner();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
// The banner is displayed, so we need to resize the timeline
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);

await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." }));
await expect(screen.findByText("First pinned message")).resolves.toBeVisible();
// The banner is already displayed, so we don't need to resize the timeline
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
});

it("should notify the timeline to resize when we hide the banner", async () => {
const { rerender } = renderBanner();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
// The banner is displayed, so we need to resize the timeline
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);

// The banner has no event to display and is hidden
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]);
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]);
rerender(
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
);
// The timeline should be resized
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(2);
});
});

describe("Right button", () => {
beforeEach(() => {
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
Expand All @@ -217,6 +258,8 @@ describe("<PinnedMessageBanner />", () => {
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);

renderBanner();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();

expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
});

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

renderBanner();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();

expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
});

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

renderBanner();
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();

expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
});

Expand All @@ -263,6 +310,8 @@ describe("<PinnedMessageBanner />", () => {
});

renderBanner();

await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();

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

0 comments on commit 9ebc38e

Please sign in to comment.