/* Copyright 2024 New Vector Ltd. Copyright 2015-2022 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { ChangeEvent } from "react"; import { Room, RoomState, RoomStateEvent, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { throttle } from "lodash"; import dis from "../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; import WidgetCard from "../views/right_panel/WidgetCard"; import SettingsStore from "../../settings/SettingsStore"; import MemberList from "../views/rooms/MemberList"; import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; import ThreadView from "./ThreadView"; import ThreadPanel from "./ThreadPanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import { PinnedMessagesCard } from "../views/right_panel/PinnedMessagesCard"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import { E2EStatus } from "../../utils/ShieldUtils"; import TimelineCard from "../views/right_panel/TimelineCard"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { IRightPanelCard, IRightPanelCardState } from "../../stores/right-panel/RightPanelStoreIPanelState"; import { Action } from "../../dispatcher/actions"; import { XOR } from "../../@types/common"; import { RightPanelTabs } from "../views/right_panel/RightPanelTabs"; import ExtensionsCard from "../views/right_panel/ExtensionsCard"; interface BaseProps { overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView) resizeNotifier: ResizeNotifier; e2eStatus?: E2EStatus; } interface RoomlessProps extends BaseProps { room?: undefined; permalinkCreator?: undefined; } interface RoomProps extends BaseProps { room: Room; permalinkCreator: RoomPermalinkCreator; onSearchChange?: (e: ChangeEvent) => void; onSearchCancel?: () => void; } type Props = XOR; interface IState { phase?: RightPanelPhases; searchQuery: string; cardState?: IRightPanelCardState; } export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; public declare context: React.ContextType; public constructor(props: Props, context: React.ContextType) { super(props, context); this.state = { searchQuery: "", }; } private readonly delayedUpdate = throttle( (): void => { this.forceUpdate(); }, 500, { leading: true, trailing: true }, ); public componentDidMount(): void { this.context.on(RoomStateEvent.Members, this.onRoomStateMember); RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); } public componentWillUnmount(): void { this.context?.removeListener(RoomStateEvent.Members, this.onRoomStateMember); RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); } public static getDerivedStateFromProps(props: Props): Partial { let currentCard: IRightPanelCard | undefined; if (props.room) { currentCard = RightPanelStore.instance.currentCardForRoom(props.room.roomId); } return { cardState: currentCard?.state, phase: currentCard?.phase ?? undefined, }; } private onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember): void => { if (!this.props.room || member.roomId !== this.props.room.roomId) { return; } // redraw the badge on the membership list if (this.state.phase === RightPanelPhases.RoomMemberList) { this.delayedUpdate(); } else if ( this.state.phase === RightPanelPhases.RoomMemberInfo && member.userId === this.state.cardState?.member?.userId ) { // refresh the member info (e.g. new power level) this.delayedUpdate(); } }; private onRightPanelStoreUpdate = (): void => { this.setState({ ...(RightPanel.getDerivedStateFromProps(this.props) as IState) }); }; private onClose = (): void => { // XXX: There are three different ways of 'closing' this panel depending on what state // things are in... this knows far more than it should do about the state of the rest // of the app and is generally a bit silly. if (this.props.overwriteCard?.state?.member) { // If we have a user prop then we're displaying a user from the 'user' page type // in LoggedInView, so need to change the page type to close the panel (we switch // to the home page which is not obviously the correct thing to do, but I'm not sure // anything else is - we could hide the close button altogether?) dis.dispatch({ action: Action.ViewHomePage, }); } else if ( this.state.phase === RightPanelPhases.EncryptionPanel && this.state.cardState?.verificationRequest?.pending ) { // When the user clicks close on the encryption panel cancel the pending request first if any this.state.cardState.verificationRequest.cancel(); } else { RightPanelStore.instance.togglePanel(this.props.room?.roomId ?? null); } }; private onSearchQueryChanged = (searchQuery: string): void => { this.setState({ searchQuery }); }; public render(): React.ReactNode { let card =
; const roomId = this.props.room?.roomId; const phase = this.props.overwriteCard?.phase ?? this.state.phase; const cardState = this.props.overwriteCard?.state ?? this.state.cardState; switch (phase) { case RightPanelPhases.RoomMemberList: if (!!roomId) { card = ( ); } break; case RightPanelPhases.SpaceMemberList: if (!!cardState?.spaceId || !!roomId) { card = ( ); } break; case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.SpaceMemberInfo: case RightPanelPhases.EncryptionPanel: { if (!!cardState?.member) { const roomMember = cardState.member instanceof RoomMember ? cardState.member : undefined; card = ( ); } break; } case RightPanelPhases.Room3pidMemberInfo: case RightPanelPhases.Space3pidMemberInfo: if (!!cardState?.memberInfoEvent) { card = ( ); } break; case RightPanelPhases.NotificationPanel: card = ; break; case RightPanelPhases.PinnedMessages: if (!!this.props.room && SettingsStore.getValue("feature_pinning")) { card = ( ); } break; case RightPanelPhases.Timeline: if (!!this.props.room) { card = ( ); } break; case RightPanelPhases.FilePanel: if (!!roomId) { card = ( ); } break; case RightPanelPhases.ThreadView: if (!!this.props.room && !!cardState?.threadHeadEvent) { card = ( ); } break; case RightPanelPhases.ThreadPanel: if (!!this.props.room) { card = ( ); } break; case RightPanelPhases.RoomSummary: if (!!this.props.room) { card = ( ); } break; case RightPanelPhases.Extensions: if (!!this.props.room) { card = ; } break; case RightPanelPhases.Widget: if (!!this.props.room && !!cardState?.widgetId) { card = ; } break; } return ( ); } }