Improve performance of RoomContext in RoomHeader

This allows a component to subscribe to only part of the RoomContext so they do not need to re-render on every single change

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-11-27 09:10:29 +00:00
parent de820e11fc
commit ea8393304b
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
35 changed files with 203 additions and 136 deletions

View file

@ -34,6 +34,7 @@ import { Layout } from "../../settings/enums/Layout";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import Measured from "../views/elements/Measured"; import Measured from "../views/elements/Measured";
import EmptyState from "../views/right_panel/EmptyState"; import EmptyState from "../views/right_panel/EmptyState";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
roomId: string; roomId: string;
@ -269,12 +270,10 @@ class FilePanel extends React.Component<IProps, IState> {
if (this.state.timelineSet) { if (this.state.timelineSet) {
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider
value={{ {...this.context}
...this.context, timelineRenderingType={TimelineRenderingType.File}
timelineRenderingType: TimelineRenderingType.File, narrow={this.state.narrow}
narrow: this.state.narrow,
}}
> >
<BaseCard <BaseCard
className="mx_FilePanel" className="mx_FilePanel"
@ -298,16 +297,11 @@ class FilePanel extends React.Component<IProps, IState> {
layout={Layout.Group} layout={Layout.Group}
/> />
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} else { } else {
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider {...this.context} timelineRenderingType={TimelineRenderingType.File}>
value={{
...this.context,
timelineRenderingType: TimelineRenderingType.File,
}}
>
<BaseCard <BaseCard
className="mx_FilePanel" className="mx_FilePanel"
onClose={this.props.onClose} onClose={this.props.onClose}
@ -315,7 +309,7 @@ class FilePanel extends React.Component<IProps, IState> {
> >
<Spinner /> <Spinner />
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
} }

View file

@ -19,6 +19,7 @@ import { Layout } from "../../settings/enums/Layout";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import Measured from "../views/elements/Measured"; import Measured from "../views/elements/Measured";
import EmptyState from "../views/right_panel/EmptyState"; import EmptyState from "../views/right_panel/EmptyState";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
onClose(): void; onClose(): void;
@ -79,12 +80,10 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
} }
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider
value={{ {...this.context}
...this.context, timelineRenderingType={TimelineRenderingType.Notification}
timelineRenderingType: TimelineRenderingType.Notification, narrow={this.state.narrow}
narrow: this.state.narrow,
}}
> >
<BaseCard <BaseCard
header={_t("notifications|enable_prompt_toast_title")} header={_t("notifications|enable_prompt_toast_title")}
@ -99,7 +98,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />} {this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
{content} {content}
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
} }

View file

@ -26,7 +26,7 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
import ResizeNotifier from "../../utils/ResizeNotifier"; import ResizeNotifier from "../../utils/ResizeNotifier";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import RoomContext from "../../contexts/RoomContext"; import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
const DEBUG = false; const DEBUG = false;
let debuglog = function (msg: string): void {}; let debuglog = function (msg: string): void {};
@ -53,7 +53,7 @@ interface Props {
export const RoomSearchView = forwardRef<ScrollPanel, Props>( export const RoomSearchView = forwardRef<ScrollPanel, Props>(
({ term, scope, promise, abortController, resizeNotifier, className, onUpdate, inProgress }: Props, ref) => { ({ term, scope, promise, abortController, resizeNotifier, className, onUpdate, inProgress }: Props, ref) => {
const client = useContext(MatrixClientContext); const client = useContext(MatrixClientContext);
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("showHiddenEvents");
const [highlights, setHighlights] = useState<string[] | null>(null); const [highlights, setHighlights] = useState<string[] | null>(null);
const [results, setResults] = useState<ISearchResults | null>(null); const [results, setResults] = useState<ISearchResults | null>(null);
const aborted = useRef(false); const aborted = useRef(false);

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, useContext } from "react"; import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { import {
IRecommendedVersion, IRecommendedVersion,
@ -54,7 +54,7 @@ import WidgetEchoStore from "../../stores/WidgetEchoStore";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import RoomContext, { TimelineRenderingType, MainSplitContentType } from "../../contexts/RoomContext"; import { TimelineRenderingType, MainSplitContentType } from "../../contexts/RoomContext";
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils"; import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { IMatrixClientCreds } from "../../MatrixClientPeg"; import { IMatrixClientCreds } from "../../MatrixClientPeg";
@ -126,6 +126,7 @@ import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import { onView3pidInvite } from "../../stores/right-panel/action-handlers"; import { onView3pidInvite } from "../../stores/right-panel/action-handlers";
import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel"; import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel";
import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner"; import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner";
import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext";
const DEBUG = false; const DEBUG = false;
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
@ -246,6 +247,7 @@ interface LocalRoomViewProps {
permalinkCreator: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
roomView: RefObject<HTMLElement>; roomView: RefObject<HTMLElement>;
onFileDrop: (dataTransfer: DataTransfer) => Promise<void>; onFileDrop: (dataTransfer: DataTransfer) => Promise<void>;
mainSplitContentType: MainSplitContentType;
} }
/** /**
@ -255,7 +257,7 @@ interface LocalRoomViewProps {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
function LocalRoomView(props: LocalRoomViewProps): ReactElement { function LocalRoomView(props: LocalRoomViewProps): ReactElement {
const context = useContext(RoomContext); const context = useScopedRoomContext("room");
const room = context.room as LocalRoom; const room = context.room as LocalRoom;
const encryptionEvent = props.localRoom.currentState.getStateEvents(EventType.RoomEncryption)[0]; const encryptionEvent = props.localRoom.currentState.getStateEvents(EventType.RoomEncryption)[0];
let encryptionTile: ReactNode; let encryptionTile: ReactNode;
@ -323,6 +325,7 @@ interface ILocalRoomCreateLoaderProps {
localRoom: LocalRoom; localRoom: LocalRoom;
names: string; names: string;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
mainSplitContentType: MainSplitContentType;
} }
/** /**
@ -1959,35 +1962,41 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.room || !this.context?.client) return null; if (!this.state.room || !this.context?.client) return null;
const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId()); const names = this.state.room.getDefaultRoomName(this.context.client.getSafeUserId());
return ( return (
<RoomContext.Provider value={this.state}> <ScopedRoomContextProvider {...this.state}>
<LocalRoomCreateLoader localRoom={localRoom} names={names} resizeNotifier={this.props.resizeNotifier} /> <LocalRoomCreateLoader
</RoomContext.Provider> localRoom={localRoom}
names={names}
resizeNotifier={this.props.resizeNotifier}
mainSplitContentType={this.state.mainSplitContentType}
/>
</ScopedRoomContextProvider>
); );
} }
private renderLocalRoomView(localRoom: LocalRoom): ReactNode { private renderLocalRoomView(localRoom: LocalRoom): ReactNode {
return ( return (
<RoomContext.Provider value={this.state}> <ScopedRoomContextProvider {...this.state}>
<LocalRoomView <LocalRoomView
localRoom={localRoom} localRoom={localRoom}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.permalinkCreator} permalinkCreator={this.permalinkCreator}
roomView={this.roomView} roomView={this.roomView}
onFileDrop={this.onFileDrop} onFileDrop={this.onFileDrop}
mainSplitContentType={this.state.mainSplitContentType}
/> />
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
private renderWaitingForThirdPartyRoomView(inviteEvent: MatrixEvent): ReactNode { private renderWaitingForThirdPartyRoomView(inviteEvent: MatrixEvent): ReactNode {
return ( return (
<RoomContext.Provider value={this.state}> <ScopedRoomContextProvider {...this.state}>
<WaitingForThirdPartyRoomView <WaitingForThirdPartyRoomView
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
roomView={this.roomView} roomView={this.roomView}
inviteEvent={inviteEvent} inviteEvent={inviteEvent}
/> />
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
@ -2516,7 +2525,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
return ( return (
<RoomContext.Provider value={this.state}> <ScopedRoomContextProvider {...this.state}>
<div className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}> <div className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
{showChatEffects && this.roomView.current && ( {showChatEffects && this.roomView.current && (
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} /> <EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
@ -2543,7 +2552,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</MainSplit> </MainSplit>
</ErrorBoundary> </ErrorBoundary>
</div> </div>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
} }

View file

@ -20,7 +20,7 @@ import MatrixClientContext, { useMatrixClientContext } from "../../contexts/Matr
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton"; import { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton";
import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from "./ContextMenu"; import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from "./ContextMenu";
import RoomContext, { TimelineRenderingType, useRoomContext } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import TimelinePanel from "./TimelinePanel"; import TimelinePanel from "./TimelinePanel";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
@ -30,6 +30,7 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
import Spinner from "../views/elements/Spinner"; import Spinner from "../views/elements/Spinner";
import { clearRoomNotification } from "../../utils/notifications"; import { clearRoomNotification } from "../../utils/notifications";
import EmptyState from "../views/right_panel/EmptyState"; import EmptyState from "../views/right_panel/EmptyState";
import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
roomId: string; roomId: string;
@ -68,7 +69,7 @@ export const ThreadPanelHeader: React.FC<{
setFilterOption: (filterOption: ThreadFilterType) => void; setFilterOption: (filterOption: ThreadFilterType) => void;
}> = ({ filterOption, setFilterOption }) => { }> = ({ filterOption, setFilterOption }) => {
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("room");
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>();
const options: readonly ThreadPanelHeaderOption[] = [ const options: readonly ThreadPanelHeaderOption[] = [
{ {
@ -184,13 +185,11 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
}, [timelineSet, timelinePanel]); }, [timelineSet, timelinePanel]);
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider
value={{ {...roomContext}
...roomContext, timelineRenderingType={TimelineRenderingType.ThreadsList}
timelineRenderingType: TimelineRenderingType.ThreadsList, showHiddenEvents={true}
showHiddenEvents: true, narrow={narrow}
narrow,
}}
> >
<BaseCard <BaseCard
header={ header={
@ -241,7 +240,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
</div> </div>
)} )}
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
}; };
export default ThreadPanel; export default ThreadPanel;

View file

@ -51,6 +51,7 @@ import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/C
import Heading from "../views/typography/Heading"; import Heading from "../views/typography/Heading";
import { SdkContextClass } from "../../contexts/SDKContext"; import { SdkContextClass } from "../../contexts/SDKContext";
import { ThreadPayload } from "../../dispatcher/payloads/ThreadPayload"; import { ThreadPayload } from "../../dispatcher/payloads/ThreadPayload";
import { ScopedRoomContextProvider } from "../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
room: Room; room: Room;
@ -422,14 +423,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
} }
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider
value={{ {...this.context}
...this.context, timelineRenderingType={TimelineRenderingType.Thread}
timelineRenderingType: TimelineRenderingType.Thread, threadId={this.state.thread?.id}
threadId: this.state.thread?.id, liveTimeline={this.state?.thread?.timelineSet?.getLiveTimeline()}
liveTimeline: this.state?.thread?.timelineSet?.getLiveTimeline(), narrow={this.state.narrow}
narrow: this.state.narrow,
}}
> >
<BaseCard <BaseCard
className={classNames("mx_ThreadView mx_ThreadPanel", { className={classNames("mx_ThreadView mx_ThreadPanel", {
@ -463,7 +462,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
/> />
)} )}
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
} }

View file

@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React, { RefObject } from "react"; import React, { RefObject } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { useRoomContext } from "../../contexts/RoomContext";
import ResizeNotifier from "../../utils/ResizeNotifier"; import ResizeNotifier from "../../utils/ResizeNotifier";
import ErrorBoundary from "../views/elements/ErrorBoundary"; import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomHeader from "../views/rooms/RoomHeader"; import RoomHeader from "../views/rooms/RoomHeader";
@ -19,6 +18,7 @@ import NewRoomIntro from "../views/rooms/NewRoomIntro";
import { UnwrappedEventTile } from "../views/rooms/EventTile"; import { UnwrappedEventTile } from "../views/rooms/EventTile";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
interface Props { interface Props {
roomView: RefObject<HTMLElement>; roomView: RefObject<HTMLElement>;
@ -32,7 +32,7 @@ interface Props {
* To avoid UTDs, users are shown a waiting room until the others have joined. * To avoid UTDs, users are shown a waiting room until the others have joined.
*/ */
export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resizeNotifier, inviteEvent }) => { export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resizeNotifier, inviteEvent }) => {
const context = useRoomContext(); const context = useScopedRoomContext("room");
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
return ( return (

View file

@ -16,10 +16,10 @@ import { Avatar } from "@vector-im/compound-web";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
import RoomContext from "../../../contexts/RoomContext";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
name?: React.ComponentProps<typeof Avatar>["name"]; // The name (first initial used as default) name?: React.ComponentProps<typeof Avatar>["name"]; // The name (first initial used as default)
@ -57,8 +57,8 @@ const calculateUrls = (url?: string | null, urls?: string[], lowBandwidth = fals
const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [string, () => void] => { const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [string, () => void] => {
// Since this is a hot code path and the settings store can be slow, we // Since this is a hot code path and the settings store can be slow, we
// use the cached lowBandwidth value from the room context if it exists // use the cached lowBandwidth value from the room context if it exists
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("lowBandwidth");
const lowBandwidth = roomContext ? roomContext.lowBandwidth : SettingsStore.getValue("lowBandwidth"); const lowBandwidth = roomContext?.lowBandwidth ?? SettingsStore.getValue("lowBandwidth");
const [imageUrls, setUrls] = useState<string[]>(calculateUrls(url, urls, lowBandwidth)); const [imageUrls, setUrls] = useState<string[]>(calculateUrls(url, urls, lowBandwidth));
const [urlsIndex, setIndex] = useState<number>(0); const [urlsIndex, setIndex] = useState<number>(0);

View file

@ -18,7 +18,6 @@ import { _t } from "../../../languageHandler";
import { isAppWidget } from "../../../stores/WidgetStore"; import { isAppWidget } from "../../../stores/WidgetStore";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import RoomContext from "../../../contexts/RoomContext";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
@ -30,6 +29,7 @@ import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayo
import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream"; import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream";
import { ModuleRunner } from "../../../modules/ModuleRunner"; import { ModuleRunner } from "../../../modules/ModuleRunner";
import { ElementWidget } from "../../../stores/widgets/StopGapWidget"; import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps extends Omit<ComponentProps<typeof IconizedContextMenu>, "children"> { interface IProps extends Omit<ComponentProps<typeof IconizedContextMenu>, "children"> {
app: IWidget; app: IWidget;
@ -114,7 +114,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
...props ...props
}) => { }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const { room, roomId } = useContext(RoomContext); const { room, roomId } = useScopedRoomContext("room", "roomId");
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app)); const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, roomId); const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, roomId);

View file

@ -12,12 +12,12 @@ import React, { useContext, useRef, useState, MouseEvent, ReactNode } from "reac
import { Tooltip } from "@vector-im/compound-web"; import { Tooltip } from "@vector-im/compound-web";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext";
import { useTimeout } from "../../../hooks/useTimeout"; import { useTimeout } from "../../../hooks/useTimeout";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds"; import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import Spinner from "./Spinner"; import Spinner from "./Spinner";
import { getFileChanged } from "../settings/AvatarSetting.tsx"; import { getFileChanged } from "../settings/AvatarSetting.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
export const AVATAR_SIZE = "52px"; export const AVATAR_SIZE = "52px";
@ -56,7 +56,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({
const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel; const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel;
const { room } = useContext(RoomContext); const { room } = useScopedRoomContext("room");
const canSetAvatar = const canSetAvatar =
isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId()); isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>; if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;

View file

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { useCallback, useContext } from "react"; import React, { useCallback } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent, Room, RoomState } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, Room, RoomState } from "matrix-js-sdk/src/matrix";
@ -18,10 +18,10 @@ import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import EventTileBubble from "./EventTileBubble"; import EventTileBubble from "./EventTileBubble";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import RoomContext from "../../../contexts/RoomContext";
import { useRoomState } from "../../../hooks/useRoomState"; import { useRoomState } from "../../../hooks/useRoomState";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import MatrixToPermalinkConstructor from "../../../utils/permalinks/MatrixToPermalinkConstructor"; import MatrixToPermalinkConstructor from "../../../utils/permalinks/MatrixToPermalinkConstructor";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
/** The m.room.create MatrixEvent that this tile represents */ /** The m.room.create MatrixEvent that this tile represents */
@ -40,7 +40,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
// the information inside mxEvent. This allows us the flexibility later to // the information inside mxEvent. This allows us the flexibility later to
// use a different predecessor (e.g. through MSC3946) and still display it // use a different predecessor (e.g. through MSC3946) and still display it
// in the timeline location of the create event. // in the timeline location of the create event.
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("room");
const predecessor = useRoomState( const predecessor = useRoomState(
roomContext.room, roomContext.room,
useCallback( useCallback(

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { useCallback, useEffect, JSX } from "react"; import React, { useCallback, useEffect, JSX, useContext } from "react";
import { Room, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Room, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { Button, Separator } from "@vector-im/compound-web"; import { Button, Separator } from "@vector-im/compound-web";
import classNames from "classnames"; import classNames from "classnames";
@ -18,7 +18,7 @@ import Spinner from "../elements/Spinner";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { PinnedEventTile } from "../rooms/PinnedEventTile"; import { PinnedEventTile } from "../rooms/PinnedEventTile";
import { useRoomState } from "../../../hooks/useRoomState"; import { useRoomState } from "../../../hooks/useRoomState";
import RoomContext, { TimelineRenderingType, useRoomContext } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import { ReadPinsEventId } from "./types"; import { ReadPinsEventId } from "./types";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { filterBoolean } from "../../../utils/arrays"; import { filterBoolean } from "../../../utils/arrays";
@ -27,6 +27,7 @@ import { UnpinAllDialog } from "../dialogs/UnpinAllDialog";
import EmptyState from "./EmptyState"; import EmptyState from "./EmptyState";
import { usePinnedEvents, useReadPinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; import { usePinnedEvents, useReadPinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
import PinningUtils from "../../../utils/PinningUtils.ts"; import PinningUtils from "../../../utils/PinningUtils.ts";
import { ScopedRoomContextProvider } from "../../../contexts/ScopedRoomContext.tsx";
/** /**
* List the pinned messages in a room inside a Card. * List the pinned messages in a room inside a Card.
@ -48,7 +49,7 @@ interface PinnedMessagesCardProps {
export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMessagesCardProps): JSX.Element { export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMessagesCardProps): JSX.Element {
const cli = useMatrixClientContext(); const cli = useMatrixClientContext();
const roomContext = useRoomContext(); const roomContext = useContext(RoomContext);
const pinnedEventIds = usePinnedEvents(room); const pinnedEventIds = usePinnedEvents(room);
const readPinnedEvents = useReadPinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room);
const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds); const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds);
@ -89,14 +90,9 @@ export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMe
className="mx_PinnedMessagesCard" className="mx_PinnedMessagesCard"
onClose={onClose} onClose={onClose}
> >
<RoomContext.Provider <ScopedRoomContextProvider {...roomContext} timelineRenderingType={TimelineRenderingType.Pinned}>
value={{
...roomContext,
timelineRenderingType: TimelineRenderingType.Pinned,
}}
>
{content} {content}
</RoomContext.Provider> </ScopedRoomContextProvider>
</BaseCard> </BaseCard>
); );
} }

View file

@ -51,7 +51,7 @@ import ShareDialog from "../dialogs/ShareDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { E2EStatus } from "../../../utils/ShieldUtils"; import { E2EStatus } from "../../../utils/ShieldUtils";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { TimelineRenderingType } from "../../../contexts/RoomContext";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import ExportDialog from "../dialogs/ExportDialog"; import ExportDialog from "../dialogs/ExportDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
@ -76,6 +76,7 @@ import { useTransition } from "../../../hooks/useTransition";
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
room: Room; room: Room;
@ -232,7 +233,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
}; };
const isRoomEncrypted = useIsEncrypted(cli, room); const isRoomEncrypted = useIsEncrypted(cli, room);
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
const e2eStatus = roomContext.e2eStatus; const e2eStatus = roomContext.e2eStatus;
const isVideoRoom = calcIsVideoRoom(room); const isVideoRoom = calcIsVideoRoom(room);

View file

@ -38,6 +38,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import Measured from "../elements/Measured"; import Measured from "../elements/Measured";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { SdkContextClass } from "../../../contexts/SDKContext"; import { SdkContextClass } from "../../../contexts/SDKContext";
import { ScopedRoomContextProvider } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
room: Room; room: Room;
@ -199,13 +200,11 @@ export default class TimelineCard extends React.Component<IProps, IState> {
const showComposer = myMembership === KnownMembership.Join; const showComposer = myMembership === KnownMembership.Join;
return ( return (
<RoomContext.Provider <ScopedRoomContextProvider
value={{ {...this.context}
...this.context, timelineRenderingType={this.props.timelineRenderingType ?? this.context.timelineRenderingType}
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType, liveTimeline={this.props.timelineSet?.getLiveTimeline()}
liveTimeline: this.props.timelineSet?.getLiveTimeline(), narrow={this.state.narrow}
narrow: this.state.narrow,
}}
> >
<BaseCard <BaseCard
className={this.props.classNames} className={this.props.classNames}
@ -255,7 +254,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
/> />
)} )}
</BaseCard> </BaseCard>
</RoomContext.Provider> </ScopedRoomContextProvider>
); );
} }
} }

View file

@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { useContext } from "react"; import React from "react";
import { EventTimeline } from "matrix-js-sdk/src/matrix"; import { EventTimeline } from "matrix-js-sdk/src/matrix";
import EventTileBubble from "../messages/EventTileBubble"; import EventTileBubble from "../messages/EventTileBubble";
import RoomContext from "../../../contexts/RoomContext";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
const HistoryTile: React.FC = () => { const HistoryTile: React.FC = () => {
const { room } = useContext(RoomContext); const { room } = useScopedRoomContext("room");
const oldState = room?.getLiveTimeline().getState(EventTimeline.BACKWARDS); const oldState = room?.getLiveTimeline().getState(EventTimeline.BACKWARDS);
const historyState = oldState?.getStateEvents("m.room.history_visibility")[0]?.getContent().history_visibility; const historyState = oldState?.getStateEvents("m.room.history_visibility")[0]?.getContent().history_visibility;

View file

@ -21,7 +21,6 @@ import PollCreateDialog from "../elements/PollCreateDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import ContentMessages from "../../../ContentMessages"; import ContentMessages from "../../../ContentMessages";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext";
import { useDispatcher } from "../../../hooks/useDispatcher"; import { useDispatcher } from "../../../hooks/useDispatcher";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds"; import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
@ -29,6 +28,7 @@ import { EmojiButton } from "./EmojiButton";
import { filterBoolean } from "../../../utils/arrays"; import { filterBoolean } from "../../../utils/arrays";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
addEmoji: (emoji: string) => boolean; addEmoji: (emoji: string) => boolean;
@ -54,7 +54,7 @@ export const OverflowMenuContext = createContext<OverflowMenuCloser | null>(null
const MessageComposerButtons: React.FC<IProps> = (props: IProps) => { const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
const matrixClient = useContext(MatrixClientContext); const matrixClient = useContext(MatrixClientContext);
const { room, narrow } = useContext(RoomContext); const { room, narrow } = useScopedRoomContext("room", "narrow");
const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer"); const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer");
@ -168,7 +168,7 @@ interface IUploadButtonProps {
// We put the file input outside the UploadButton component so that it doesn't get killed when the context menu closes. // We put the file input outside the UploadButton component so that it doesn't get killed when the context menu closes.
const UploadButtonContextProvider: React.FC<IUploadButtonProps> = ({ roomId, relation, children }) => { const UploadButtonContextProvider: React.FC<IUploadButtonProps> = ({ roomId, relation, children }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("timelineRenderingType");
const uploadInput = useRef<HTMLInputElement>(null); const uploadInput = useRef<HTMLInputElement>(null);
const onUploadClick = (): void => { const onUploadClick = (): void => {

View file

@ -11,7 +11,6 @@ import { EventType, Room, User, MatrixClient } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import { _t, _td, TranslationKey } from "../../../languageHandler"; import { _t, _td, TranslationKey } from "../../../languageHandler";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
@ -30,6 +29,7 @@ import { UIComponent } from "../../../settings/UIFeature";
import { privateShouldBeEncrypted } from "../../../utils/rooms"; import { privateShouldBeEncrypted } from "../../../utils/rooms";
import { LocalRoom } from "../../../models/LocalRoom"; import { LocalRoom } from "../../../models/LocalRoom";
import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite"; import { shouldEncryptRoomWithSingle3rdPartyInvite } from "../../../utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean { function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId); const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
@ -51,7 +51,7 @@ const determineIntroMessage = (room: Room, encryptedSingle3rdPartyInvite: boolea
const NewRoomIntro: React.FC = () => { const NewRoomIntro: React.FC = () => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const { room, roomId } = useContext(RoomContext); const { room, roomId } = useScopedRoomContext("room", "roomId");
if (!room || !roomId) { if (!room || !roomId) {
throw new Error("Unable to create a NewRoomIntro without room and roomId"); throw new Error("Unable to create a NewRoomIntro without room and roomId");

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { useCallback, useContext, useMemo, useState } from "react"; import React, { useCallback, useMemo, useState } from "react";
import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web"; import { Body as BodyText, Button, IconButton, Menu, MenuItem, Tooltip } from "@vector-im/compound-web";
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call"; import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call";
@ -48,10 +48,10 @@ import { CallGuestLinkButton } from "./RoomHeader/CallGuestLinkButton";
import { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
import WithPresenceIndicator, { useDmMember } from "../avatars/WithPresenceIndicator"; import WithPresenceIndicator, { useDmMember } from "../avatars/WithPresenceIndicator";
import { IOOBData } from "../../../stores/ThreepidInviteStore"; import { IOOBData } from "../../../stores/ThreepidInviteStore";
import RoomContext from "../../../contexts/RoomContext";
import { MainSplitContentType } from "../../structures/RoomView"; import { MainSplitContentType } from "../../structures/RoomView";
import defaultDispatcher from "../../../dispatcher/dispatcher.ts"; import defaultDispatcher from "../../../dispatcher/dispatcher.ts";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog.tsx"; import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
export default function RoomHeader({ export default function RoomHeader({
room, room,
@ -229,7 +229,7 @@ export default function RoomHeader({
voiceCallButton = undefined; voiceCallButton = undefined;
} }
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("mainSplitContentType");
const isVideoRoom = calcIsVideoRoom(room); const isVideoRoom = calcIsVideoRoom(room);
const showChatButton = const showChatButton =
isVideoRoom || isVideoRoom ||

View file

@ -16,7 +16,6 @@ import { CardContext } from "../right_panel/context";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import RoomContext from "../../../contexts/RoomContext";
import MemberAvatar from "../avatars/MemberAvatar"; import MemberAvatar from "../avatars/MemberAvatar";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
@ -24,6 +23,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
import { notificationLevelToIndicator } from "../../../utils/notifications"; import { notificationLevelToIndicator } from "../../../utils/notifications";
import { EventPreviewTile, useEventPreview } from "./EventPreview.tsx"; import { EventPreviewTile, useEventPreview } from "./EventPreview.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -31,7 +31,7 @@ interface IProps {
} }
const ThreadSummary: React.FC<IProps> = ({ mxEvent, thread, ...props }) => { const ThreadSummary: React.FC<IProps> = ({ mxEvent, thread, ...props }) => {
const roomContext = useContext(RoomContext); const roomContext = useScopedRoomContext("narrow");
const cardContext = useContext(CardContext); const cardContext = useContext(CardContext);
const count = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.length); const count = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.length);
const { level } = useUnreadNotifications(thread.room, thread.id); const { level } = useUnreadNotifications(thread.room, thread.id);

View file

@ -13,14 +13,14 @@ import { EmojiButton } from "../../EmojiButton";
import dis from "../../../../../dispatcher/dispatcher"; import dis from "../../../../../dispatcher/dispatcher";
import { ComposerInsertPayload } from "../../../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerInsertPayload } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import { useRoomContext } from "../../../../../contexts/RoomContext"; import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
interface EmojiProps { interface EmojiProps {
menuPosition: MenuProps; menuPosition: MenuProps;
} }
export function Emoji({ menuPosition }: EmojiProps): JSX.Element { export function Emoji({ menuPosition }: EmojiProps): JSX.Element {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("timelineRenderingType");
return ( return (
<EmojiButton <EmojiButton

View file

@ -10,11 +10,11 @@ import React, { ForwardedRef, forwardRef, FunctionComponent } from "react";
import { FormattingFunctions, MappedSuggestion } from "@vector-im/matrix-wysiwyg"; import { FormattingFunctions, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import Autocomplete from "../../Autocomplete"; import Autocomplete from "../../Autocomplete";
import { ICompletion } from "../../../../../autocomplete/Autocompleter"; import { ICompletion } from "../../../../../autocomplete/Autocompleter";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import { getMentionDisplayText, getMentionAttributes, buildQuery } from "../utils/autocomplete"; import { getMentionDisplayText, getMentionAttributes, buildQuery } from "../utils/autocomplete";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
interface WysiwygAutocompleteProps { interface WysiwygAutocompleteProps {
/** /**
@ -53,7 +53,7 @@ const WysiwygAutocomplete = forwardRef(
{ suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps, { suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps,
ref: ForwardedRef<Autocomplete>, ref: ForwardedRef<Autocomplete>,
): JSX.Element | null => { ): JSX.Element | null => {
const { room } = useRoomContext(); const { room } = useScopedRoomContext("room");
const client = useMatrixClientContext(); const client = useMatrixClientContext();
function handleConfirm(completion: ICompletion): void { function handleConfirm(completion: ICompletion): void {

View file

@ -19,12 +19,12 @@ import { Editor } from "./Editor";
import { useInputEventProcessor } from "../hooks/useInputEventProcessor"; import { useInputEventProcessor } from "../hooks/useInputEventProcessor";
import { useSetCursorPosition } from "../hooks/useSetCursorPosition"; import { useSetCursorPosition } from "../hooks/useSetCursorPosition";
import { useIsFocused } from "../hooks/useIsFocused"; import { useIsFocused } from "../hooks/useIsFocused";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import defaultDispatcher from "../../../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import { parsePermalink } from "../../../../../utils/permalinks/Permalinks"; import { parsePermalink } from "../../../../../utils/permalinks/Permalinks";
import { isNotNull } from "../../../../../Typeguards"; import { isNotNull } from "../../../../../Typeguards";
import { useSettingValue } from "../../../../../hooks/useSettings"; import { useSettingValue } from "../../../../../hooks/useSettings";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
interface WysiwygComposerProps { interface WysiwygComposerProps {
disabled?: boolean; disabled?: boolean;
@ -56,7 +56,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({
children, children,
eventRelation, eventRelation,
}: WysiwygComposerProps) { }: WysiwygComposerProps) {
const { room } = useRoomContext(); const { room } = useScopedRoomContext("room");
const autocompleteRef = useRef<Autocomplete | null>(null); const autocompleteRef = useRef<Autocomplete | null>(null);
const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation); const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation);

View file

@ -10,10 +10,10 @@ import { ISendEventResponse } from "matrix-js-sdk/src/matrix";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
import { endEditing } from "../utils/editing"; import { endEditing } from "../utils/editing";
import { editMessage } from "../utils/message"; import { editMessage } from "../utils/message";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
export function useEditing( export function useEditing(
editorStateTransfer: EditorStateTransfer, editorStateTransfer: EditorStateTransfer,
@ -24,7 +24,7 @@ export function useEditing(
editMessage(): Promise<ISendEventResponse | undefined>; editMessage(): Promise<ISendEventResponse | undefined>;
endEditing(): void; endEditing(): void;
} { } {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("timelineRenderingType");
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const [isSaveDisabled, setIsSaveDisabled] = useState(true); const [isSaveDisabled, setIsSaveDisabled] = useState(true);

View file

@ -10,11 +10,11 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { useMemo } from "react"; import { useMemo } from "react";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import { parseEvent } from "../../../../../editor/deserialize"; import { parseEvent } from "../../../../../editor/deserialize";
import { CommandPartCreator, Part } from "../../../../../editor/parts"; import { CommandPartCreator, Part } from "../../../../../editor/parts";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
function getFormattedContent(editorStateTransfer: EditorStateTransfer): string { function getFormattedContent(editorStateTransfer: EditorStateTransfer): string {
return ( return (
@ -60,12 +60,12 @@ export function parseEditorStateTransfer(
} }
export function useInitialContent(editorStateTransfer: EditorStateTransfer): string | undefined { export function useInitialContent(editorStateTransfer: EditorStateTransfer): string | undefined {
const roomContext = useRoomContext(); const { room } = useScopedRoomContext("room");
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
return useMemo<string | undefined>(() => { return useMemo<string | undefined>(() => {
if (editorStateTransfer && roomContext.room && mxClient) { if (editorStateTransfer && room && mxClient) {
return parseEditorStateTransfer(editorStateTransfer, roomContext.room, mxClient); return parseEditorStateTransfer(editorStateTransfer, room, mxClient);
} }
}, [editorStateTransfer, roomContext, mxClient]); }, [editorStateTransfer, room, mxClient]);
} }

View file

@ -16,7 +16,6 @@ import { KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts
import { findEditableEvent } from "../../../../../utils/EventUtils"; import { findEditableEvent } from "../../../../../utils/EventUtils";
import dis from "../../../../../dispatcher/dispatcher"; import dis from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import { IRoomState } from "../../../../structures/RoomView"; import { IRoomState } from "../../../../structures/RoomView";
import { ComposerContextState, useComposerContext } from "../ComposerContext"; import { ComposerContextState, useComposerContext } from "../ComposerContext";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
@ -26,6 +25,7 @@ import { getEventsFromEditorStateTransfer, getEventsFromRoom } from "../utils/ev
import { endEditing } from "../utils/editing"; import { endEditing } from "../utils/editing";
import Autocomplete from "../../Autocomplete"; import Autocomplete from "../../Autocomplete";
import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils"; import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
export function useInputEventProcessor( export function useInputEventProcessor(
onSend: () => void, onSend: () => void,
@ -33,7 +33,7 @@ export function useInputEventProcessor(
initialContent?: string, initialContent?: string,
eventRelation?: IEventRelation, eventRelation?: IEventRelation,
): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null { ): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("liveTimeline", "room", "replyToEvent", "timelineRenderingType");
const composerContext = useComposerContext(); const composerContext = useComposerContext();
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const isCtrlEnterToSend = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend"); const isCtrlEnterToSend = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
@ -94,7 +94,7 @@ function handleKeyboardEvent(
initialContent: string | undefined, initialContent: string | undefined,
composer: Wysiwyg, composer: Wysiwyg,
editor: HTMLElement, editor: HTMLElement,
roomContext: IRoomState, roomContext: Pick<IRoomState, "liveTimeline" | "timelineRenderingType" | "room">,
composerContext: ComposerContextState, composerContext: ComposerContextState,
mxClient: MatrixClient | undefined, mxClient: MatrixClient | undefined,
autocompleteRef: React.RefObject<Autocomplete>, autocompleteRef: React.RefObject<Autocomplete>,
@ -175,7 +175,7 @@ function dispatchEditEvent(
isForward: boolean, isForward: boolean,
editorStateTransfer: EditorStateTransfer | undefined, editorStateTransfer: EditorStateTransfer | undefined,
composerContext: ComposerContextState, composerContext: ComposerContextState,
roomContext: IRoomState, roomContext: Pick<IRoomState, "liveTimeline" | "timelineRenderingType" | "room">,
mxClient: MatrixClient, mxClient: MatrixClient,
): boolean { ): boolean {
const foundEvents = editorStateTransfer const foundEvents = editorStateTransfer

View file

@ -16,8 +16,8 @@ import Autocomplete from "../../Autocomplete";
import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils"; import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsClipboardEvent } from "./utils";
import { useSuggestion } from "./useSuggestion"; import { useSuggestion } from "./useSuggestion";
import { isNotNull, isNotUndefined } from "../../../../../Typeguards"; import { isNotNull, isNotUndefined } from "../../../../../Typeguards";
import { useRoomContext } from "../../../../../contexts/RoomContext";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
function isDivElement(target: EventTarget): target is HTMLDivElement { function isDivElement(target: EventTarget): target is HTMLDivElement {
return target instanceof HTMLDivElement; return target instanceof HTMLDivElement;
@ -63,7 +63,7 @@ export function usePlainTextListeners(
onSelect: (event: SyntheticEvent<HTMLDivElement>) => void; onSelect: (event: SyntheticEvent<HTMLDivElement>) => void;
suggestion: MappedSuggestion | null; suggestion: MappedSuggestion | null;
} { } {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("room", "timelineRenderingType", "replyToEvent");
const mxClient = useMatrixClientContext(); const mxClient = useMatrixClientContext();
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);

View file

@ -11,20 +11,21 @@ import { RefObject, useCallback, useRef } from "react";
import defaultDispatcher from "../../../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import { ActionPayload } from "../../../../../dispatcher/payloads"; import { ActionPayload } from "../../../../../dispatcher/payloads";
import { TimelineRenderingType, useRoomContext } from "../../../../../contexts/RoomContext"; import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
import { useDispatcher } from "../../../../../hooks/useDispatcher"; import { useDispatcher } from "../../../../../hooks/useDispatcher";
import { focusComposer } from "./utils"; import { focusComposer } from "./utils";
import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
import { ComposerFunctions } from "../types"; import { ComposerFunctions } from "../types";
import { setSelection } from "../utils/selection"; import { setSelection } from "../utils/selection";
import { useComposerContext } from "../ComposerContext"; import { useComposerContext } from "../ComposerContext";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
export function useWysiwygEditActionHandler( export function useWysiwygEditActionHandler(
disabled: boolean, disabled: boolean,
composerElement: RefObject<HTMLElement>, composerElement: RefObject<HTMLElement>,
composerFunctions: ComposerFunctions, composerFunctions: ComposerFunctions,
): void { ): void {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("timelineRenderingType");
const composerContext = useComposerContext(); const composerContext = useComposerContext();
const timeoutId = useRef<number | null>(null); const timeoutId = useRef<number | null>(null);

View file

@ -11,20 +11,21 @@ import { MutableRefObject, useCallback, useRef } from "react";
import defaultDispatcher from "../../../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import { ActionPayload } from "../../../../../dispatcher/payloads"; import { ActionPayload } from "../../../../../dispatcher/payloads";
import { TimelineRenderingType, useRoomContext } from "../../../../../contexts/RoomContext"; import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
import { useDispatcher } from "../../../../../hooks/useDispatcher"; import { useDispatcher } from "../../../../../hooks/useDispatcher";
import { focusComposer } from "./utils"; import { focusComposer } from "./utils";
import { ComposerFunctions } from "../types"; import { ComposerFunctions } from "../types";
import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
import { useComposerContext } from "../ComposerContext"; import { useComposerContext } from "../ComposerContext";
import { setSelection } from "../utils/selection"; import { setSelection } from "../utils/selection";
import { useScopedRoomContext } from "../../../../../contexts/ScopedRoomContext.tsx";
export function useWysiwygSendActionHandler( export function useWysiwygSendActionHandler(
disabled: boolean, disabled: boolean,
composerElement: MutableRefObject<HTMLElement>, composerElement: MutableRefObject<HTMLElement>,
composerFunctions: ComposerFunctions, composerFunctions: ComposerFunctions,
): void { ): void {
const roomContext = useRoomContext(); const roomContext = useScopedRoomContext("timelineRenderingType");
const composerContext = useComposerContext(); const composerContext = useComposerContext();
const timeoutId = useRef<number | null>(null); const timeoutId = useRef<number | null>(null);

View file

@ -22,7 +22,7 @@ import { isNotNull } from "../../../../../Typeguards";
export function focusComposer( export function focusComposer(
composerElement: MutableRefObject<HTMLElement | null>, composerElement: MutableRefObject<HTMLElement | null>,
renderingType: TimelineRenderingType, renderingType: TimelineRenderingType,
roomContext: IRoomState, roomContext: Pick<IRoomState, "timelineRenderingType">,
timeoutId: MutableRefObject<number | null>, timeoutId: MutableRefObject<number | null>,
): void { ): void {
if (renderingType === roomContext.timelineRenderingType) { if (renderingType === roomContext.timelineRenderingType) {
@ -123,7 +123,7 @@ export function handleEventWithAutocomplete(
export function handleClipboardEvent( export function handleClipboardEvent(
event: ClipboardEvent | InputEvent, event: ClipboardEvent | InputEvent,
data: DataTransfer | null, data: DataTransfer | null,
roomContext: IRoomState, roomContext: Pick<IRoomState, "room" | "timelineRenderingType" | "replyToEvent">,
mxClient: MatrixClient, mxClient: MatrixClient,
eventRelation?: IEventRelation, eventRelation?: IEventRelation,
): boolean { ): boolean {

View file

@ -13,7 +13,7 @@ import dis from "../../../../../dispatcher/dispatcher";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
export function endEditing(roomContext: IRoomState): void { export function endEditing(roomContext: Pick<IRoomState, "timelineRenderingType">): void {
// todo local storage // todo local storage
// localStorage.removeItem(this.editorRoomKey); // localStorage.removeItem(this.editorRoomKey);
// localStorage.removeItem(this.editorStateKey); // localStorage.removeItem(this.editorStateKey);

View file

@ -15,7 +15,7 @@ import { ComposerContextState } from "../ComposerContext";
// From EditMessageComposer private get events(): MatrixEvent[] // From EditMessageComposer private get events(): MatrixEvent[]
export function getEventsFromEditorStateTransfer( export function getEventsFromEditorStateTransfer(
editorStateTransfer: EditorStateTransfer, editorStateTransfer: EditorStateTransfer,
roomContext: IRoomState, roomContext: Pick<IRoomState, "liveTimeline">,
mxClient: MatrixClient, mxClient: MatrixClient,
): MatrixEvent[] | undefined { ): MatrixEvent[] | undefined {
const liveTimelineEvents = roomContext.liveTimeline?.getEvents(); const liveTimelineEvents = roomContext.liveTimeline?.getEvents();
@ -41,7 +41,7 @@ export function getEventsFromEditorStateTransfer(
// From SendMessageComposer private onKeyDown = (event: KeyboardEvent): void // From SendMessageComposer private onKeyDown = (event: KeyboardEvent): void
export function getEventsFromRoom( export function getEventsFromRoom(
composerContext: ComposerContextState, composerContext: ComposerContextState,
roomContext: IRoomState, roomContext: Pick<IRoomState, "liveTimeline" | "room">,
): MatrixEvent[] | undefined { ): MatrixEvent[] | undefined {
const isReplyingToThread = composerContext.eventRelation?.key === THREAD_RELATION_TYPE.name; const isReplyingToThread = composerContext.eventRelation?.key === THREAD_RELATION_TYPE.name;
return roomContext.liveTimeline return roomContext.liveTimeline

View file

@ -39,7 +39,7 @@ export interface SendMessageParams {
mxClient: MatrixClient; mxClient: MatrixClient;
relation?: IEventRelation; relation?: IEventRelation;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
roomContext: IRoomState; roomContext: Pick<IRoomState, "timelineRenderingType" | "room">;
} }
export async function sendMessage( export async function sendMessage(
@ -177,7 +177,7 @@ export async function sendMessage(
interface EditMessageParams { interface EditMessageParams {
mxClient: MatrixClient; mxClient: MatrixClient;
roomContext: IRoomState; roomContext: Pick<IRoomState, "timelineRenderingType">;
editorStateTransfer: EditorStateTransfer; editorStateTransfer: EditorStateTransfer;
} }

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { createContext, useContext } from "react"; import { createContext } from "react";
import { IRoomState } from "../components/structures/RoomView"; import { IRoomState } from "../components/structures/RoomView";
import { Layout } from "../settings/enums/Layout"; import { Layout } from "../settings/enums/Layout";
@ -78,6 +78,3 @@ const RoomContext = createContext<
}); });
RoomContext.displayName = "RoomContext"; RoomContext.displayName = "RoomContext";
export default RoomContext; export default RoomContext;
export function useRoomContext(): IRoomState {
return useContext(RoomContext);
}

View file

@ -0,0 +1,71 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import React, { ContextType, createContext, memo, ReactNode, useContext, useEffect, useRef, useState } from "react";
import { objectKeyChanges } from "../utils/objects.ts";
import { useTypedEventEmitter } from "../hooks/useEventEmitter.ts";
import RoomContext from "./RoomContext.ts";
type ContextValue = ContextType<typeof RoomContext>;
export enum NotificationStateEvents {
Update = "update",
}
type EventHandlerMap<C extends Record<string, any>> = {
[NotificationStateEvents.Update]: (keys: Array<keyof C>) => void;
};
class EfficientContext<C extends Record<string, any>> extends TypedEventEmitter<
NotificationStateEvents,
EventHandlerMap<C>
> {
public state?: C;
public setState(state: C): void {
const changedKeys = objectKeyChanges(this.state ?? ({} as C), state);
this.state = state;
this.emit(NotificationStateEvents.Update, changedKeys);
}
}
const ScopedRoomContext = createContext<EfficientContext<ContextValue> | undefined>(undefined);
// Uses react memo and leverages splatting the value to ensure that the context is only updated when the state changes (shallow compare)
export const ScopedRoomContextProvider = memo(
({ children, ...state }: { children: ReactNode } & ContextValue): JSX.Element => {
const contextRef = useRef(new EfficientContext<ContextValue>());
useEffect(() => {
contextRef.current.setState(state);
}, [state]);
// Includes the legacy RoomContext provider for backwards compatibility with class components
return (
<RoomContext.Provider value={state}>
<ScopedRoomContext.Provider value={contextRef.current}>{children}</ScopedRoomContext.Provider>
</RoomContext.Provider>
);
},
);
export function useScopedRoomContext<K extends Array<keyof ContextValue>>(
...keys: K
): { [key in K[number]]: ContextValue[key] } {
const context = useContext(ScopedRoomContext);
const [state, setState] = useState<{ [key in K[number]]: ContextValue[key] }>({} as any);
useTypedEventEmitter(context, NotificationStateEvents.Update, (updatedKeys: K[]): void => {
if (context?.state && updatedKeys.some((updatedKey) => keys.includes(updatedKey as any))) {
setState(context.state);
}
});
return state;
}

View file

@ -7,10 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { RoomMember } from "matrix-js-sdk/src/matrix"; import { RoomMember } from "matrix-js-sdk/src/matrix";
import { useContext, useMemo } from "react"; import { useMemo } from "react";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import { TimelineRenderingType } from "../../contexts/RoomContext";
import { useSettingValue } from "../useSettings"; import { useSettingValue } from "../useSettings";
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext.tsx";
export function useRoomMemberProfile({ export function useRoomMemberProfile({
userId = "", userId = "",
@ -21,7 +22,7 @@ export function useRoomMemberProfile({
member?: RoomMember | null; member?: RoomMember | null;
forceHistorical?: boolean; forceHistorical?: boolean;
}): RoomMember | undefined | null { }): RoomMember | undefined | null {
const context = useContext(RoomContext); const context = useScopedRoomContext("room", "timelineRenderingType");
const useOnlyCurrentProfiles = useSettingValue("useOnlyCurrentProfiles"); const useOnlyCurrentProfiles = useSettingValue("useOnlyCurrentProfiles");
const member = useMemo(() => { const member = useMemo(() => {