Notify to resize the timeline when the pinned message banner is displayed or hidden (#28654)
This commit is contained in:
parent
5686666ad2
commit
dbdb23f6bc
3 changed files with 86 additions and 6 deletions
|
@ -2372,7 +2372,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
const pinnedMessageBanner = (
|
const pinnedMessageBanner = (
|
||||||
<PinnedMessageBanner room={this.state.room} permalinkCreator={this.permalinkCreator} />
|
<PinnedMessageBanner
|
||||||
|
room={this.state.room}
|
||||||
|
permalinkCreator={this.permalinkCreator}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
let messageComposer;
|
let messageComposer;
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* 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 PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid";
|
||||||
import { Button } from "@vector-im/compound-web";
|
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 classNames from "classnames";
|
||||||
|
|
||||||
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
|
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
|
||||||
|
@ -25,6 +25,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||||
import MessageEvent from "../messages/MessageEvent";
|
import MessageEvent from "../messages/MessageEvent";
|
||||||
import PosthogTrackers from "../../../PosthogTrackers.ts";
|
import PosthogTrackers from "../../../PosthogTrackers.ts";
|
||||||
import { EventPreview } from "./EventPreview.tsx";
|
import { EventPreview } from "./EventPreview.tsx";
|
||||||
|
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The props for the {@link PinnedMessageBanner} component.
|
* The props for the {@link PinnedMessageBanner} component.
|
||||||
|
@ -38,12 +39,20 @@ interface PinnedMessageBannerProps {
|
||||||
* The room where the banner is displayed
|
* The room where the banner is displayed
|
||||||
*/
|
*/
|
||||||
room: Room;
|
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.
|
* 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 pinnedEventIds = usePinnedEvents(room);
|
||||||
const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds);
|
const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds);
|
||||||
const eventCount = pinnedEvents.length;
|
const eventCount = pinnedEvents.length;
|
||||||
|
@ -56,6 +65,8 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
|
||||||
}, [eventCount]);
|
}, [eventCount]);
|
||||||
|
|
||||||
const pinnedEvent = pinnedEvents[currentEventIndex];
|
const pinnedEvent = pinnedEvents[currentEventIndex];
|
||||||
|
useNotifyTimeline(pinnedEvent, resizeNotifier);
|
||||||
|
|
||||||
if (!pinnedEvent) return null;
|
if (!pinnedEvent) return null;
|
||||||
|
|
||||||
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
|
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
|
||||||
|
@ -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;
|
const MAX_INDICATORS = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@ import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelSto
|
||||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||||
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
||||||
import { Action } from "../../../../../src/dispatcher/actions";
|
import { Action } from "../../../../../src/dispatcher/actions";
|
||||||
|
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier.ts";
|
||||||
|
|
||||||
describe("<PinnedMessageBanner />", () => {
|
describe("<PinnedMessageBanner />", () => {
|
||||||
const userId = "@alice:server.org";
|
const userId = "@alice:server.org";
|
||||||
|
@ -28,10 +29,12 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
let mockClient: MatrixClient;
|
let mockClient: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let permalinkCreator: RoomPermalinkCreator;
|
let permalinkCreator: RoomPermalinkCreator;
|
||||||
|
let resizeNotifier: ResizeNotifier;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClient = stubClient();
|
mockClient = stubClient();
|
||||||
room = new Room(roomId, mockClient, userId);
|
room = new Room(roomId, mockClient, userId);
|
||||||
permalinkCreator = new RoomPermalinkCreator(room);
|
permalinkCreator = new RoomPermalinkCreator(room);
|
||||||
|
resizeNotifier = new ResizeNotifier();
|
||||||
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
|
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,7 +80,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
*/
|
*/
|
||||||
function renderBanner() {
|
function renderBanner() {
|
||||||
return render(
|
return render(
|
||||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
|
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||||
withClientContextRenderOptions(mockClient),
|
withClientContextRenderOptions(mockClient),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -145,7 +148,9 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
event3.getId()!,
|
event3.getId()!,
|
||||||
]);
|
]);
|
||||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
|
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();
|
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -206,6 +211,42 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
expect(asFragment()).toMatchSnapshot();
|
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", () => {
|
describe("Right button", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
||||||
|
@ -217,6 +258,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||||
|
|
||||||
renderBanner();
|
renderBanner();
|
||||||
|
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -228,6 +271,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
renderBanner();
|
renderBanner();
|
||||||
|
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -239,6 +284,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
renderBanner();
|
renderBanner();
|
||||||
|
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -263,6 +310,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
renderBanner();
|
renderBanner();
|
||||||
|
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||||
|
|
||||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue