You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-10-31 01:45:39 +03:00 
			
		
		
		
	Right panel store refactor (#7313)
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
		
							
								
								
									
										2
									
								
								src/@types/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/@types/global.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -32,7 +32,7 @@ import SettingsStore from "../settings/SettingsStore"; | |||||||
| import { ActiveRoomObserver } from "../ActiveRoomObserver"; | import { ActiveRoomObserver } from "../ActiveRoomObserver"; | ||||||
| import { Notifier } from "../Notifier"; | import { Notifier } from "../Notifier"; | ||||||
| import type { Renderer } from "react-dom"; | import type { Renderer } from "react-dom"; | ||||||
| import RightPanelStore from "../stores/RightPanelStore"; | import RightPanelStore from "../stores/right-panel/RightPanelStore"; | ||||||
| import WidgetStore from "../stores/WidgetStore"; | import WidgetStore from "../stores/WidgetStore"; | ||||||
| import CallHandler from "../CallHandler"; | import CallHandler from "../CallHandler"; | ||||||
| import { Analytics } from "../Analytics"; | import { Analytics } from "../Analytics"; | ||||||
|   | |||||||
| @@ -27,12 +27,12 @@ import { isValid3pidInvite } from "./RoomInvite"; | |||||||
| import SettingsStore from "./settings/SettingsStore"; | import SettingsStore from "./settings/SettingsStore"; | ||||||
| import { ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES } from "./mjolnir/BanList"; | import { ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES } from "./mjolnir/BanList"; | ||||||
| import { WIDGET_LAYOUT_EVENT_TYPE } from "./stores/widgets/WidgetLayoutStore"; | import { WIDGET_LAYOUT_EVENT_TYPE } from "./stores/widgets/WidgetLayoutStore"; | ||||||
| import { RightPanelPhases } from './stores/RightPanelStorePhases'; | import { RightPanelPhases } from './stores/right-panel/RightPanelStorePhases'; | ||||||
| import { Action } from './dispatcher/actions'; | import { Action } from './dispatcher/actions'; | ||||||
| import defaultDispatcher from './dispatcher/dispatcher'; | import defaultDispatcher from './dispatcher/dispatcher'; | ||||||
| import { SetRightPanelPhasePayload } from './dispatcher/payloads/SetRightPanelPhasePayload'; |  | ||||||
| import { MatrixClientPeg } from "./MatrixClientPeg"; | import { MatrixClientPeg } from "./MatrixClientPeg"; | ||||||
| import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog"; | import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog"; | ||||||
|  | import RightPanelStore from './stores/right-panel/RightPanelStore'; | ||||||
|  |  | ||||||
| // These functions are frequently used just to check whether an event has | // These functions are frequently used just to check whether an event has | ||||||
| // any text to display at all. For this reason they return deferred values | // any text to display at all. For this reason they return deferred values | ||||||
| @@ -503,11 +503,7 @@ const onPinnedOrUnpinnedMessageClick = (messageId: string, roomId: string): void | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const onPinnedMessagesClick = (): void => { | const onPinnedMessagesClick = (): void => { | ||||||
|     defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |     RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); | ||||||
|         action: Action.SetRightPanelPhase, |  | ||||||
|         phase: RightPanelPhases.PinnedMessages, |  | ||||||
|         allowClose: false, |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null { | function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null { | ||||||
|   | |||||||
| @@ -27,9 +27,9 @@ import { logger } from "matrix-js-sdk/src/logger"; | |||||||
| import { MatrixClientPeg } from '../../MatrixClientPeg'; | import { MatrixClientPeg } from '../../MatrixClientPeg'; | ||||||
| import EventIndexPeg from "../../indexing/EventIndexPeg"; | import EventIndexPeg from "../../indexing/EventIndexPeg"; | ||||||
| import { _t } from '../../languageHandler'; | import { _t } from '../../languageHandler'; | ||||||
| import BaseCard from "../views/right_panel/BaseCard"; | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; |  | ||||||
| import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice"; | import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice"; | ||||||
|  | import BaseCard from "../views/right_panel/BaseCard"; | ||||||
| import { replaceableComponent } from "../../utils/replaceableComponent"; | import { replaceableComponent } from "../../utils/replaceableComponent"; | ||||||
| import ResizeNotifier from '../../utils/ResizeNotifier'; | import ResizeNotifier from '../../utils/ResizeNotifier'; | ||||||
| import TimelinePanel from "./TimelinePanel"; | import TimelinePanel from "./TimelinePanel"; | ||||||
|   | |||||||
| @@ -38,13 +38,14 @@ import GroupStore from '../../stores/GroupStore'; | |||||||
| import FlairStore from '../../stores/FlairStore'; | import FlairStore from '../../stores/FlairStore'; | ||||||
| import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; | import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; | ||||||
| import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks"; | import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks"; | ||||||
| import RightPanelStore from "../../stores/RightPanelStore"; | import RightPanelStore from "../../stores/right-panel/RightPanelStore"; | ||||||
| import AutoHideScrollbar from "./AutoHideScrollbar"; | import AutoHideScrollbar from "./AutoHideScrollbar"; | ||||||
| import { mediaFromMxc } from "../../customisations/Media"; | import { mediaFromMxc } from "../../customisations/Media"; | ||||||
| import { replaceableComponent } from "../../utils/replaceableComponent"; | import { replaceableComponent } from "../../utils/replaceableComponent"; | ||||||
| import { createSpaceFromCommunity } from "../../utils/space"; | import { createSpaceFromCommunity } from "../../utils/space"; | ||||||
| import { Action } from "../../dispatcher/actions"; | import { Action } from "../../dispatcher/actions"; | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
|  | import { UPDATE_EVENT } from "../../stores/AsyncStore"; | ||||||
|  |  | ||||||
| const LONG_DESC_PLACEHOLDER = _td( | const LONG_DESC_PLACEHOLDER = _td( | ||||||
|     `<h1>HTML for your community's page</h1> |     `<h1>HTML for your community's page</h1> | ||||||
| @@ -427,7 +428,7 @@ export default class GroupView extends React.Component { | |||||||
|         membershipBusy: false, |         membershipBusy: false, | ||||||
|         publicityBusy: false, |         publicityBusy: false, | ||||||
|         inviterProfile: null, |         inviterProfile: null, | ||||||
|         showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, |         showRightPanel: RightPanelStore.instance.isOpenForGroup, | ||||||
|         showUpgradeNotice: !localStorage.getItem(UPGRADE_NOTICE_LS_KEY), |         showUpgradeNotice: !localStorage.getItem(UPGRADE_NOTICE_LS_KEY), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -439,7 +440,7 @@ export default class GroupView extends React.Component { | |||||||
|         this._initGroupStore(this.props.groupId, true); |         this._initGroupStore(this.props.groupId, true); | ||||||
|  |  | ||||||
|         this._dispatcherRef = dis.register(this._onAction); |         this._dispatcherRef = dis.register(this._onAction); | ||||||
|         this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); |         RightPanelStore.instance.on(UPDATE_EVENT, this._onRightPanelStoreUpdate); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     componentWillUnmount() { |     componentWillUnmount() { | ||||||
| @@ -447,10 +448,7 @@ export default class GroupView extends React.Component { | |||||||
|         this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); |         this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); | ||||||
|         dis.unregister(this._dispatcherRef); |         dis.unregister(this._dispatcherRef); | ||||||
|  |  | ||||||
|         // Remove RightPanelStore listener |         RightPanelStore.instance.off(UPDATE_EVENT, this._onRightPanelStoreUpdate); | ||||||
|         if (this._rightPanelStoreToken) { |  | ||||||
|             this._rightPanelStoreToken.remove(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO: [REACT-WARNING] Replace with appropriate lifecycle event |     // TODO: [REACT-WARNING] Replace with appropriate lifecycle event | ||||||
| @@ -468,7 +466,7 @@ export default class GroupView extends React.Component { | |||||||
|  |  | ||||||
|     _onRightPanelStoreUpdate = () => { |     _onRightPanelStoreUpdate = () => { | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, |             showRightPanel: RightPanelStore.instance.isOpenForGroup, | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -824,10 +822,7 @@ export default class GroupView extends React.Component { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     _onAdminsLinkClick = () => { |     _onAdminsLinkClick = () => { | ||||||
|         dis.dispatch({ |         RightPanelStore.instance.setCard({ phase: RightPanelPhases.GroupMemberList }); | ||||||
|             action: Action.SetRightPanelPhase, |  | ||||||
|             phase: RightPanelPhases.GroupMemberList, |  | ||||||
|         }); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     _getGroupSection() { |     _getGroupSection() { | ||||||
|   | |||||||
| @@ -44,7 +44,6 @@ import CallContainer from '../views/voip/CallContainer'; | |||||||
| import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; | import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; | ||||||
| import RoomListStore from "../../stores/room-list/RoomListStore"; | import RoomListStore from "../../stores/room-list/RoomListStore"; | ||||||
| import NonUrgentToastContainer from "./NonUrgentToastContainer"; | import NonUrgentToastContainer from "./NonUrgentToastContainer"; | ||||||
| import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload"; |  | ||||||
| import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; | import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; | ||||||
| import Modal from "../../Modal"; | import Modal from "../../Modal"; | ||||||
| import { ICollapseConfig } from "../../resizer/distributors/collapse"; | import { ICollapseConfig } from "../../resizer/distributors/collapse"; | ||||||
| @@ -68,6 +67,7 @@ import GroupFilterPanel from './GroupFilterPanel'; | |||||||
| import CustomRoomTagPanel from './CustomRoomTagPanel'; | import CustomRoomTagPanel from './CustomRoomTagPanel'; | ||||||
| import { mediaFromMxc } from "../../customisations/Media"; | import { mediaFromMxc } from "../../customisations/Media"; | ||||||
| import LegacyCommunityPreview from "./LegacyCommunityPreview"; | import LegacyCommunityPreview from "./LegacyCommunityPreview"; | ||||||
|  | import RightPanelStore from '../../stores/right-panel/RightPanelStore'; | ||||||
|  |  | ||||||
| // We need to fetch each pinned message individually (if we don't already have it) | // We need to fetch each pinned message individually (if we don't already have it) | ||||||
| // so each pinned message may trigger a request. Limit the number per room for sanity. | // so each pinned message may trigger a request. Limit the number per room for sanity. | ||||||
| @@ -489,10 +489,7 @@ class LoggedInView extends React.Component<IProps, IState> { | |||||||
|                 break; |                 break; | ||||||
|             case NavigationAction.ToggleRoomSidePanel: |             case NavigationAction.ToggleRoomSidePanel: | ||||||
|                 if (this.props.page_type === "room_view" || this.props.page_type === "group_view") { |                 if (this.props.page_type === "room_view" || this.props.page_type === "group_view") { | ||||||
|                     dis.dispatch<ToggleRightPanelPayload>({ |                     RightPanelStore.instance.togglePanel(); | ||||||
|                         action: Action.ToggleRightPanel, |  | ||||||
|                         type: this.props.page_type === "room_view" ? "room" : "group", |  | ||||||
|                     }); |  | ||||||
|                     handled = true; |                     handled = true; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|   | |||||||
| @@ -20,17 +20,12 @@ import { Room } from "matrix-js-sdk/src/models/room"; | |||||||
| import { RoomState } from "matrix-js-sdk/src/models/room-state"; | import { RoomState } from "matrix-js-sdk/src/models/room-state"; | ||||||
| import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | ||||||
| import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | ||||||
| import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; |  | ||||||
| import { throttle } from 'lodash'; | import { throttle } from 'lodash'; | ||||||
|  |  | ||||||
| import dis from '../../dispatcher/dispatcher'; | import dis from '../../dispatcher/dispatcher'; | ||||||
| import GroupStore from '../../stores/GroupStore'; | import GroupStore from '../../stores/GroupStore'; | ||||||
| import { | import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases'; | ||||||
|     RIGHT_PANEL_PHASES_NO_ARGS, | import RightPanelStore from "../../stores/right-panel/RightPanelStore"; | ||||||
|     RIGHT_PANEL_SPACE_PHASES, |  | ||||||
|     RightPanelPhases, |  | ||||||
| } from "../../stores/RightPanelStorePhases"; |  | ||||||
| import RightPanelStore from "../../stores/RightPanelStore"; |  | ||||||
| import MatrixClientContext from "../../contexts/MatrixClientContext"; | import MatrixClientContext from "../../contexts/MatrixClientContext"; | ||||||
| import { Action } from "../../dispatcher/actions"; | import { Action } from "../../dispatcher/actions"; | ||||||
| import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; | import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; | ||||||
| @@ -50,16 +45,17 @@ import ThreadPanel from "./ThreadPanel"; | |||||||
| import NotificationPanel from "./NotificationPanel"; | import NotificationPanel from "./NotificationPanel"; | ||||||
| import ResizeNotifier from "../../utils/ResizeNotifier"; | import ResizeNotifier from "../../utils/ResizeNotifier"; | ||||||
| import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; | import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; | ||||||
| import SpaceStore from "../../stores/spaces/SpaceStore"; |  | ||||||
| import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; | import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; | ||||||
| import { E2EStatus } from '../../utils/ShieldUtils'; | import { E2EStatus } from '../../utils/ShieldUtils'; | ||||||
| import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads'; | import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads'; | ||||||
| import TimelineCard from '../views/right_panel/TimelineCard'; | import TimelineCard from '../views/right_panel/TimelineCard'; | ||||||
|  | import { UPDATE_EVENT } from '../../stores/AsyncStore'; | ||||||
|  | import { IRightPanelCard, IRightPanelCardState } from '../../stores/right-panel/RightPanelStoreIPanelState'; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     room?: Room; // if showing panels for a given room, this is set |     room?: Room; // if showing panels for a given room, this is set | ||||||
|     groupId?: string; // if showing panels for a given group, this is set |     groupId?: string; // if showing panels for a given group, this is set | ||||||
|     member?: RoomMember; // used if we know the room member ahead of opening the panel |     overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView) | ||||||
|     resizeNotifier: ResizeNotifier; |     resizeNotifier: ResizeNotifier; | ||||||
|     permalinkCreator?: RoomPermalinkCreator; |     permalinkCreator?: RoomPermalinkCreator; | ||||||
|     e2eStatus?: E2EStatus; |     e2eStatus?: E2EStatus; | ||||||
| @@ -68,17 +64,8 @@ interface IProps { | |||||||
| interface IState { | interface IState { | ||||||
|     phase: RightPanelPhases; |     phase: RightPanelPhases; | ||||||
|     isUserPrivilegedInGroup?: boolean; |     isUserPrivilegedInGroup?: boolean; | ||||||
|     member?: RoomMember; |  | ||||||
|     verificationRequest?: VerificationRequest; |  | ||||||
|     verificationRequestPromise?: Promise<VerificationRequest>; |  | ||||||
|     space?: Room; |  | ||||||
|     widgetId?: string; |  | ||||||
|     groupRoomId?: string; |  | ||||||
|     groupId?: string; |  | ||||||
|     event: MatrixEvent; |  | ||||||
|     initialEvent?: MatrixEvent; |  | ||||||
|     initialEventHighlighted?: boolean; |  | ||||||
|     searchQuery: string; |     searchQuery: string; | ||||||
|  |     cardState?: IRightPanelCardState; | ||||||
| } | } | ||||||
|  |  | ||||||
| @replaceableComponent("structures.RightPanel") | @replaceableComponent("structures.RightPanel") | ||||||
| @@ -89,11 +76,11 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|  |  | ||||||
|     constructor(props, context) { |     constructor(props, context) { | ||||||
|         super(props, context); |         super(props, context); | ||||||
|  |  | ||||||
|         this.state = { |         this.state = { | ||||||
|             ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, |             cardState: RightPanelStore.instance.currentCard?.state, | ||||||
|             phase: this.getPhaseFromProps(), |             phase: RightPanelStore.instance.currentCard?.phase, | ||||||
|             isUserPrivilegedInGroup: null, |             isUserPrivilegedInGroup: null, | ||||||
|             member: this.getUserForPanel(), |  | ||||||
|             searchQuery: "", |             searchQuery: "", | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| @@ -102,56 +89,11 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|         this.forceUpdate(); |         this.forceUpdate(); | ||||||
|     }, 500, { leading: true, trailing: true }); |     }, 500, { leading: true, trailing: true }); | ||||||
|  |  | ||||||
|     // Helper function to split out the logic for getPhaseFromProps() and the constructor |  | ||||||
|     // as both are called at the same time in the constructor. |  | ||||||
|     private getUserForPanel(): RoomMember { |  | ||||||
|         if (this.state && this.state.member) return this.state.member; |  | ||||||
|         const lastParams = RightPanelStore.getSharedInstance().roomPanelPhaseParams; |  | ||||||
|         return this.props.member || lastParams['member']; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // gets the current phase from the props and also maybe the store |  | ||||||
|     private getPhaseFromProps() { |  | ||||||
|         const rps = RightPanelStore.getSharedInstance(); |  | ||||||
|         const userForPanel = this.getUserForPanel(); |  | ||||||
|         if (this.props.groupId) { |  | ||||||
|             if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.groupPanelPhase)) { |  | ||||||
|                 dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.GroupMemberList }); |  | ||||||
|                 return RightPanelPhases.GroupMemberList; |  | ||||||
|             } |  | ||||||
|             return rps.groupPanelPhase; |  | ||||||
|         } else if (SpaceStore.spacesEnabled && this.props.room?.isSpaceRoom() |  | ||||||
|             && !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase) |  | ||||||
|         ) { |  | ||||||
|             return RightPanelPhases.SpaceMemberList; |  | ||||||
|         } else if (userForPanel) { |  | ||||||
|             // XXX FIXME AAAAAARGH: What is going on with this class!? It takes some of its state |  | ||||||
|             // from its props and some from a store, except if the contents of the store changes |  | ||||||
|             // while it's mounted in which case it replaces all of its state with that of the store, |  | ||||||
|             // except it uses a dispatch instead of a normal store listener? |  | ||||||
|             // Unfortunately rewriting this would almost certainly break showing the right panel |  | ||||||
|             // in some of the many cases, and I don't have time to re-architect it and test all |  | ||||||
|             // the flows now, so adding yet another special case so if the store thinks there is |  | ||||||
|             // a verification going on for the member we're displaying, we show that, otherwise |  | ||||||
|             // we race if a verification is started while the panel isn't displayed because we're |  | ||||||
|             // not mounted in time to get the dispatch. |  | ||||||
|             // Until then, let this code serve as a warning from history. |  | ||||||
|             if ( |  | ||||||
|                 rps.roomPanelPhaseParams.member && |  | ||||||
|                 userForPanel.userId === rps.roomPanelPhaseParams.member.userId && |  | ||||||
|                 rps.roomPanelPhaseParams.verificationRequest |  | ||||||
|             ) { |  | ||||||
|                 return rps.roomPanelPhase; |  | ||||||
|             } |  | ||||||
|             return RightPanelPhases.RoomMemberInfo; |  | ||||||
|         } |  | ||||||
|         return rps.roomPanelPhase; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public componentDidMount(): void { |     public componentDidMount(): void { | ||||||
|         this.dispatcherRef = dis.register(this.onAction); |         this.dispatcherRef = dis.register(this.onAction); | ||||||
|         const cli = this.context; |         const cli = this.context; | ||||||
|         cli.on("RoomState.members", this.onRoomStateMember); |         cli.on("RoomState.members", this.onRoomStateMember); | ||||||
|  |         RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         this.initGroupStore(this.props.groupId); |         this.initGroupStore(this.props.groupId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -160,6 +102,7 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|         if (this.context) { |         if (this.context) { | ||||||
|             this.context.removeListener("RoomState.members", this.onRoomStateMember); |             this.context.removeListener("RoomState.members", this.onRoomStateMember); | ||||||
|         } |         } | ||||||
|  |         RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         this.unregisterGroupStore(); |         this.unregisterGroupStore(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -193,42 +136,36 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|         // redraw the badge on the membership list |         // redraw the badge on the membership list | ||||||
|         if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) { |         if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) { | ||||||
|             this.delayedUpdate(); |             this.delayedUpdate(); | ||||||
|         } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId && |         } else if ( | ||||||
|                 member.userId === this.state.member.userId) { |             this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId && | ||||||
|  |             member.userId === this.state.cardState.member.userId | ||||||
|  |         ) { | ||||||
|             // refresh the member info (e.g. new power level) |             // refresh the member info (e.g. new power level) | ||||||
|             this.delayedUpdate(); |             this.delayedUpdate(); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     private onRightPanelStoreUpdate = () => { | ||||||
|  |         const currentPanel = RightPanelStore.instance.currentCard; | ||||||
|  |         this.setState({ | ||||||
|  |             cardState: currentPanel.state, | ||||||
|  |             phase: currentPanel.phase, | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     private onAction = (payload: ActionPayload) => { |     private onAction = (payload: ActionPayload) => { | ||||||
|         const isChangingRoom = payload.action === Action.ViewRoom && payload.room_id !== this.props.room.roomId; |         const isChangingRoom = payload.action === Action.ViewRoom && payload.room_id !== this.props.room.roomId; | ||||||
|         const isViewingThread = this.state.phase === RightPanelPhases.ThreadView; |         const isViewingThread = this.state.phase === RightPanelPhases.ThreadView; | ||||||
|         if (isChangingRoom && isViewingThread) { |         if (isChangingRoom && isViewingThread) { | ||||||
|             dispatchShowThreadsPanelEvent(); |             dispatchShowThreadsPanelEvent(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (payload.action === Action.AfterRightPanelPhaseChange) { |  | ||||||
|             this.setState({ |  | ||||||
|                 phase: payload.phase, |  | ||||||
|                 groupRoomId: payload.groupRoomId, |  | ||||||
|                 groupId: payload.groupId, |  | ||||||
|                 member: payload.member, |  | ||||||
|                 event: payload.event, |  | ||||||
|                 initialEvent: payload.initialEvent, |  | ||||||
|                 initialEventHighlighted: payload.highlighted, |  | ||||||
|                 verificationRequest: payload.verificationRequest, |  | ||||||
|                 verificationRequestPromise: payload.verificationRequestPromise, |  | ||||||
|                 widgetId: payload.widgetId, |  | ||||||
|                 space: payload.space, |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private onClose = () => { |     private onClose = () => { | ||||||
|         // XXX: There are three different ways of 'closing' this panel depending on what state |         // 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 |         // 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. |         // of the app and is generally a bit silly. | ||||||
|         if (this.props.member) { |         if (this.props.overwriteCard?.state?.member) { | ||||||
|             // If we have a user prop then we're displaying a user from the 'user' page type |             // 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 |             // 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 |             // to the home page which is not obviously the correct thing to do, but I'm not sure | ||||||
| @@ -238,16 +175,12 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|             }); |             }); | ||||||
|         } else if ( |         } else if ( | ||||||
|             this.state.phase === RightPanelPhases.EncryptionPanel && |             this.state.phase === RightPanelPhases.EncryptionPanel && | ||||||
|             this.state.verificationRequest && this.state.verificationRequest.pending |             this.state.cardState.verificationRequest && this.state.cardState.verificationRequest.pending | ||||||
|         ) { |         ) { | ||||||
|             // When the user clicks close on the encryption panel cancel the pending request first if any |             // When the user clicks close on the encryption panel cancel the pending request first if any | ||||||
|             this.state.verificationRequest.cancel(); |             this.state.cardState.verificationRequest.cancel(); | ||||||
|         } else { |         } else { | ||||||
|             // the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here |             RightPanelStore.instance.togglePanel(); | ||||||
|             dis.dispatch({ |  | ||||||
|                 action: Action.ToggleRightPanel, |  | ||||||
|                 type: this.props.groupId ? "group" : "room", |  | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -256,13 +189,14 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     public render(): JSX.Element { |     public render(): JSX.Element { | ||||||
|         let panel = <div />; |         let card = <div />; | ||||||
|         const roomId = this.props.room ? this.props.room.roomId : undefined; |         const roomId = this.props.room ? this.props.room.roomId : undefined; | ||||||
|  |         const phase = this.props.overwriteCard?.phase ?? this.state.phase; | ||||||
|         switch (this.state.phase) { |         const cardState = this.props.overwriteCard?.state ?? this.state.cardState; | ||||||
|  |         switch (phase) { | ||||||
|             case RightPanelPhases.RoomMemberList: |             case RightPanelPhases.RoomMemberList: | ||||||
|                 if (roomId) { |                 if (roomId) { | ||||||
|                     panel = <MemberList |                     card = <MemberList | ||||||
|                         roomId={roomId} |                         roomId={roomId} | ||||||
|                         key={roomId} |                         key={roomId} | ||||||
|                         onClose={this.onClose} |                         onClose={this.onClose} | ||||||
| @@ -272,9 +206,9 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case RightPanelPhases.SpaceMemberList: |             case RightPanelPhases.SpaceMemberList: | ||||||
|                 panel = <MemberList |                 card = <MemberList | ||||||
|                     roomId={this.state.space ? this.state.space.roomId : roomId} |                     roomId={cardState.spaceId ? cardState.spaceId : roomId} | ||||||
|                     key={this.state.space ? this.state.space.roomId : roomId} |                     key={cardState.spaceId ? cardState.spaceId : roomId} | ||||||
|                     onClose={this.onClose} |                     onClose={this.onClose} | ||||||
|                     searchQuery={this.state.searchQuery} |                     searchQuery={this.state.searchQuery} | ||||||
|                     onSearchQueryChanged={this.onSearchQueryChanged} |                     onSearchQueryChanged={this.onSearchQueryChanged} | ||||||
| @@ -283,61 +217,66 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|  |  | ||||||
|             case RightPanelPhases.GroupMemberList: |             case RightPanelPhases.GroupMemberList: | ||||||
|                 if (this.props.groupId) { |                 if (this.props.groupId) { | ||||||
|                     panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />; |                     card = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.GroupRoomList: |             case RightPanelPhases.GroupRoomList: | ||||||
|                 panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />; |                 card = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.RoomMemberInfo: |             case RightPanelPhases.RoomMemberInfo: | ||||||
|             case RightPanelPhases.SpaceMemberInfo: |             case RightPanelPhases.SpaceMemberInfo: | ||||||
|             case RightPanelPhases.EncryptionPanel: |             case RightPanelPhases.EncryptionPanel: { | ||||||
|                 panel = <UserInfo |                 const roomMember = cardState.member instanceof RoomMember | ||||||
|                     user={this.state.member} |                     ? cardState.member | ||||||
|                     room={this.context.getRoom(this.state.member.roomId) ?? this.props.room} |                     : undefined; | ||||||
|                     key={roomId || this.state.member.userId} |                 card = <UserInfo | ||||||
|  |                     user={cardState.member} | ||||||
|  |                     room={this.context.getRoom(roomMember?.roomId) ?? this.props.room} | ||||||
|  |                     key={roomId || cardState.member.userId} | ||||||
|                     onClose={this.onClose} |                     onClose={this.onClose} | ||||||
|                     phase={this.state.phase} |                     phase={phase} | ||||||
|                     verificationRequest={this.state.verificationRequest} |                     verificationRequest={cardState.verificationRequest} | ||||||
|                     verificationRequestPromise={this.state.verificationRequestPromise} |                     verificationRequestPromise={cardState.verificationRequestPromise} | ||||||
|                 />; |                 />; | ||||||
|                 break; |                 break; | ||||||
|  |             } | ||||||
|             case RightPanelPhases.Room3pidMemberInfo: |             case RightPanelPhases.Room3pidMemberInfo: | ||||||
|             case RightPanelPhases.Space3pidMemberInfo: |             case RightPanelPhases.Space3pidMemberInfo: | ||||||
|                 panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />; |                 card = <ThirdPartyMemberInfo event={cardState.memberInfoEvent} key={roomId} />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.GroupMemberInfo: |             case RightPanelPhases.GroupMemberInfo: | ||||||
|                 panel = <UserInfo |                 card = <UserInfo | ||||||
|                     user={this.state.member} |                     user={cardState.member} | ||||||
|                     groupId={this.props.groupId} |                     groupId={this.props.groupId} | ||||||
|                     key={this.state.member.userId} |                     key={cardState.member.userId} | ||||||
|                     phase={this.state.phase} |                     phase={phase} | ||||||
|                     onClose={this.onClose} />; |                     onClose={this.onClose} | ||||||
|  |                 />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.GroupRoomInfo: |             case RightPanelPhases.GroupRoomInfo: | ||||||
|                 panel = <GroupRoomInfo |                 card = <GroupRoomInfo | ||||||
|                     groupRoomId={this.state.groupRoomId} |                     groupRoomId={cardState.groupRoomId} | ||||||
|                     groupId={this.props.groupId} |                     groupId={this.props.groupId} | ||||||
|                     key={this.state.groupRoomId} />; |                     key={cardState.groupRoomId} | ||||||
|  |                 />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.NotificationPanel: |             case RightPanelPhases.NotificationPanel: | ||||||
|                 panel = <NotificationPanel onClose={this.onClose} />; |                 card = <NotificationPanel onClose={this.onClose} />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.PinnedMessages: |             case RightPanelPhases.PinnedMessages: | ||||||
|                 if (SettingsStore.getValue("feature_pinning")) { |                 if (SettingsStore.getValue("feature_pinning")) { | ||||||
|                     panel = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />; |                     card = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case RightPanelPhases.Timeline: |             case RightPanelPhases.Timeline: | ||||||
|                 if (!SettingsStore.getValue("feature_maximised_widgets")) break; |                 if (!SettingsStore.getValue("feature_maximised_widgets")) break; | ||||||
|                 panel = <TimelineCard |                 card = <TimelineCard | ||||||
|                     classNames="mx_ThreadPanel mx_TimelineCard" |                     classNames="mx_ThreadPanel mx_TimelineCard" | ||||||
|                     room={this.props.room} |                     room={this.props.room} | ||||||
|                     timelineSet={this.props.room.getUnfilteredTimelineSet()} |                     timelineSet={this.props.room.getUnfilteredTimelineSet()} | ||||||
| @@ -348,23 +287,24 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|                 />; |                 />; | ||||||
|                 break; |                 break; | ||||||
|             case RightPanelPhases.FilePanel: |             case RightPanelPhases.FilePanel: | ||||||
|                 panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />; |                 card = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.ThreadView: |             case RightPanelPhases.ThreadView: | ||||||
|                 panel = <ThreadView |                 card = <ThreadView | ||||||
|                     room={this.props.room} |                     room={this.props.room} | ||||||
|                     resizeNotifier={this.props.resizeNotifier} |                     resizeNotifier={this.props.resizeNotifier} | ||||||
|                     onClose={this.onClose} |                     onClose={this.onClose} | ||||||
|                     mxEvent={this.state.event} |                     mxEvent={cardState.threadHeadEvent} | ||||||
|                     initialEvent={this.state.initialEvent} |                     initialEvent={cardState.initialEvent} | ||||||
|                     initialEventHighlighted={this.state.initialEventHighlighted} |                     isInitialEventHighlighted={cardState.isInitialEventHighlighted} | ||||||
|                     permalinkCreator={this.props.permalinkCreator} |                     permalinkCreator={this.props.permalinkCreator} | ||||||
|                     e2eStatus={this.props.e2eStatus} />; |                     e2eStatus={this.props.e2eStatus} | ||||||
|  |                 />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.ThreadPanel: |             case RightPanelPhases.ThreadPanel: | ||||||
|                 panel = <ThreadPanel |                 card = <ThreadPanel | ||||||
|                     roomId={roomId} |                     roomId={roomId} | ||||||
|                     resizeNotifier={this.props.resizeNotifier} |                     resizeNotifier={this.props.resizeNotifier} | ||||||
|                     onClose={this.onClose} |                     onClose={this.onClose} | ||||||
| @@ -373,17 +313,21 @@ export default class RightPanel extends React.Component<IProps, IState> { | |||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.RoomSummary: |             case RightPanelPhases.RoomSummary: | ||||||
|                 panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />; |                 card = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />; | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case RightPanelPhases.Widget: |             case RightPanelPhases.Widget: | ||||||
|                 panel = <WidgetCard room={this.props.room} widgetId={this.state.widgetId} onClose={this.onClose} />; |                 card = <WidgetCard | ||||||
|  |                     room={this.props.room} | ||||||
|  |                     widgetId={cardState.widgetId} | ||||||
|  |                     onClose={this.onClose} | ||||||
|  |                 />; | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
|             <aside className="mx_RightPanel dark-panel" id="mx_RightPanel"> |             <aside className="mx_RightPanel dark-panel" id="mx_RightPanel"> | ||||||
|                 { panel } |                 { card } | ||||||
|             </aside> |             </aside> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -84,6 +84,7 @@ interface IState { | |||||||
|  |  | ||||||
| @replaceableComponent("structures.RoomStatusBar") | @replaceableComponent("structures.RoomStatusBar") | ||||||
| export default class RoomStatusBar extends React.PureComponent<IProps, IState> { | export default class RoomStatusBar extends React.PureComponent<IProps, IState> { | ||||||
|  |     private unmounted = false; | ||||||
|     public static contextType = MatrixClientContext; |     public static contextType = MatrixClientContext; | ||||||
|  |  | ||||||
|     constructor(props: IProps, context: typeof MatrixClientContext) { |     constructor(props: IProps, context: typeof MatrixClientContext) { | ||||||
| @@ -110,6 +111,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public componentWillUnmount(): void { |     public componentWillUnmount(): void { | ||||||
|  |         this.unmounted = true; | ||||||
|         // we may have entirely lost our client as we're logging out before clicking login on the guest bar... |         // we may have entirely lost our client as we're logging out before clicking login on the guest bar... | ||||||
|         const client = this.context; |         const client = this.context; | ||||||
|         if (client) { |         if (client) { | ||||||
| @@ -122,6 +124,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> { | |||||||
|         if (state === "SYNCING" && prevState === "SYNCING") { |         if (state === "SYNCING" && prevState === "SYNCING") { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         if (this.unmounted) return; | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             syncState: state, |             syncState: state, | ||||||
|             syncStateData: data, |             syncStateData: data, | ||||||
|   | |||||||
| @@ -53,7 +53,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 from "../views/elements/AccessibleButton"; | import AccessibleButton from "../views/elements/AccessibleButton"; | ||||||
| import RightPanelStore from "../../stores/RightPanelStore"; | import RightPanelStore from "../../stores/right-panel/RightPanelStore"; | ||||||
| import { haveTileForEvent } from "../views/rooms/EventTile"; | import { haveTileForEvent } from "../views/rooms/EventTile"; | ||||||
| import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; | import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; | ||||||
| import MatrixClientContext, { withMatrixClientHOC, MatrixClientProps } from "../../contexts/MatrixClientContext"; | import MatrixClientContext, { withMatrixClientHOC, MatrixClientProps } from "../../contexts/MatrixClientContext"; | ||||||
| @@ -98,8 +98,7 @@ import { dispatchShowThreadEvent } from '../../dispatcher/dispatch-actions/threa | |||||||
| import { fetchInitialEvent } from "../../utils/EventUtils"; | import { fetchInitialEvent } from "../../utils/EventUtils"; | ||||||
| import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; | import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; | ||||||
| import AppsDrawer from '../views/rooms/AppsDrawer'; | import AppsDrawer from '../views/rooms/AppsDrawer'; | ||||||
| import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload'; | import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { RightPanelPhases } from '../../stores/RightPanelStorePhases'; |  | ||||||
|  |  | ||||||
| const DEBUG = false; | const DEBUG = false; | ||||||
| let debuglog = function(msg: string) {}; | let debuglog = function(msg: string) {}; | ||||||
| @@ -214,7 +213,6 @@ export interface IRoomState { | |||||||
| export class RoomView extends React.Component<IRoomProps, IRoomState> { | export class RoomView extends React.Component<IRoomProps, IRoomState> { | ||||||
|     private readonly dispatcherRef: string; |     private readonly dispatcherRef: string; | ||||||
|     private readonly roomStoreToken: EventSubscription; |     private readonly roomStoreToken: EventSubscription; | ||||||
|     private readonly rightPanelStoreToken: EventSubscription; |  | ||||||
|     private settingWatchers: string[]; |     private settingWatchers: string[]; | ||||||
|  |  | ||||||
|     private unmounted = false; |     private unmounted = false; | ||||||
| @@ -246,7 +244,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|             canPeek: false, |             canPeek: false, | ||||||
|             showApps: false, |             showApps: false, | ||||||
|             isPeeking: false, |             isPeeking: false, | ||||||
|             showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, |             showRightPanel: RightPanelStore.instance.isOpenForRoom, | ||||||
|             joining: false, |             joining: false, | ||||||
|             atEndOfLiveTimeline: true, |             atEndOfLiveTimeline: true, | ||||||
|             atEndOfLiveTimelineInit: false, |             atEndOfLiveTimelineInit: false, | ||||||
| @@ -289,7 +287,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|         this.context.on("Event.decrypted", this.onEventDecrypted); |         this.context.on("Event.decrypted", this.onEventDecrypted); | ||||||
|         // Start listening for RoomViewStore updates |         // Start listening for RoomViewStore updates | ||||||
|         this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); |         this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); | ||||||
|         this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); |  | ||||||
|  |         RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|  |  | ||||||
|         WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); |         WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); | ||||||
|         WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate); |         WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate); | ||||||
| @@ -337,13 +336,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|         }); |         }); | ||||||
|         if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) { |         if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) { | ||||||
|             // Show chat in right panel when a widget is maximised |             // Show chat in right panel when a widget is maximised | ||||||
|             dis.dispatch<SetRightPanelPhasePayload>({ |             RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }); | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.Timeline, |  | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|         this.checkWidgets(this.state.room); |         this.checkWidgets(this.state.room); | ||||||
|         this.checkRightPanel(this.state.room); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private checkWidgets = (room) => { |     private checkWidgets = (room) => { | ||||||
| @@ -361,22 +356,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|             : MainSplitContentType.Timeline; |             : MainSplitContentType.Timeline; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private checkRightPanel = (room) => { |  | ||||||
|         // This is a hack to hide the chat. This should not be necessary once the right panel |  | ||||||
|         // phase is stored per room. (need to be done after check widget so that mainSplitContentType is updated) |  | ||||||
|         if ( |  | ||||||
|             RightPanelStore.getSharedInstance().roomPanelPhase === RightPanelPhases.Timeline && |  | ||||||
|             this.state.showRightPanel && |  | ||||||
|             !WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room) |  | ||||||
|         ) { |  | ||||||
|             // Two timelines are shown prevent this by hiding the right panel |  | ||||||
|             dis.dispatch({ |  | ||||||
|                 action: Action.ToggleRightPanel, |  | ||||||
|                 type: "room", |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     private onReadReceiptsChange = () => { |     private onReadReceiptsChange = () => { | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), |             showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), | ||||||
| @@ -754,11 +733,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|         if (this.roomStoreToken) { |         if (this.roomStoreToken) { | ||||||
|             this.roomStoreToken.remove(); |             this.roomStoreToken.remove(); | ||||||
|         } |         } | ||||||
|         // Remove RightPanelStore listener |  | ||||||
|         if (this.rightPanelStoreToken) { |  | ||||||
|             this.rightPanelStoreToken.remove(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |         RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); |         WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); | ||||||
|         WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate); |         WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate); | ||||||
|  |  | ||||||
| @@ -793,7 +769,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|  |  | ||||||
|     private onRightPanelStoreUpdate = () => { |     private onRightPanelStoreUpdate = () => { | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, |             showRightPanel: RightPanelStore.instance.isOpenForRoom, | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -1039,7 +1015,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { | |||||||
|         this.updateE2EStatus(room); |         this.updateE2EStatus(room); | ||||||
|         this.updatePermissions(room); |         this.updatePermissions(room); | ||||||
|         this.checkWidgets(room); |         this.checkWidgets(room); | ||||||
|         this.checkRightPanel(room); |  | ||||||
|  |  | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             liveTimeline: room.getLiveTimeline(), |             liveTimeline: room.getLiveTimeline(), | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import React, { RefObject, useContext, useRef, useState } from "react"; | |||||||
| import { EventType } from "matrix-js-sdk/src/@types/event"; | import { EventType } from "matrix-js-sdk/src/@types/event"; | ||||||
| import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials"; | import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials"; | ||||||
| import { Room } from "matrix-js-sdk/src/models/room"; | import { Room } from "matrix-js-sdk/src/models/room"; | ||||||
| import { EventSubscription } from "fbemitter"; |  | ||||||
| import { logger } from "matrix-js-sdk/src/logger"; | import { logger } from "matrix-js-sdk/src/logger"; | ||||||
|  |  | ||||||
| import MatrixClientContext from "../../contexts/MatrixClientContext"; | import MatrixClientContext from "../../contexts/MatrixClientContext"; | ||||||
| @@ -43,9 +42,8 @@ import MainSplit from './MainSplit'; | |||||||
| import ErrorBoundary from "../views/elements/ErrorBoundary"; | import ErrorBoundary from "../views/elements/ErrorBoundary"; | ||||||
| import { ActionPayload } from "../../dispatcher/payloads"; | import { ActionPayload } from "../../dispatcher/payloads"; | ||||||
| import RightPanel from "./RightPanel"; | import RightPanel from "./RightPanel"; | ||||||
| import RightPanelStore from "../../stores/RightPanelStore"; | import RightPanelStore from "../../stores/right-panel/RightPanelStore"; | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
| import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import { useStateArray } from "../../hooks/useStateArray"; | import { useStateArray } from "../../hooks/useStateArray"; | ||||||
| import SpacePublicShare from "../views/spaces/SpacePublicShare"; | import SpacePublicShare from "../views/spaces/SpacePublicShare"; | ||||||
| import { | import { | ||||||
| @@ -85,6 +83,7 @@ import { useDispatcher } from "../../hooks/useDispatcher"; | |||||||
| import { useRoomState } from "../../hooks/useRoomState"; | import { useRoomState } from "../../hooks/useRoomState"; | ||||||
| import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; | import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; | ||||||
| import { UIComponent } from "../../settings/UIFeature"; | import { UIComponent } from "../../settings/UIFeature"; | ||||||
|  | import { UPDATE_EVENT } from "../../stores/AsyncStore"; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     space: Room; |     space: Room; | ||||||
| @@ -164,10 +163,9 @@ const SpaceInfo = ({ space }: { space: Room }) => { | |||||||
|                     kind="link" |                     kind="link" | ||||||
|                     className="mx_SpaceRoomView_info_memberCount" |                     className="mx_SpaceRoomView_info_memberCount" | ||||||
|                     onClick={() => { |                     onClick={() => { | ||||||
|                         defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |                         RightPanelStore.instance.setCard({ | ||||||
|                             action: Action.SetRightPanelPhase, |  | ||||||
|                             phase: RightPanelPhases.RoomMemberList, |                             phase: RightPanelPhases.RoomMemberList, | ||||||
|                             refireParams: { space }, |                             state: { spaceId: space.roomId }, | ||||||
|                         }); |                         }); | ||||||
|                     }} |                     }} | ||||||
|                 > |                 > | ||||||
| @@ -473,11 +471,7 @@ const SpaceLanding = ({ space }: { space: Room }) => { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     const onMembersClick = () => { |     const onMembersClick = () => { | ||||||
|         defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |         RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList, state: { spaceId: space.roomId } }); | ||||||
|             action: Action.SetRightPanelPhase, |  | ||||||
|             phase: RightPanelPhases.RoomMemberList, |  | ||||||
|             refireParams: { space }, |  | ||||||
|         }); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     return <div className="mx_SpaceRoomView_landing"> |     return <div className="mx_SpaceRoomView_landing"> | ||||||
| @@ -796,7 +790,6 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> { | |||||||
|  |  | ||||||
|     private readonly creator: string; |     private readonly creator: string; | ||||||
|     private readonly dispatcherRef: string; |     private readonly dispatcherRef: string; | ||||||
|     private readonly rightPanelStoreToken: EventSubscription; |  | ||||||
|  |  | ||||||
|     constructor(props, context) { |     constructor(props, context) { | ||||||
|         super(props, context); |         super(props, context); | ||||||
| @@ -813,18 +806,18 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> { | |||||||
|  |  | ||||||
|         this.state = { |         this.state = { | ||||||
|             phase, |             phase, | ||||||
|             showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, |             showRightPanel: RightPanelStore.instance.isOpenForRoom, | ||||||
|             myMembership: this.props.space.getMyMembership(), |             myMembership: this.props.space.getMyMembership(), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         this.dispatcherRef = defaultDispatcher.register(this.onAction); |         this.dispatcherRef = defaultDispatcher.register(this.onAction); | ||||||
|         this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); |         RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         this.context.on("Room.myMembership", this.onMyMembership); |         this.context.on("Room.myMembership", this.onMyMembership); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     componentWillUnmount() { |     componentWillUnmount() { | ||||||
|         defaultDispatcher.unregister(this.dispatcherRef); |         defaultDispatcher.unregister(this.dispatcherRef); | ||||||
|         this.rightPanelStoreToken.remove(); |         RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         this.context.off("Room.myMembership", this.onMyMembership); |         this.context.off("Room.myMembership", this.onMyMembership); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -836,7 +829,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> { | |||||||
|  |  | ||||||
|     private onRightPanelStoreUpdate = () => { |     private onRightPanelStoreUpdate = () => { | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, |             showRightPanel: RightPanelStore.instance.isOpenForRoom, | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -849,28 +842,19 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> { | |||||||
|         if (payload.action !== Action.ViewUser && payload.action !== "view_3pid_invite") return; |         if (payload.action !== Action.ViewUser && payload.action !== "view_3pid_invite") return; | ||||||
|  |  | ||||||
|         if (payload.action === Action.ViewUser && payload.member) { |         if (payload.action === Action.ViewUser && payload.member) { | ||||||
|             defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |             RightPanelStore.instance.setCard({ | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.SpaceMemberInfo, |                 phase: RightPanelPhases.SpaceMemberInfo, | ||||||
|                 refireParams: { |                 state: { spaceId: this.props.space.roomId, member: payload.member }, | ||||||
|                     space: this.props.space, |  | ||||||
|                     member: payload.member, |  | ||||||
|                 }, |  | ||||||
|             }); |             }); | ||||||
|         } else if (payload.action === "view_3pid_invite" && payload.event) { |         } else if (payload.action === "view_3pid_invite" && payload.event) { | ||||||
|             defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |             RightPanelStore.instance.setCard({ | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.Space3pidMemberInfo, |                 phase: RightPanelPhases.Space3pidMemberInfo, | ||||||
|                 refireParams: { |                 state: { spaceId: this.props.space.roomId, member: payload.member }, | ||||||
|                     space: this.props.space, |  | ||||||
|                     event: payload.event, |  | ||||||
|                 }, |  | ||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|             defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |             RightPanelStore.instance.setCard({ | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.SpaceMemberList, |                 phase: RightPanelPhases.SpaceMemberList, | ||||||
|                 refireParams: { space: this.props.space }, |                 state: { spaceId: this.props.space.roomId }, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; | |||||||
| import { RelationType } from 'matrix-js-sdk/src/@types/event'; | import { RelationType } from 'matrix-js-sdk/src/@types/event'; | ||||||
|  |  | ||||||
| import BaseCard from "../views/right_panel/BaseCard"; | import BaseCard from "../views/right_panel/BaseCard"; | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
| import { replaceableComponent } from "../../utils/replaceableComponent"; | import { replaceableComponent } from "../../utils/replaceableComponent"; | ||||||
| import ResizeNotifier from '../../utils/ResizeNotifier'; | import ResizeNotifier from '../../utils/ResizeNotifier'; | ||||||
| import { TileShape } from '../views/rooms/EventTile'; | import { TileShape } from '../views/rooms/EventTile'; | ||||||
| @@ -30,7 +30,6 @@ import { Layout } from '../../settings/enums/Layout'; | |||||||
| import TimelinePanel from './TimelinePanel'; | import TimelinePanel from './TimelinePanel'; | ||||||
| import dis from "../../dispatcher/dispatcher"; | import dis from "../../dispatcher/dispatcher"; | ||||||
| import { ActionPayload } from '../../dispatcher/payloads'; | import { ActionPayload } from '../../dispatcher/payloads'; | ||||||
| import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload'; |  | ||||||
| import { Action } from '../../dispatcher/actions'; | import { Action } from '../../dispatcher/actions'; | ||||||
| import { MatrixClientPeg } from '../../MatrixClientPeg'; | import { MatrixClientPeg } from '../../MatrixClientPeg'; | ||||||
| import { E2EStatus } from '../../utils/ShieldUtils'; | import { E2EStatus } from '../../utils/ShieldUtils'; | ||||||
| @@ -40,7 +39,7 @@ import ContentMessages from '../../ContentMessages'; | |||||||
| import UploadBar from './UploadBar'; | import UploadBar from './UploadBar'; | ||||||
| import { _t } from '../../languageHandler'; | import { _t } from '../../languageHandler'; | ||||||
| import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu'; | import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu'; | ||||||
| import RightPanelStore from '../../stores/RightPanelStore'; | import RightPanelStore from '../../stores/right-panel/RightPanelStore'; | ||||||
| import SettingsStore from '../../settings/SettingsStore'; | import SettingsStore from '../../settings/SettingsStore'; | ||||||
| import { WidgetLayoutStore } from '../../stores/widgets/WidgetLayoutStore'; | import { WidgetLayoutStore } from '../../stores/widgets/WidgetLayoutStore'; | ||||||
|  |  | ||||||
| @@ -52,7 +51,7 @@ interface IProps { | |||||||
|     permalinkCreator?: RoomPermalinkCreator; |     permalinkCreator?: RoomPermalinkCreator; | ||||||
|     e2eStatus?: E2EStatus; |     e2eStatus?: E2EStatus; | ||||||
|     initialEvent?: MatrixEvent; |     initialEvent?: MatrixEvent; | ||||||
|     initialEventHighlighted?: boolean; |     isInitialEventHighlighted?: boolean; | ||||||
| } | } | ||||||
| interface IState { | interface IState { | ||||||
|     thread?: Thread; |     thread?: Thread; | ||||||
| @@ -94,10 +93,7 @@ export default class ThreadView extends React.Component<IProps, IState> { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (prevProps.room !== this.props.room) { |         if (prevProps.room !== this.props.room) { | ||||||
|             dis.dispatch<SetRightPanelPhasePayload>({ |             RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }); | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.RoomSummary, |  | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -168,7 +164,7 @@ export default class ThreadView extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private onScroll = (): void => { |     private onScroll = (): void => { | ||||||
|         if (this.props.initialEvent && this.props.initialEventHighlighted) { |         if (this.props.initialEvent && this.props.isInitialEventHighlighted) { | ||||||
|             dis.dispatch({ |             dis.dispatch({ | ||||||
|                 action: Action.ViewRoom, |                 action: Action.ViewRoom, | ||||||
|                 room_id: this.props.room.roomId, |                 room_id: this.props.room.roomId, | ||||||
| @@ -189,7 +185,7 @@ export default class ThreadView extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     public render(): JSX.Element { |     public render(): JSX.Element { | ||||||
|         const highlightedEventId = this.props.initialEventHighlighted |         const highlightedEventId = this.props.isInitialEventHighlighted | ||||||
|             ? this.props.initialEvent?.getId() |             ? this.props.initialEvent?.getId() | ||||||
|             : null; |             : null; | ||||||
|  |  | ||||||
| @@ -198,7 +194,7 @@ export default class ThreadView extends React.Component<IProps, IState> { | |||||||
|             event_id: this.state.thread?.id, |             event_id: this.state.thread?.id, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let previousPhase = RightPanelStore.getSharedInstance().previousPhase; |         let previousPhase = RightPanelStore.instance.previousCard.phase; | ||||||
|         if (!SettingsStore.getValue("feature_maximised_widgets")) { |         if (!SettingsStore.getValue("feature_maximised_widgets")) { | ||||||
|             previousPhase = RightPanelPhases.ThreadPanel; |             previousPhase = RightPanelPhases.ThreadPanel; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -680,6 +680,7 @@ class TimelinePanel extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private onSync = (clientSyncState: SyncState, prevState: SyncState, data: object): void => { |     private onSync = (clientSyncState: SyncState, prevState: SyncState, data: object): void => { | ||||||
|  |         if (this.unmounted) return; | ||||||
|         this.setState({ clientSyncState }); |         this.setState({ clientSyncState }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ import MainSplit from "./MainSplit"; | |||||||
| import RightPanel from "./RightPanel"; | import RightPanel from "./RightPanel"; | ||||||
| import Spinner from "../views/elements/Spinner"; | import Spinner from "../views/elements/Spinner"; | ||||||
| import ResizeNotifier from "../../utils/ResizeNotifier"; | import ResizeNotifier from "../../utils/ResizeNotifier"; | ||||||
|  | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     userId?: string; |     userId?: string; | ||||||
| @@ -88,7 +89,10 @@ export default class UserView extends React.Component<IProps, IState> { | |||||||
|         if (this.state.loading) { |         if (this.state.loading) { | ||||||
|             return <Spinner />; |             return <Spinner />; | ||||||
|         } else if (this.state.member) { |         } else if (this.state.member) { | ||||||
|             const panel = <RightPanel member={this.state.member} resizeNotifier={this.props.resizeNotifier} />; |             const panel = <RightPanel | ||||||
|  |                 overwriteCard={{ phase: RightPanelPhases.RoomMemberInfo, state: { member: this.state.member } }} | ||||||
|  |                 resizeNotifier={this.props.resizeNotifier} | ||||||
|  |             />; | ||||||
|             return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}> |             return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}> | ||||||
|                 <HomePage /> |                 <HomePage /> | ||||||
|             </MainSplit>); |             </MainSplit>); | ||||||
|   | |||||||
| @@ -38,12 +38,10 @@ import Modal from "../../../Modal"; | |||||||
| import ExportDialog from "../dialogs/ExportDialog"; | import ExportDialog from "../dialogs/ExportDialog"; | ||||||
| import { onRoomFilesClick, onRoomMembersClick } from "../right_panel/RoomSummaryCard"; | import { onRoomFilesClick, onRoomMembersClick } from "../right_panel/RoomSummaryCard"; | ||||||
| import RoomViewStore from "../../../stores/RoomViewStore"; | import RoomViewStore from "../../../stores/RoomViewStore"; | ||||||
| import defaultDispatcher from "../../../dispatcher/dispatcher"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import { Action } from "../../../dispatcher/actions"; |  | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; |  | ||||||
| import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog"; | import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog"; | ||||||
| import { useEventEmitterState } from "../../../hooks/useEventEmitter"; | import { useEventEmitterState } from "../../../hooks/useEventEmitter"; | ||||||
|  | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
| import DMRoomMap from "../../../utils/DMRoomMap"; | import DMRoomMap from "../../../utils/DMRoomMap"; | ||||||
|  |  | ||||||
| interface IProps extends IContextMenuProps { | interface IProps extends IContextMenuProps { | ||||||
| @@ -272,11 +270,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => { | |||||||
|                     ev.stopPropagation(); |                     ev.stopPropagation(); | ||||||
|  |  | ||||||
|                     ensureViewingRoom(); |                     ensureViewingRoom(); | ||||||
|                     defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |                     RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }, false); | ||||||
|                         action: Action.SetRightPanelPhase, |  | ||||||
|                         phase: RightPanelPhases.RoomSummary, |  | ||||||
|                         allowClose: false, |  | ||||||
|                     }); |  | ||||||
|                     onFinished(); |                     onFinished(); | ||||||
|                 }} |                 }} | ||||||
|                 label={_t("Widgets")} |                 label={_t("Widgets")} | ||||||
|   | |||||||
| @@ -26,19 +26,13 @@ import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; | |||||||
| import { isValid3pidInvite } from "../../../RoomInvite"; | import { isValid3pidInvite } from "../../../RoomInvite"; | ||||||
| import EventListSummary from "./EventListSummary"; | import EventListSummary from "./EventListSummary"; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
| import defaultDispatcher from '../../../dispatcher/dispatcher'; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; |  | ||||||
| import { Action } from '../../../dispatcher/actions'; |  | ||||||
| import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; |  | ||||||
| import { jsxJoin } from '../../../utils/ReactUtils'; | import { jsxJoin } from '../../../utils/ReactUtils'; | ||||||
| import { Layout } from '../../../settings/enums/Layout'; | import { Layout } from '../../../settings/enums/Layout'; | ||||||
|  | import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; | ||||||
|  |  | ||||||
| const onPinnedMessagesClick = (): void => { | const onPinnedMessagesClick = (): void => { | ||||||
|     defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |     RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); | ||||||
|         action: Action.SetRightPanelPhase, |  | ||||||
|         phase: RightPanelPhases.PinnedMessages, |  | ||||||
|         allowClose: false, |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const SENDER_AS_DISPLAY_NAME_EVENTS = [EventType.RoomServerAcl, EventType.RoomPinnedEvents]; | const SENDER_AS_DISPLAY_NAME_EVENTS = [EventType.RoomServerAcl, EventType.RoomPinnedEvents]; | ||||||
|   | |||||||
| @@ -20,14 +20,13 @@ import PropTypes from 'prop-types'; | |||||||
|  |  | ||||||
| import { _t } from '../../../languageHandler'; | import { _t } from '../../../languageHandler'; | ||||||
| import * as sdk from '../../../index'; | import * as sdk from '../../../index'; | ||||||
| import dis from '../../../dispatcher/dispatcher'; |  | ||||||
| import GroupStore from '../../../stores/GroupStore'; | import GroupStore from '../../../stores/GroupStore'; | ||||||
| import { showGroupInviteDialog } from '../../../GroupAddressPicker'; | import { showGroupInviteDialog } from '../../../GroupAddressPicker'; | ||||||
| import AccessibleButton from '../elements/AccessibleButton'; | import AccessibleButton from '../elements/AccessibleButton'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; | import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; | ||||||
| import { Action } from "../../../dispatcher/actions"; |  | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
|  | import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; | ||||||
|  |  | ||||||
| const INITIAL_LOAD_NUM_MEMBERS = 30; | const INITIAL_LOAD_NUM_MEMBERS = 30; | ||||||
|  |  | ||||||
| @@ -170,10 +169,9 @@ export default class GroupMemberList extends React.Component { | |||||||
|  |  | ||||||
|     onInviteToGroupButtonClick = () => { |     onInviteToGroupButtonClick = () => { | ||||||
|         showGroupInviteDialog(this.props.groupId).then(() => { |         showGroupInviteDialog(this.props.groupId).then(() => { | ||||||
|             dis.dispatch({ |             RightPanelStore.instance.setCard({ | ||||||
|                 action: Action.SetRightPanelPhase, |  | ||||||
|                 phase: RightPanelPhases.GroupMemberList, |                 phase: RightPanelPhases.GroupMemberList, | ||||||
|                 refireParams: { groupId: this.props.groupId }, |                 state: { groupId: this.props.groupId }, | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -22,12 +22,11 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; | |||||||
| import { _t } from '../../../languageHandler'; | import { _t } from '../../../languageHandler'; | ||||||
| import { getNameForEventRoom, userLabelForEventRoom } | import { getNameForEventRoom, userLabelForEventRoom } | ||||||
|     from '../../../utils/KeyVerificationStateObserver'; |     from '../../../utils/KeyVerificationStateObserver'; | ||||||
| import dis from "../../../dispatcher/dispatcher"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; |  | ||||||
| import { Action } from "../../../dispatcher/actions"; |  | ||||||
| import EventTileBubble from "./EventTileBubble"; | import EventTileBubble from "./EventTileBubble"; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
| import AccessibleButton from '../elements/AccessibleButton'; | import AccessibleButton from '../elements/AccessibleButton'; | ||||||
|  | import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     mxEvent: MatrixEvent; |     mxEvent: MatrixEvent; | ||||||
| @@ -52,10 +51,9 @@ export default class MKeyVerificationRequest extends React.Component<IProps> { | |||||||
|     private openRequest = () => { |     private openRequest = () => { | ||||||
|         const { verificationRequest } = this.props.mxEvent; |         const { verificationRequest } = this.props.mxEvent; | ||||||
|         const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); |         const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); | ||||||
|         dis.dispatch({ |         RightPanelStore.instance.setCard({ | ||||||
|             action: Action.SetRightPanelPhase, |  | ||||||
|             phase: RightPanelPhases.EncryptionPanel, |             phase: RightPanelPhases.EncryptionPanel, | ||||||
|             refireParams: { verificationRequest, member }, |             state: { verificationRequest, member }, | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,10 +20,8 @@ import classNames from 'classnames'; | |||||||
| import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; | import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; | ||||||
| import { _t } from "../../../languageHandler"; | import { _t } from "../../../languageHandler"; | ||||||
| import AccessibleButton from "../elements/AccessibleButton"; | import AccessibleButton from "../elements/AccessibleButton"; | ||||||
| import defaultDispatcher from "../../../dispatcher/dispatcher"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; | import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; | ||||||
| import { Action } from "../../../dispatcher/actions"; |  | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; |  | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     header?: ReactNode; |     header?: ReactNode; | ||||||
| @@ -34,7 +32,7 @@ interface IProps { | |||||||
|     previousPhaseLabel?: string; |     previousPhaseLabel?: string; | ||||||
|     closeLabel?: string; |     closeLabel?: string; | ||||||
|     onClose?(): void; |     onClose?(): void; | ||||||
|     refireParams?; |     cardState?; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface IGroupProps { | interface IGroupProps { | ||||||
| @@ -59,16 +57,15 @@ const BaseCard: React.FC<IProps> = ({ | |||||||
|     previousPhase, |     previousPhase, | ||||||
|     previousPhaseLabel, |     previousPhaseLabel, | ||||||
|     children, |     children, | ||||||
|     refireParams, |     cardState, | ||||||
| }) => { | }) => { | ||||||
|     let backButton; |     let backButton; | ||||||
|     if (previousPhase) { |     if (previousPhase) { | ||||||
|         const onBackClick = () => { |         const onBackClick = () => { | ||||||
|             defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |             // TODO RightPanelStore (will be addressed in a follow up PR): this should ideally be: | ||||||
|                 action: Action.SetRightPanelPhase, |             // RightPanelStore.instance.popRightPanel(); | ||||||
|                 phase: previousPhase, |  | ||||||
|                 refireParams: refireParams, |             RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState }); | ||||||
|             }); |  | ||||||
|         }; |         }; | ||||||
|         const label = previousPhaseLabel ?? _t("Back"); |         const label = previousPhaseLabel ?? _t("Back"); | ||||||
|         backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />; |         backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />; | ||||||
|   | |||||||
| @@ -31,9 +31,8 @@ import { useEventEmitter } from "../../../hooks/useEventEmitter"; | |||||||
| import Modal from "../../../Modal"; | import Modal from "../../../Modal"; | ||||||
| import * as sdk from "../../../index"; | import * as sdk from "../../../index"; | ||||||
| import { _t } from "../../../languageHandler"; | import { _t } from "../../../languageHandler"; | ||||||
| import dis from "../../../dispatcher/dispatcher"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { Action } from "../../../dispatcher/actions"; | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; |  | ||||||
|  |  | ||||||
| // cancellation codes which constitute a key mismatch | // cancellation codes which constitute a key mismatch | ||||||
| const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; | const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; | ||||||
| @@ -117,10 +116,9 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => { | |||||||
|         setRequest(verificationRequest_); |         setRequest(verificationRequest_); | ||||||
|         setPhase(verificationRequest_.phase); |         setPhase(verificationRequest_.phase); | ||||||
|         // Notify the RightPanelStore about this |         // Notify the RightPanelStore about this | ||||||
|         dis.dispatch({ |         RightPanelStore.instance.setCard({ | ||||||
|             action: Action.SetRightPanelPhase, |  | ||||||
|             phase: RightPanelPhases.EncryptionPanel, |             phase: RightPanelPhases.EncryptionPanel, | ||||||
|             refireParams: { member, verificationRequest: verificationRequest_ }, |             state: { member, verificationRequest: verificationRequest_ }, | ||||||
|         }); |         }); | ||||||
|     }, [member]); |     }, [member]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ import React from 'react'; | |||||||
| import { _t } from '../../../languageHandler'; | import { _t } from '../../../languageHandler'; | ||||||
| import HeaderButton from './HeaderButton'; | import HeaderButton from './HeaderButton'; | ||||||
| import HeaderButtons, { HeaderKind } from './HeaderButtons'; | import HeaderButtons, { HeaderKind } from './HeaderButtons'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { Action } from "../../../dispatcher/actions"; | import { Action } from "../../../dispatcher/actions"; | ||||||
| import { ActionPayload } from "../../../dispatcher/payloads"; | import { ActionPayload } from "../../../dispatcher/payloads"; | ||||||
| import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; | import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; | ||||||
|   | |||||||
| @@ -21,15 +21,11 @@ limitations under the License. | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  |  | ||||||
| import dis from '../../../dispatcher/dispatcher'; | import dis from '../../../dispatcher/dispatcher'; | ||||||
| import RightPanelStore from "../../../stores/RightPanelStore"; | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { Action } from '../../../dispatcher/actions'; | import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState'; | ||||||
| import { |  | ||||||
|     SetRightPanelPhasePayload, |  | ||||||
|     SetRightPanelPhaseRefireParams, |  | ||||||
| } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; |  | ||||||
| import type { EventSubscription } from "fbemitter"; |  | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
|  | import { UPDATE_EVENT } from '../../../stores/AsyncStore'; | ||||||
| import { NotificationColor } from '../../../stores/notifications/NotificationColor'; | import { NotificationColor } from '../../../stores/notifications/NotificationColor'; | ||||||
|  |  | ||||||
| export enum HeaderKind { | export enum HeaderKind { | ||||||
| @@ -47,38 +43,35 @@ interface IProps {} | |||||||
|  |  | ||||||
| @replaceableComponent("views.right_panel.HeaderButtons") | @replaceableComponent("views.right_panel.HeaderButtons") | ||||||
| export default abstract class HeaderButtons<P = {}> extends React.Component<IProps & P, IState> { | export default abstract class HeaderButtons<P = {}> extends React.Component<IProps & P, IState> { | ||||||
|     private storeToken: EventSubscription; |     private unmounted = false; | ||||||
|     private dispatcherRef: string; |     private dispatcherRef: string; | ||||||
|  |  | ||||||
|     constructor(props: IProps & P, kind: HeaderKind) { |     constructor(props: IProps & P, kind: HeaderKind) { | ||||||
|         super(props); |         super(props); | ||||||
|  |  | ||||||
|         const rps = RightPanelStore.getSharedInstance(); |         const rps = RightPanelStore.instance; | ||||||
|         this.state = { |         this.state = { | ||||||
|             headerKind: kind, |             headerKind: kind, | ||||||
|  |             phase: rps.currentCard.phase, | ||||||
|             threadNotificationColor: NotificationColor.None, |             threadNotificationColor: NotificationColor.None, | ||||||
|             phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase, |  | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public componentDidMount() { |     public componentDidMount() { | ||||||
|         this.storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this)); |         RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         this.dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses |         this.dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public componentWillUnmount() { |     public componentWillUnmount() { | ||||||
|         if (this.storeToken) this.storeToken.remove(); |         this.unmounted = true; | ||||||
|  |         RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); | ||||||
|         if (this.dispatcherRef) dis.unregister(this.dispatcherRef); |         if (this.dispatcherRef) dis.unregister(this.dispatcherRef); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected abstract onAction(payload); |     protected abstract onAction(payload); | ||||||
|  |  | ||||||
|     public setPhase(phase: RightPanelPhases, extras?: Partial<SetRightPanelPhaseRefireParams>) { |     public setPhase(phase: RightPanelPhases, cardState?: Partial<IRightPanelCardState>) { | ||||||
|         dis.dispatch<SetRightPanelPhasePayload>({ |         RightPanelStore.instance.setCard({ phase, state: cardState }); | ||||||
|             action: Action.SetRightPanelPhase, |  | ||||||
|             phase: phase, |  | ||||||
|             refireParams: extras, |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public isPhase(phases: string | string[]) { |     public isPhase(phases: string | string[]) { | ||||||
| @@ -89,14 +82,12 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onRightPanelUpdate() { |     private onRightPanelStoreUpdate = () => { | ||||||
|         const rps = RightPanelStore.getSharedInstance(); |         if (this.unmounted) return; | ||||||
|         if (this.state.headerKind === HeaderKind.Room) { |         let phase = RightPanelStore.instance.currentCard.phase; | ||||||
|             this.setState({ phase: rps.visibleRoomPanelPhase }); |         if (!RightPanelStore.instance.isOpenForGroup) {phase = null;} | ||||||
|         } else if (this.state.headerKind === HeaderKind.Group) { |         this.setState({ phase }); | ||||||
|             this.setState({ phase: rps.visibleGroupPanelPhase }); |     }; | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // XXX: Make renderButtons a prop |     // XXX: Make renderButtons a prop | ||||||
|     public abstract renderButtons(): JSX.Element; |     public abstract renderButtons(): JSX.Element; | ||||||
|   | |||||||
| @@ -25,16 +25,15 @@ import { Room } from "matrix-js-sdk/src/models/room"; | |||||||
| import { _t } from '../../../languageHandler'; | import { _t } from '../../../languageHandler'; | ||||||
| import HeaderButton from './HeaderButton'; | import HeaderButton from './HeaderButton'; | ||||||
| import HeaderButtons, { HeaderKind } from './HeaderButtons'; | import HeaderButtons, { HeaderKind } from './HeaderButtons'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { Action } from "../../../dispatcher/actions"; | import { Action } from "../../../dispatcher/actions"; | ||||||
| import { ActionPayload } from "../../../dispatcher/payloads"; | import { ActionPayload } from "../../../dispatcher/payloads"; | ||||||
| import RightPanelStore from "../../../stores/RightPanelStore"; | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
| import { useSettingValue } from "../../../hooks/useSettings"; | import { useSettingValue } from "../../../hooks/useSettings"; | ||||||
| import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; | import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; | ||||||
| import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads"; | import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads"; | ||||||
| import SettingsStore from "../../../settings/SettingsStore"; | import SettingsStore from "../../../settings/SettingsStore"; | ||||||
| import dis from "../../../dispatcher/dispatcher"; |  | ||||||
| import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; | import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; | ||||||
| import { NotificationColor } from "../../../stores/notifications/NotificationColor"; | import { NotificationColor } from "../../../stores/notifications/NotificationColor"; | ||||||
| import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState"; | import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState"; | ||||||
| @@ -161,7 +160,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> { | |||||||
|             } |             } | ||||||
|         } else if (payload.action === "view_3pid_invite") { |         } else if (payload.action === "view_3pid_invite") { | ||||||
|             if (payload.event) { |             if (payload.event) { | ||||||
|                 this.setPhase(RightPanelPhases.Room3pidMemberInfo, { event: payload.event }); |                 this.setPhase(RightPanelPhases.Room3pidMemberInfo, { memberInfoEvent: payload.event }); | ||||||
|             } else { |             } else { | ||||||
|                 this.setPhase(RightPanelPhases.RoomMemberList); |                 this.setPhase(RightPanelPhases.RoomMemberList); | ||||||
|             } |             } | ||||||
| @@ -170,12 +169,12 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> { | |||||||
|  |  | ||||||
|     private onRoomSummaryClicked = () => { |     private onRoomSummaryClicked = () => { | ||||||
|         // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close |         // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close | ||||||
|         const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase; |         const currentPhase = RightPanelStore.instance.currentCard.phase; | ||||||
|         if (ROOM_INFO_PHASES.includes(lastPhase)) { |         if (ROOM_INFO_PHASES.includes(currentPhase)) { | ||||||
|             if (this.state.phase === lastPhase) { |             if (this.state.phase === currentPhase) { | ||||||
|                 this.setPhase(lastPhase); |                 this.setPhase(currentPhase); | ||||||
|             } else { |             } else { | ||||||
|                 this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams); |                 this.setPhase(currentPhase, RightPanelStore.instance.currentCard.state); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             // This toggles for us, if needed |             // This toggles for us, if needed | ||||||
| @@ -198,10 +197,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> { | |||||||
|  |  | ||||||
|     private onThreadsPanelClicked = () => { |     private onThreadsPanelClicked = () => { | ||||||
|         if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { |         if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { | ||||||
|             dis.dispatch({ |             RightPanelStore.instance.togglePanel(); | ||||||
|                 action: Action.ToggleRightPanel, |  | ||||||
|                 type: "room", |  | ||||||
|             }); |  | ||||||
|         } else { |         } else { | ||||||
|             dispatchShowThreadsPanelEvent(); |             dispatchShowThreadsPanelEvent(); | ||||||
|         } |         } | ||||||
| @@ -227,6 +223,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> { | |||||||
|         rightPanelPhaseButtons.set(RightPanelPhases.ThreadPanel, |         rightPanelPhaseButtons.set(RightPanelPhases.ThreadPanel, | ||||||
|             SettingsStore.getValue("feature_thread") |             SettingsStore.getValue("feature_thread") | ||||||
|                 ? <HeaderButton |                 ? <HeaderButton | ||||||
|  |                     key={RightPanelPhases.ThreadPanel} | ||||||
|                     name="threadsButton" |                     name="threadsButton" | ||||||
|                     title={_t("Threads")} |                     title={_t("Threads")} | ||||||
|                     onClick={this.onThreadsPanelClicked} |                     onClick={this.onThreadsPanelClicked} | ||||||
|   | |||||||
| @@ -25,9 +25,7 @@ import { _t } from '../../../languageHandler'; | |||||||
| import RoomAvatar from "../avatars/RoomAvatar"; | import RoomAvatar from "../avatars/RoomAvatar"; | ||||||
| import AccessibleButton from "../elements/AccessibleButton"; | import AccessibleButton from "../elements/AccessibleButton"; | ||||||
| import defaultDispatcher from "../../../dispatcher/dispatcher"; | import defaultDispatcher from "../../../dispatcher/dispatcher"; | ||||||
| import { Action } from "../../../dispatcher/actions"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; |  | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import Modal from "../../../Modal"; | import Modal from "../../../Modal"; | ||||||
| import ShareDialog from '../dialogs/ShareDialog'; | import ShareDialog from '../dialogs/ShareDialog'; | ||||||
| import { useEventEmitter } from "../../../hooks/useEventEmitter"; | import { useEventEmitter } from "../../../hooks/useEventEmitter"; | ||||||
| @@ -48,6 +46,7 @@ import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widget | |||||||
| import RoomName from "../elements/RoomName"; | import RoomName from "../elements/RoomName"; | ||||||
| import UIStore from "../../../stores/UIStore"; | import UIStore from "../../../stores/UIStore"; | ||||||
| import ExportDialog from "../dialogs/ExportDialog"; | import ExportDialog from "../dialogs/ExportDialog"; | ||||||
|  | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     room: Room; |     room: Room; | ||||||
| @@ -103,12 +102,10 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => { | |||||||
|     }, [room.roomId]); |     }, [room.roomId]); | ||||||
|  |  | ||||||
|     const onOpenWidgetClick = () => { |     const onOpenWidgetClick = () => { | ||||||
|         defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |         // TODO RightPanelStore (will be addressed in a follow up PR): should push the widget | ||||||
|             action: Action.SetRightPanelPhase, |         RightPanelStore.instance.setCard({ | ||||||
|             phase: RightPanelPhases.Widget, |             phase: RightPanelPhases.Widget, | ||||||
|             refireParams: { |             state: { widgetId: app.id }, | ||||||
|                 widgetId: app.id, |  | ||||||
|             }, |  | ||||||
|         }); |         }); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -237,19 +234,13 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const onRoomMembersClick = (allowClose = true) => { | export const onRoomMembersClick = (allowClose = true) => { | ||||||
|     defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |     // TODO RightPanelStore (will be addressed in a follow up PR): should push the phase | ||||||
|         action: Action.SetRightPanelPhase, |     RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList }, allowClose); | ||||||
|         phase: RightPanelPhases.RoomMemberList, |  | ||||||
|         allowClose, |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const onRoomFilesClick = (allowClose = true) => { | export const onRoomFilesClick = (allowClose = true) => { | ||||||
|     defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |     // TODO RightPanelStore (will be addressed in a follow up PR): should push the phase | ||||||
|         action: Action.SetRightPanelPhase, |     RightPanelStore.instance.setCard({ phase: RightPanelPhases.FilePanel }, allowClose); | ||||||
|         phase: RightPanelPhases.FilePanel, |  | ||||||
|         allowClose, |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const onRoomSettingsClick = () => { | const onRoomSettingsClick = () => { | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ interface IState { | |||||||
|     editState?: EditorStateTransfer; |     editState?: EditorStateTransfer; | ||||||
|     replyToEvent?: MatrixEvent; |     replyToEvent?: MatrixEvent; | ||||||
|     initialEventId?: string; |     initialEventId?: string; | ||||||
|     initialEventHighlighted?: boolean; |     isInitialEventHighlighted?: boolean; | ||||||
|  |  | ||||||
|     // settings: |     // settings: | ||||||
|     showReadReceipts?: boolean; |     showReadReceipts?: boolean; | ||||||
| @@ -103,7 +103,7 @@ export default class TimelineCard extends React.Component<IProps, IState> { | |||||||
|             // roomLoadError: RoomViewStore.getRoomLoadError(), |             // roomLoadError: RoomViewStore.getRoomLoadError(), | ||||||
|  |  | ||||||
|             initialEventId: RoomViewStore.getInitialEventId(), |             initialEventId: RoomViewStore.getInitialEventId(), | ||||||
|             initialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), |             isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), | ||||||
|             replyToEvent: RoomViewStore.getQuotingEvent(), |             replyToEvent: RoomViewStore.getQuotingEvent(), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
| @@ -127,7 +127,7 @@ export default class TimelineCard extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private onScroll = (): void => { |     private onScroll = (): void => { | ||||||
|         if (this.state.initialEventId && this.state.initialEventHighlighted) { |         if (this.state.initialEventId && this.state.isInitialEventHighlighted) { | ||||||
|             dis.dispatch({ |             dis.dispatch({ | ||||||
|                 action: Action.ViewRoom, |                 action: Action.ViewRoom, | ||||||
|                 room_id: this.props.room.roomId, |                 room_id: this.props.room.roomId, | ||||||
| @@ -145,7 +145,7 @@ export default class TimelineCard extends React.Component<IProps, IState> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     public render(): JSX.Element { |     public render(): JSX.Element { | ||||||
|         const highlightedEventId = this.state.initialEventHighlighted |         const highlightedEventId = this.state.isInitialEventHighlighted | ||||||
|             ? this.state.initialEventId |             ? this.state.initialEventId | ||||||
|             : null; |             : null; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ import E2EIcon from "../rooms/E2EIcon"; | |||||||
| import { useEventEmitter } from "../../../hooks/useEventEmitter"; | import { useEventEmitter } from "../../../hooks/useEventEmitter"; | ||||||
| import { textualPowerLevel } from '../../../Roles'; | import { textualPowerLevel } from '../../../Roles'; | ||||||
| import MatrixClientContext from "../../../contexts/MatrixClientContext"; | import MatrixClientContext from "../../../contexts/MatrixClientContext"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import EncryptionPanel from "./EncryptionPanel"; | import EncryptionPanel from "./EncryptionPanel"; | ||||||
| import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; | import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; | ||||||
| import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification'; | import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification'; | ||||||
| @@ -63,7 +63,6 @@ import ErrorDialog from "../dialogs/ErrorDialog"; | |||||||
| import QuestionDialog from "../dialogs/QuestionDialog"; | import QuestionDialog from "../dialogs/QuestionDialog"; | ||||||
| import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog"; | import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog"; | ||||||
| import InfoDialog from "../dialogs/InfoDialog"; | import InfoDialog from "../dialogs/InfoDialog"; | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import RoomAvatar from "../avatars/RoomAvatar"; | import RoomAvatar from "../avatars/RoomAvatar"; | ||||||
| import RoomName from "../elements/RoomName"; | import RoomName from "../elements/RoomName"; | ||||||
| import { mediaFromMxc } from "../../../customisations/Media"; | import { mediaFromMxc } from "../../../customisations/Media"; | ||||||
| @@ -75,6 +74,8 @@ import { bulkSpaceBehaviour } from "../../../utils/space"; | |||||||
| import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; | import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; | ||||||
| import { UIComponent } from "../../../settings/UIFeature"; | import { UIComponent } from "../../../settings/UIFeature"; | ||||||
| import { TimelineRenderingType } from "../../../contexts/RoomContext"; | import { TimelineRenderingType } from "../../../contexts/RoomContext"; | ||||||
|  | import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; | ||||||
|  | import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState'; | ||||||
| import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage"; | import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage"; | ||||||
|  |  | ||||||
| export interface IDevice { | export interface IDevice { | ||||||
| @@ -1649,25 +1650,22 @@ const UserInfo: React.FC<IProps> = ({ | |||||||
|  |  | ||||||
|     const classes = ["mx_UserInfo"]; |     const classes = ["mx_UserInfo"]; | ||||||
|  |  | ||||||
|     let refireParams; |     let cardState: IRightPanelCardState; | ||||||
|     let previousPhase: RightPanelPhases; |     let previousPhase: RightPanelPhases; | ||||||
|     // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time |     // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time | ||||||
|     if (room && phase === RightPanelPhases.EncryptionPanel) { |     if (room && phase === RightPanelPhases.EncryptionPanel) { | ||||||
|         previousPhase = RightPanelPhases.RoomMemberInfo; |         previousPhase = RightPanelPhases.RoomMemberInfo; | ||||||
|         refireParams = { member }; |         cardState = { member }; | ||||||
|     } else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) { |     } else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) { | ||||||
|         previousPhase = previousPhase = RightPanelPhases.SpaceMemberList; |         previousPhase = RightPanelPhases.SpaceMemberList; | ||||||
|         refireParams = { space: room }; |         cardState = { spaceId: room.roomId }; | ||||||
|     } else if (room) { |     } else if (room) { | ||||||
|         previousPhase = RightPanelPhases.RoomMemberList; |         previousPhase = RightPanelPhases.RoomMemberList; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const onEncryptionPanelClose = () => { |     const onEncryptionPanelClose = () => { | ||||||
|         dis.dispatch<SetRightPanelPhasePayload>({ |         // TODO RightPanelStore (will be addressed in a follow up PR): here we want to pop the panel | ||||||
|             action: Action.SetRightPanelPhase, |         RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState }); | ||||||
|             phase: previousPhase, |  | ||||||
|             refireParams: refireParams, |  | ||||||
|         }); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let content; |     let content; | ||||||
| @@ -1723,7 +1721,7 @@ const UserInfo: React.FC<IProps> = ({ | |||||||
|         onClose={onClose} |         onClose={onClose} | ||||||
|         closeLabel={closeLabel} |         closeLabel={closeLabel} | ||||||
|         previousPhase={previousPhase} |         previousPhase={previousPhase} | ||||||
|         refireParams={refireParams} |         cardState={cardState} | ||||||
|     > |     > | ||||||
|         { content } |         { content } | ||||||
|     </BaseCard>; |     </BaseCard>; | ||||||
|   | |||||||
| @@ -23,14 +23,12 @@ import WidgetUtils from "../../../utils/WidgetUtils"; | |||||||
| import AppTile from "../elements/AppTile"; | import AppTile from "../elements/AppTile"; | ||||||
| import { _t } from "../../../languageHandler"; | import { _t } from "../../../languageHandler"; | ||||||
| import { useWidgets } from "./RoomSummaryCard"; | import { useWidgets } from "./RoomSummaryCard"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import defaultDispatcher from "../../../dispatcher/dispatcher"; |  | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import { Action } from "../../../dispatcher/actions"; |  | ||||||
| import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu"; | import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu"; | ||||||
| import WidgetContextMenu from "../context_menus/WidgetContextMenu"; | import WidgetContextMenu from "../context_menus/WidgetContextMenu"; | ||||||
| import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; | import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; | ||||||
| import UIStore from "../../../stores/UIStore"; | import UIStore from "../../../stores/UIStore"; | ||||||
|  | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     room: Room; |     room: Room; | ||||||
| @@ -50,10 +48,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => { | |||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|         if (!app || isPinned) { |         if (!app || isPinned) { | ||||||
|             // stop showing this card |             // stop showing this card | ||||||
|             defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ |  | ||||||
|                 action: Action.SetRightPanelPhase, |             //TODO RightPanelStore (will be addressed in a follow up PR): here we want to just pop the widget card. | ||||||
|                 phase: RightPanelPhases.RoomSummary, |             RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }); | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|     }, [app, isPinned]); |     }, [app, isPinned]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ interface IState { | |||||||
|  |  | ||||||
| @replaceableComponent("views.rooms.AppsDrawer") | @replaceableComponent("views.rooms.AppsDrawer") | ||||||
| export default class AppsDrawer extends React.Component<IProps, IState> { | export default class AppsDrawer extends React.Component<IProps, IState> { | ||||||
|  |     private unmounted = false; | ||||||
|     private resizeContainer: HTMLDivElement; |     private resizeContainer: HTMLDivElement; | ||||||
|     private resizer: Resizer; |     private resizer: Resizer; | ||||||
|     private dispatcherRef: string; |     private dispatcherRef: string; | ||||||
| @@ -85,6 +86,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public componentWillUnmount(): void { |     public componentWillUnmount(): void { | ||||||
|  |         this.unmounted = true; | ||||||
|         ScalarMessaging.stopListening(); |         ScalarMessaging.stopListening(); | ||||||
|         WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps); |         WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps); | ||||||
|         if (this.dispatcherRef) dis.unregister(this.dispatcherRef); |         if (this.dispatcherRef) dis.unregister(this.dispatcherRef); | ||||||
| @@ -213,6 +215,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> { | |||||||
|     private centerApps = (): IApp[] => this.state.apps[Container.Center]; |     private centerApps = (): IApp[] => this.state.apps[Container.Center]; | ||||||
|  |  | ||||||
|     private updateApps = (): void => { |     private updateApps = (): void => { | ||||||
|  |         if (this.unmounted) return; | ||||||
|         this.setState({ |         this.setState({ | ||||||
|             apps: this.getApps(), |             apps: this.getApps(), | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ import { isValid3pidInvite } from "../../../RoomInvite"; | |||||||
| import { MatrixClientPeg } from "../../../MatrixClientPeg"; | import { MatrixClientPeg } from "../../../MatrixClientPeg"; | ||||||
| import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; | import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; | ||||||
| import BaseCard from "../right_panel/BaseCard"; | import BaseCard from "../right_panel/BaseCard"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import RoomAvatar from "../avatars/RoomAvatar"; | import RoomAvatar from "../avatars/RoomAvatar"; | ||||||
| import RoomName from "../elements/RoomName"; | import RoomName from "../elements/RoomName"; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ import { ContextMenuTooltipButton } from '../../structures/ContextMenu'; | |||||||
| import RoomContextMenu from "../context_menus/RoomContextMenu"; | import RoomContextMenu from "../context_menus/RoomContextMenu"; | ||||||
| import { contextMenuBelow } from './RoomTile'; | import { contextMenuBelow } from './RoomTile'; | ||||||
| import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore'; | import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore'; | ||||||
| import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { NotificationStateEvents } from '../../../stores/notifications/NotificationState'; | import { NotificationStateEvents } from '../../../stores/notifications/NotificationState'; | ||||||
|  |  | ||||||
| export interface ISearchInfo { | export interface ISearchInfo { | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ import { ButtonEvent } from "../elements/AccessibleButton"; | |||||||
| import Modal from "../../../Modal"; | import Modal from "../../../Modal"; | ||||||
| import EditCommunityPrototypeDialog from "../dialogs/EditCommunityPrototypeDialog"; | import EditCommunityPrototypeDialog from "../dialogs/EditCommunityPrototypeDialog"; | ||||||
| import { Action } from "../../../dispatcher/actions"; | import { Action } from "../../../dispatcher/actions"; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; | ||||||
| import ErrorDialog from "../dialogs/ErrorDialog"; | import ErrorDialog from "../dialogs/ErrorDialog"; | ||||||
| import { showCommunityInviteDialog } from "../../../RoomInvite"; | import { showCommunityInviteDialog } from "../../../RoomInvite"; | ||||||
| import { useDispatcher } from "../../../hooks/useDispatcher"; | import { useDispatcher } from "../../../hooks/useDispatcher"; | ||||||
| @@ -50,6 +50,7 @@ import { | |||||||
|     UPDATE_HOME_BEHAVIOUR, |     UPDATE_HOME_BEHAVIOUR, | ||||||
|     UPDATE_SELECTED_SPACE, |     UPDATE_SELECTED_SPACE, | ||||||
| } from "../../../stores/spaces"; | } from "../../../stores/spaces"; | ||||||
|  | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
| import TooltipTarget from "../elements/TooltipTarget"; | import TooltipTarget from "../elements/TooltipTarget"; | ||||||
|  |  | ||||||
| const contextMenuBelow = (elementRect: DOMRect) => { | const contextMenuBelow = (elementRect: DOMRect) => { | ||||||
| @@ -99,7 +100,7 @@ const PrototypeCommunityContextMenu = (props: ComponentProps<typeof SpaceContext | |||||||
|                 action: 'view_room', |                 action: 'view_room', | ||||||
|                 room_id: chat.roomId, |                 room_id: chat.roomId, | ||||||
|             }, true); |             }, true); | ||||||
|             dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList }); |             RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList }, undefined, chat.roomId); | ||||||
|         } else { |         } else { | ||||||
|             // "This should never happen" clauses go here for the prototype. |             // "This should never happen" clauses go here for the prototype. | ||||||
|             Modal.createTrackedDialog('Failed to find general chat', '', ErrorDialog, { |             Modal.createTrackedDialog('Failed to find general chat', '', ErrorDialog, { | ||||||
|   | |||||||
| @@ -21,8 +21,7 @@ import { logger } from "matrix-js-sdk/src/logger"; | |||||||
|  |  | ||||||
| import { _t } from '../../../languageHandler'; | import { _t } from '../../../languageHandler'; | ||||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; | import { MatrixClientPeg } from '../../../MatrixClientPeg'; | ||||||
| import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; | import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases'; | ||||||
| import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; |  | ||||||
| import { userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver"; | import { userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver"; | ||||||
| import dis from "../../../dispatcher/dispatcher"; | import dis from "../../../dispatcher/dispatcher"; | ||||||
| import ToastStore from "../../../stores/ToastStore"; | import ToastStore from "../../../stores/ToastStore"; | ||||||
| @@ -31,6 +30,7 @@ import GenericToast from "./GenericToast"; | |||||||
| import { Action } from "../../../dispatcher/actions"; | import { Action } from "../../../dispatcher/actions"; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
| import VerificationRequestDialog from "../dialogs/VerificationRequestDialog"; | import VerificationRequestDialog from "../dialogs/VerificationRequestDialog"; | ||||||
|  | import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; | ||||||
|  |  | ||||||
| interface IProps { | interface IProps { | ||||||
|     toastKey: string; |     toastKey: string; | ||||||
| @@ -115,14 +115,14 @@ export default class VerificationRequestToast extends React.PureComponent<IProps | |||||||
|                     room_id: request.channel.roomId, |                     room_id: request.channel.roomId, | ||||||
|                     should_peek: false, |                     should_peek: false, | ||||||
|                 }); |                 }); | ||||||
|                 dis.dispatch<SetRightPanelPhasePayload>({ |                 RightPanelStore.instance.setCard( | ||||||
|                     action: Action.SetRightPanelPhase, |                     { | ||||||
|                         phase: RightPanelPhases.EncryptionPanel, |                         phase: RightPanelPhases.EncryptionPanel, | ||||||
|                     refireParams: { |                         state: { verificationRequest: request, member: cli.getUser(request.otherUserId) }, | ||||||
|                         verificationRequest: request, |  | ||||||
|                         member: cli.getUser(request.otherUserId), |  | ||||||
|                     }, |                     }, | ||||||
|                 }); |                     undefined, | ||||||
|  |                     request.channel.roomId, | ||||||
|  |                 ); | ||||||
|             } else { |             } else { | ||||||
|                 Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, { |                 Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, { | ||||||
|                     verificationRequest: request, |                     verificationRequest: request, | ||||||
|   | |||||||
| @@ -102,16 +102,6 @@ export enum Action { | |||||||
|      */ |      */ | ||||||
|     ViewRoomDelta = "view_room_delta", |     ViewRoomDelta = "view_room_delta", | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Sets the phase for the right panel. Should be used with SetRightPanelPhasePayload. |  | ||||||
|      */ |  | ||||||
|     SetRightPanelPhase = "set_right_panel_phase", |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Toggles the right panel. Should be used with ToggleRightPanelPayload. |  | ||||||
|      */ |  | ||||||
|     ToggleRightPanel = "toggle_right_panel", |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. |      * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -15,31 +15,26 @@ limitations under the License. | |||||||
| */ | */ | ||||||
| import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | ||||||
|  |  | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; | import RightPanelStore from "../../stores/right-panel/RightPanelStore"; | ||||||
| import { Action } from "../actions"; | import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; | ||||||
| import dis from '../dispatcher'; |  | ||||||
| import { SetRightPanelPhasePayload } from "../payloads/SetRightPanelPhasePayload"; |  | ||||||
|  |  | ||||||
| export const dispatchShowThreadEvent = ( | export const dispatchShowThreadEvent = ( | ||||||
|     rootEvent: MatrixEvent, |     rootEvent: MatrixEvent, | ||||||
|     initialEvent?: MatrixEvent, |     initialEvent?: MatrixEvent, | ||||||
|     highlighted?: boolean, |     highlighted?: boolean, | ||||||
| ) => { | ) => { | ||||||
|     dis.dispatch({ |     // TODO RightPanelStore (will be addressed in a follow up PR): this should really be a push! | ||||||
|         action: Action.SetRightPanelPhase, |     RightPanelStore.instance.setCard({ | ||||||
|         phase: RightPanelPhases.ThreadView, |         phase: RightPanelPhases.ThreadView, | ||||||
|         refireParams: { |         state: { | ||||||
|             event: rootEvent, |             threadHeadEvent: rootEvent, | ||||||
|             initialEvent, |             initialEvent, | ||||||
|             highlighted, |             isInitialEventHighlighted: highlighted, | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const dispatchShowThreadsPanelEvent = () => { | export const dispatchShowThreadsPanelEvent = () => { | ||||||
|     dis.dispatch<SetRightPanelPhasePayload>({ |     RightPanelStore.instance.setCard({ phase: RightPanelPhases.ThreadPanel }); | ||||||
|         action: Action.SetRightPanelPhase, |  | ||||||
|         phase: RightPanelPhases.ThreadPanel, |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,31 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2020 The Matrix.org Foundation C.I.C. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; |  | ||||||
|  |  | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; |  | ||||||
| import { SetRightPanelPhaseRefireParams } from "./SetRightPanelPhasePayload"; |  | ||||||
| import { ActionPayload } from "../payloads"; |  | ||||||
| import { Action } from "../actions"; |  | ||||||
|  |  | ||||||
| interface AfterRightPanelPhaseChangeAction extends ActionPayload { |  | ||||||
|     action: Action.AfterRightPanelPhaseChange; |  | ||||||
|     phase: RightPanelPhases; |  | ||||||
|     verificationRequestPromise?: Promise<VerificationRequest>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export type AfterRightPanelPhaseChangePayload |  | ||||||
|     = AfterRightPanelPhaseChangeAction & SetRightPanelPhaseRefireParams; |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2020 The Matrix.org Foundation C.I.C. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; |  | ||||||
| import { Room } from "matrix-js-sdk/src/models/room"; |  | ||||||
| import { RoomMember } from "matrix-js-sdk/src/models/room-member"; |  | ||||||
| import { User } from "matrix-js-sdk/src/models/user"; |  | ||||||
|  |  | ||||||
| import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; |  | ||||||
| import { ActionPayload } from "../payloads"; |  | ||||||
| import { Action } from "../actions"; |  | ||||||
|  |  | ||||||
| export interface SetRightPanelPhasePayload extends ActionPayload { |  | ||||||
|     action: Action.SetRightPanelPhase; |  | ||||||
|  |  | ||||||
|     phase: RightPanelPhases; |  | ||||||
|     refireParams?: SetRightPanelPhaseRefireParams; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * By default SetRightPanelPhase can close the panel, this allows overriding that behaviour |  | ||||||
|      */ |  | ||||||
|     allowClose?: boolean; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export interface SetRightPanelPhaseRefireParams { |  | ||||||
|     member?: RoomMember | User; |  | ||||||
|     verificationRequest?: VerificationRequest; |  | ||||||
|     groupId?: string; |  | ||||||
|     groupRoomId?: string; |  | ||||||
|     // XXX: The type for event should 'view_3pid_invite' action's payload |  | ||||||
|     event?: any; |  | ||||||
|     widgetId?: string; |  | ||||||
|     space?: Room; |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2020 The Matrix.org Foundation C.I.C. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| import { ActionPayload } from "../payloads"; |  | ||||||
| import { Action } from "../actions"; |  | ||||||
|  |  | ||||||
| export interface ToggleRightPanelPayload extends ActionPayload { |  | ||||||
|     action: Action.ToggleRightPanel; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The type of room that the panel is toggled in. |  | ||||||
|      */ |  | ||||||
|     type: "group" | "room"; |  | ||||||
| } |  | ||||||
| @@ -32,7 +32,6 @@ import SystemFontController from './controllers/SystemFontController'; | |||||||
| import UseSystemFontController from './controllers/UseSystemFontController'; | import UseSystemFontController from './controllers/UseSystemFontController'; | ||||||
| import { SettingLevel } from "./SettingLevel"; | import { SettingLevel } from "./SettingLevel"; | ||||||
| import SettingController from "./controllers/SettingController"; | import SettingController from "./controllers/SettingController"; | ||||||
| import { RightPanelPhases } from "../stores/RightPanelStorePhases"; |  | ||||||
| import { isMac } from '../Keyboard'; | import { isMac } from '../Keyboard'; | ||||||
| import UIFeatureController from "./controllers/UIFeatureController"; | import UIFeatureController from "./controllers/UIFeatureController"; | ||||||
| import { UIFeature } from "./UIFeature"; | import { UIFeature } from "./UIFeature"; | ||||||
| @@ -771,21 +770,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { | |||||||
|         displayName: _td("Show previews/thumbnails for images"), |         displayName: _td("Show previews/thumbnails for images"), | ||||||
|         default: true, |         default: true, | ||||||
|     }, |     }, | ||||||
|     "showRightPanelInRoom": { |     "RightPanel.phasesGlobal": { | ||||||
|         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, |         supportedLevels: [SettingLevel.DEVICE], | ||||||
|         default: false, |         default: null, | ||||||
|     }, |     }, | ||||||
|     "showRightPanelInGroup": { |     "RightPanel.phases": { | ||||||
|         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, |         supportedLevels: [SettingLevel.ROOM_DEVICE], | ||||||
|         default: false, |         default: null, | ||||||
|     }, |  | ||||||
|     "lastRightPanelPhaseForRoom": { |  | ||||||
|         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, |  | ||||||
|         default: RightPanelPhases.RoomSummary, |  | ||||||
|     }, |  | ||||||
|     "lastRightPanelPhaseForGroup": { |  | ||||||
|         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, |  | ||||||
|         default: RightPanelPhases.GroupMemberList, |  | ||||||
|     }, |     }, | ||||||
|     "enableEventIndexing": { |     "enableEventIndexing": { | ||||||
|         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, |         supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, | ||||||
|   | |||||||
| @@ -57,17 +57,6 @@ export default class DeviceSettingsHandler extends SettingsHandler { | |||||||
|             return null; // wrong type or otherwise not set |             return null; // wrong type or otherwise not set | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Special case the right panel - see `setValue` for rationale. |  | ||||||
|         if ([ |  | ||||||
|             "showRightPanelInRoom", |  | ||||||
|             "showRightPanelInGroup", |  | ||||||
|             "lastRightPanelPhaseForRoom", |  | ||||||
|             "lastRightPanelPhaseForGroup", |  | ||||||
|         ].includes(settingName)) { |  | ||||||
|             const val = JSON.parse(localStorage.getItem(`mx_${settingName}`) || "{}"); |  | ||||||
|             return val['value']; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Special case for old useIRCLayout setting |         // Special case for old useIRCLayout setting | ||||||
|         if (settingName === "layout") { |         if (settingName === "layout") { | ||||||
|             const settings = this.getSettings() || {}; |             const settings = this.getSettings() || {}; | ||||||
| @@ -106,20 +95,6 @@ export default class DeviceSettingsHandler extends SettingsHandler { | |||||||
|             return Promise.resolve(); |             return Promise.resolve(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Special case the right panel because we want to be able to update these all |  | ||||||
|         // concurrently without stomping on one another. We could use async/await, though |  | ||||||
|         // that introduces just enough latency to be annoying. |  | ||||||
|         if ([ |  | ||||||
|             "showRightPanelInRoom", |  | ||||||
|             "showRightPanelInGroup", |  | ||||||
|             "lastRightPanelPhaseForRoom", |  | ||||||
|             "lastRightPanelPhaseForGroup", |  | ||||||
|         ].includes(settingName)) { |  | ||||||
|             localStorage.setItem(`mx_${settingName}`, JSON.stringify({ value: newValue })); |  | ||||||
|             this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue); |  | ||||||
|             return Promise.resolve(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Special case for old useIRCLayout setting |         // Special case for old useIRCLayout setting | ||||||
|         if (settingName === "layout") { |         if (settingName === "layout") { | ||||||
|             const settings = this.getSettings() || {}; |             const settings = this.getSettings() || {}; | ||||||
|   | |||||||
| @@ -1,245 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2019 The Matrix.org Foundation C.I.C. |  | ||||||
|  |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| import { Store } from 'flux/utils'; |  | ||||||
| import { logger } from "matrix-js-sdk/src/logger"; |  | ||||||
|  |  | ||||||
| import dis from '../dispatcher/dispatcher'; |  | ||||||
| import { pendingVerificationRequestForUser } from '../verification'; |  | ||||||
| import SettingsStore from "../settings/SettingsStore"; |  | ||||||
| import { RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS } from "./RightPanelStorePhases"; |  | ||||||
| import { ActionPayload } from "../dispatcher/payloads"; |  | ||||||
| import { Action } from '../dispatcher/actions'; |  | ||||||
| import { SettingLevel } from "../settings/SettingLevel"; |  | ||||||
|  |  | ||||||
| interface RightPanelStoreState { |  | ||||||
|     // Whether or not to show the right panel at all. We split out rooms and groups |  | ||||||
|     // because they're different flows for the user to follow. |  | ||||||
|     showRoomPanel: boolean; |  | ||||||
|     showGroupPanel: boolean; |  | ||||||
|  |  | ||||||
|     // The last phase (screen) the right panel was showing |  | ||||||
|     lastRoomPhase: RightPanelPhases; |  | ||||||
|     lastGroupPhase: RightPanelPhases; |  | ||||||
|  |  | ||||||
|     previousPhase?: RightPanelPhases; |  | ||||||
|  |  | ||||||
|     // Extra information about the last phase |  | ||||||
|     lastRoomPhaseParams: {[key: string]: any}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const INITIAL_STATE: RightPanelStoreState = { |  | ||||||
|     showRoomPanel: SettingsStore.getValue("showRightPanelInRoom"), |  | ||||||
|     showGroupPanel: SettingsStore.getValue("showRightPanelInGroup"), |  | ||||||
|     lastRoomPhase: SettingsStore.getValue("lastRightPanelPhaseForRoom"), |  | ||||||
|     lastGroupPhase: SettingsStore.getValue("lastRightPanelPhaseForGroup"), |  | ||||||
|     lastRoomPhaseParams: {}, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const GROUP_PHASES = [ |  | ||||||
|     RightPanelPhases.GroupMemberList, |  | ||||||
|     RightPanelPhases.GroupRoomList, |  | ||||||
|     RightPanelPhases.GroupRoomInfo, |  | ||||||
|     RightPanelPhases.GroupMemberInfo, |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| const MEMBER_INFO_PHASES = [ |  | ||||||
|     RightPanelPhases.RoomMemberInfo, |  | ||||||
|     RightPanelPhases.Room3pidMemberInfo, |  | ||||||
|     RightPanelPhases.EncryptionPanel, |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A class for tracking the state of the right panel between layouts and |  | ||||||
|  * sessions. |  | ||||||
|  */ |  | ||||||
| export default class RightPanelStore extends Store<ActionPayload> { |  | ||||||
|     private static instance: RightPanelStore; |  | ||||||
|     private state: RightPanelStoreState; |  | ||||||
|     private lastRoomId: string; |  | ||||||
|  |  | ||||||
|     constructor() { |  | ||||||
|         super(dis); |  | ||||||
|  |  | ||||||
|         // Initialise state |  | ||||||
|         this.state = INITIAL_STATE; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get isOpenForRoom(): boolean { |  | ||||||
|         return this.state.showRoomPanel; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get isOpenForGroup(): boolean { |  | ||||||
|         return this.state.showGroupPanel; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get roomPanelPhase(): RightPanelPhases { |  | ||||||
|         return this.state.lastRoomPhase; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get groupPanelPhase(): RightPanelPhases { |  | ||||||
|         return this.state.lastGroupPhase; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get previousPhase(): RightPanelPhases | null { |  | ||||||
|         return RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.previousPhase) ? this.state.previousPhase : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get visibleRoomPanelPhase(): RightPanelPhases { |  | ||||||
|         return this.isOpenForRoom ? this.roomPanelPhase : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get visibleGroupPanelPhase(): RightPanelPhases { |  | ||||||
|         return this.isOpenForGroup ? this.groupPanelPhase : null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get roomPanelPhaseParams(): any { |  | ||||||
|         return this.state.lastRoomPhaseParams || {}; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private setState(newState: Partial<RightPanelStoreState>) { |  | ||||||
|         this.state = Object.assign(this.state, newState); |  | ||||||
|  |  | ||||||
|         SettingsStore.setValue( |  | ||||||
|             "showRightPanelInRoom", |  | ||||||
|             null, |  | ||||||
|             SettingLevel.DEVICE, |  | ||||||
|             this.state.showRoomPanel, |  | ||||||
|         ); |  | ||||||
|         SettingsStore.setValue( |  | ||||||
|             "showRightPanelInGroup", |  | ||||||
|             null, |  | ||||||
|             SettingLevel.DEVICE, |  | ||||||
|             this.state.showGroupPanel, |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastRoomPhase)) { |  | ||||||
|             SettingsStore.setValue( |  | ||||||
|                 "lastRightPanelPhaseForRoom", |  | ||||||
|                 null, |  | ||||||
|                 SettingLevel.DEVICE, |  | ||||||
|                 this.state.lastRoomPhase, |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastGroupPhase)) { |  | ||||||
|             SettingsStore.setValue( |  | ||||||
|                 "lastRightPanelPhaseForGroup", |  | ||||||
|                 null, |  | ||||||
|                 SettingLevel.DEVICE, |  | ||||||
|                 this.state.lastGroupPhase, |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.__emitChange(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention |  | ||||||
|         switch (payload.action) { |  | ||||||
|             case Action.ViewRoom: |  | ||||||
|                 if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink |  | ||||||
|                 // fallthrough |  | ||||||
|             case 'view_group': |  | ||||||
|                 this.lastRoomId = payload.room_id; |  | ||||||
|  |  | ||||||
|                 // Reset to the member list if we're viewing member info |  | ||||||
|                 if (MEMBER_INFO_PHASES.includes(this.state.lastRoomPhase)) { |  | ||||||
|                     this.setState({ lastRoomPhase: RightPanelPhases.RoomMemberList, lastRoomPhaseParams: {} }); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Do the same for groups |  | ||||||
|                 if (this.state.lastGroupPhase === RightPanelPhases.GroupMemberInfo) { |  | ||||||
|                     this.setState({ lastGroupPhase: RightPanelPhases.GroupMemberList }); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|             case Action.SetRightPanelPhase: { |  | ||||||
|                 let targetPhase = payload.phase; |  | ||||||
|                 let refireParams = payload.refireParams; |  | ||||||
|                 const allowClose = payload.allowClose ?? true; |  | ||||||
|                 // redirect to EncryptionPanel if there is an ongoing verification request |  | ||||||
|                 if (targetPhase === RightPanelPhases.RoomMemberInfo && payload.refireParams) { |  | ||||||
|                     const { member } = payload.refireParams; |  | ||||||
|                     const pendingRequest = pendingVerificationRequestForUser(member); |  | ||||||
|                     if (pendingRequest) { |  | ||||||
|                         targetPhase = RightPanelPhases.EncryptionPanel; |  | ||||||
|                         refireParams = { |  | ||||||
|                             verificationRequest: pendingRequest, |  | ||||||
|                             member, |  | ||||||
|                         }; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (!RightPanelPhases[targetPhase]) { |  | ||||||
|                     logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (GROUP_PHASES.includes(targetPhase)) { |  | ||||||
|                     if (targetPhase === this.state.lastGroupPhase) { |  | ||||||
|                         this.setState({ |  | ||||||
|                             showGroupPanel: !this.state.showGroupPanel, |  | ||||||
|                             previousPhase: null, |  | ||||||
|                         }); |  | ||||||
|                     } else { |  | ||||||
|                         this.setState({ |  | ||||||
|                             lastGroupPhase: targetPhase, |  | ||||||
|                             showGroupPanel: true, |  | ||||||
|                             previousPhase: this.state.lastGroupPhase, |  | ||||||
|                         }); |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (targetPhase === this.state.lastRoomPhase && !refireParams && allowClose) { |  | ||||||
|                         this.setState({ |  | ||||||
|                             showRoomPanel: !this.state.showRoomPanel, |  | ||||||
|                             previousPhase: null, |  | ||||||
|                         }); |  | ||||||
|                     } else { |  | ||||||
|                         this.setState({ |  | ||||||
|                             lastRoomPhase: targetPhase, |  | ||||||
|                             showRoomPanel: true, |  | ||||||
|                             lastRoomPhaseParams: refireParams || {}, |  | ||||||
|                             previousPhase: this.state.lastRoomPhase, |  | ||||||
|                         }); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Let things like the member info panel actually open to the right member. |  | ||||||
|                 dis.dispatch({ |  | ||||||
|                     action: Action.AfterRightPanelPhaseChange, |  | ||||||
|                     phase: targetPhase, |  | ||||||
|                     ...(refireParams || {}), |  | ||||||
|                 }); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             case Action.ToggleRightPanel: |  | ||||||
|                 if (payload.type === "room") { |  | ||||||
|                     this.setState({ showRoomPanel: !this.state.showRoomPanel }); |  | ||||||
|                 } else { // group |  | ||||||
|                     this.setState({ showGroupPanel: !this.state.showGroupPanel }); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static getSharedInstance(): RightPanelStore { |  | ||||||
|         if (!RightPanelStore.instance) { |  | ||||||
|             RightPanelStore.instance = new RightPanelStore(); |  | ||||||
|         } |  | ||||||
|         return RightPanelStore.instance; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| window.mxRightPanelStore = RightPanelStore.getSharedInstance(); |  | ||||||
							
								
								
									
										370
									
								
								src/stores/right-panel/RightPanelStore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								src/stores/right-panel/RightPanelStore.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,370 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2019-2021 The Matrix.org Foundation C.I.C. | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  |  | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | import { logger } from "matrix-js-sdk/src/logger"; | ||||||
|  | import { EventSubscription } from 'fbemitter'; | ||||||
|  |  | ||||||
|  | import defaultDispatcher from '../../dispatcher/dispatcher'; | ||||||
|  | import { pendingVerificationRequestForUser } from '../../verification'; | ||||||
|  | import SettingsStore from "../../settings/SettingsStore"; | ||||||
|  | import { RightPanelPhases } from "./RightPanelStorePhases"; | ||||||
|  | import { ActionPayload } from "../../dispatcher/payloads"; | ||||||
|  | import { Action } from '../../dispatcher/actions'; | ||||||
|  | import { SettingLevel } from "../../settings/SettingLevel"; | ||||||
|  | import { UPDATE_EVENT } from '../AsyncStore'; | ||||||
|  | import { ReadyWatchingStore } from '../ReadyWatchingStore'; | ||||||
|  | import { | ||||||
|  |     IRightPanelCard, | ||||||
|  |     convertToStatePanel, | ||||||
|  |     convertToStorePanel, | ||||||
|  |     IRightPanelForRoom, | ||||||
|  |     convertCardToStore, | ||||||
|  | } from './RightPanelStoreIPanelState'; | ||||||
|  | import { MatrixClientPeg } from "../../MatrixClientPeg"; | ||||||
|  | // import RoomViewStore from '../RoomViewStore'; | ||||||
|  |  | ||||||
|  | const GROUP_PHASES = [ | ||||||
|  |     RightPanelPhases.GroupMemberList, | ||||||
|  |     RightPanelPhases.GroupRoomList, | ||||||
|  |     RightPanelPhases.GroupRoomInfo, | ||||||
|  |     RightPanelPhases.GroupMemberInfo, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | const MEMBER_INFO_PHASES = [ | ||||||
|  |     RightPanelPhases.RoomMemberInfo, | ||||||
|  |     RightPanelPhases.Room3pidMemberInfo, | ||||||
|  |     RightPanelPhases.EncryptionPanel, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A class for tracking the state of the right panel between layouts and | ||||||
|  |  * sessions. This state includes a history for each room. Each history element | ||||||
|  |  * contains the phase (e.g. RightPanelPhase.RoomMemberInfo) and the state (e.g. | ||||||
|  |  * the member) associated with it. | ||||||
|  |  * Groups are treated the same as rooms (they are also stored in the byRoom | ||||||
|  |  * object). This is possible since the store keeps track of the opened | ||||||
|  |  * room/group -> the store will provide the correct history for that group/room. | ||||||
|  | */ | ||||||
|  | export default class RightPanelStore extends ReadyWatchingStore { | ||||||
|  |     private static internalInstance: RightPanelStore; | ||||||
|  |     private viewedRoomId: string; | ||||||
|  |     private isViewingRoom?: boolean; | ||||||
|  |     private dispatcherRefRightPanelStore: string; | ||||||
|  |     private roomStoreToken: EventSubscription; | ||||||
|  |  | ||||||
|  |     private global?: IRightPanelForRoom = null; | ||||||
|  |     private byRoom: { | ||||||
|  |         [roomId: string]: IRightPanelForRoom; | ||||||
|  |     } = {}; | ||||||
|  |  | ||||||
|  |     private constructor() { | ||||||
|  |         super(defaultDispatcher); | ||||||
|  |         this.dispatcherRefRightPanelStore = defaultDispatcher.register(this.onDispatch); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected async onReady(): Promise<any> { | ||||||
|  |         // TODO RightPanelStore (will be addressed when dropping groups): This should be used instead of the onDispatch callback when groups are removed. | ||||||
|  |         // RoomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); | ||||||
|  |         MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequestUpdate); | ||||||
|  |         this.loadCacheFromSettings(); | ||||||
|  |         this.emitAndUpdateSettings(); | ||||||
|  |     } | ||||||
|  |     public destroy() { | ||||||
|  |         if (this.dispatcherRefRightPanelStore) { | ||||||
|  |             defaultDispatcher.unregister(this.dispatcherRefRightPanelStore); | ||||||
|  |         } | ||||||
|  |         super.destroy(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected async onNotReady(): Promise<any> { | ||||||
|  |         if (this.roomStoreToken) { | ||||||
|  |             this.roomStoreToken.remove(); | ||||||
|  |         } | ||||||
|  |         MatrixClientPeg.get().off("crypto.verification.request", this.onVerificationRequestUpdate); | ||||||
|  |         // TODO RightPanelStore (will be addressed when dropping groups): User this instead of the dispatcher. | ||||||
|  |         // RoomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Getters | ||||||
|  |     public get isOpenForRoom(): boolean { | ||||||
|  |         return this.byRoom[this.viewedRoomId]?.isOpen ?? false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public get roomPhaseHistory(): Array<IRightPanelCard> { | ||||||
|  |         return this.byRoom[this.viewedRoomId]?.history ?? []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public get currentCard(): IRightPanelCard { | ||||||
|  |         const hist = this.roomPhaseHistory; | ||||||
|  |         if (hist.length >= 1) { | ||||||
|  |             return hist[hist.length - 1]; | ||||||
|  |         } | ||||||
|  |         return { state: {}, phase: null }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public currentCardForRoom(roomId: string): IRightPanelCard { | ||||||
|  |         const hist = this.byRoom[roomId]?.history ?? []; | ||||||
|  |         if (hist.length > 0) { | ||||||
|  |             return hist[hist.length - 1]; | ||||||
|  |         } | ||||||
|  |         return this.currentCard ?? { state: {}, phase: null }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public get previousCard(): IRightPanelCard { | ||||||
|  |         const hist = this.roomPhaseHistory; | ||||||
|  |         if (hist?.length >= 2) { | ||||||
|  |             return hist[hist.length - 2]; | ||||||
|  |         } | ||||||
|  |         return { state: {}, phase: null }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // The Group associated getters are just for backwards compatibility. Can be removed when deprecating groups. | ||||||
|  |     public get isOpenForGroup(): boolean { return this.isOpenForRoom; } | ||||||
|  |     public get groupPhaseHistory(): Array<IRightPanelCard> { return this.roomPhaseHistory; } | ||||||
|  |     public get currentGroup(): IRightPanelCard { return this.currentCard; } | ||||||
|  |     public get previousGroup(): IRightPanelCard { return this.previousCard; } | ||||||
|  |  | ||||||
|  |     // Setters | ||||||
|  |     public setCard(card: IRightPanelCard, allowClose = true, roomId?: string) { | ||||||
|  |         const rId = roomId ?? this.viewedRoomId; | ||||||
|  |         // this was previously a very multifunctional command: | ||||||
|  |         // Toggle panel: if the same phase is send but without a state | ||||||
|  |         // Update state: if the same phase is send but with a state | ||||||
|  |         // Set right panel and erase history: if a "different to the current" phase is send (with or without a state) | ||||||
|  |         const redirect = this.getVerificationRedirect(card); | ||||||
|  |         const targetPhase = redirect?.phase ?? card.phase; | ||||||
|  |         const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); | ||||||
|  |  | ||||||
|  |         // Checks for wrong SetRightPanelPhase requests | ||||||
|  |         if (!this.isPhaseActionIsValid(targetPhase)) return; | ||||||
|  |  | ||||||
|  |         if (targetPhase === this.currentCard?.phase && | ||||||
|  |             allowClose && | ||||||
|  |             (this.compareCards({ phase: targetPhase, state: cardState }, this.currentCard) || !cardState) | ||||||
|  |         ) { | ||||||
|  |             // Toggle panel: a toggle command needs to fullfil the following: | ||||||
|  |             // - the same phase | ||||||
|  |             // - the panel can be closed | ||||||
|  |             // - does not contain any state information (state) | ||||||
|  |             if (targetPhase != RightPanelPhases.EncryptionPanel) { | ||||||
|  |                 this.togglePanel(rId); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } else if ((targetPhase === this.currentCardForRoom(rId)?.phase && !!cardState)) { | ||||||
|  |             // Update state: set right panel with a new state but keep the phase (dont know it this is ever needed...) | ||||||
|  |             const hist = this.byRoom[rId]?.history ?? []; | ||||||
|  |             hist[hist.length - 1].state = cardState; | ||||||
|  |             this.emitAndUpdateSettings(); | ||||||
|  |             return; | ||||||
|  |         } else if (targetPhase !== this.currentCard?.phase) { | ||||||
|  |             // Set right panel and erase history. | ||||||
|  |             this.setRightPanelCache({ phase: targetPhase, state: cardState ?? {} }, rId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public pushCard( | ||||||
|  |         card: IRightPanelCard, | ||||||
|  |         allowClose = true, | ||||||
|  |         roomId: string = null, | ||||||
|  |     ) { | ||||||
|  |         const rId = roomId ?? this.viewedRoomId; | ||||||
|  |         const redirect = this.getVerificationRedirect(card); | ||||||
|  |         const targetPhase = redirect?.phase ?? card.phase; | ||||||
|  |         const pState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); | ||||||
|  |  | ||||||
|  |         // Checks for wrong SetRightPanelPhase requests | ||||||
|  |         if (!this.isPhaseActionIsValid(targetPhase)) return; | ||||||
|  |  | ||||||
|  |         let roomCache = this.byRoom[rId]; | ||||||
|  |         if (!!roomCache) { | ||||||
|  |             // append new phase | ||||||
|  |             roomCache.history.push({ state: pState, phase: targetPhase }); | ||||||
|  |             roomCache.isOpen = allowClose ? roomCache.isOpen : true; | ||||||
|  |         } else { | ||||||
|  |             // setup room panel cache with the new card | ||||||
|  |             roomCache = { | ||||||
|  |                 history: [{ phase: targetPhase, state: pState ?? {} }], | ||||||
|  |                 // if there was no right panel store object the the panel was closed -> keep it closed, except if allowClose==false | ||||||
|  |                 isOpen: !allowClose, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.emitAndUpdateSettings(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public popCard(roomId: string = null) { | ||||||
|  |         const rId = roomId ?? this.viewedRoomId; | ||||||
|  |         if (!this.byRoom[rId]) return; | ||||||
|  |  | ||||||
|  |         const removedCard = this.byRoom[rId].history.pop(); | ||||||
|  |         this.emitAndUpdateSettings(); | ||||||
|  |         return removedCard; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public togglePanel(roomId: string = null) { | ||||||
|  |         const rId = roomId ?? this.viewedRoomId; | ||||||
|  |         if (!this.byRoom[rId]) return; | ||||||
|  |  | ||||||
|  |         this.byRoom[rId].isOpen = !this.byRoom[rId].isOpen; | ||||||
|  |         this.emitAndUpdateSettings(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Private | ||||||
|  |     private loadCacheFromSettings() { | ||||||
|  |         const room = this.mxClient?.getRoom(this.viewedRoomId); | ||||||
|  |         if (!!room) { | ||||||
|  |             this.global = this.global ?? | ||||||
|  |                 convertToStatePanel(SettingsStore.getValue("RightPanel.phasesGlobal"), room); | ||||||
|  |             this.byRoom[this.viewedRoomId] = this.byRoom[this.viewedRoomId] ?? | ||||||
|  |                 convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room); | ||||||
|  |         } else { | ||||||
|  |             console.warn("Could not restore the right panel after load because there was no associated room object." + | ||||||
|  |                 "The right panel can only be restored for rooms and spaces but not for groups"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private compareCards(a: IRightPanelCard, b: IRightPanelCard): boolean { | ||||||
|  |         return JSON.stringify(convertCardToStore(a)) == JSON.stringify(convertCardToStore(b)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private emitAndUpdateSettings() { | ||||||
|  |         const storePanelGlobal = convertToStorePanel(this.global); | ||||||
|  |         SettingsStore.setValue("RightPanel.phasesGlobal", null, SettingLevel.DEVICE, storePanelGlobal); | ||||||
|  |  | ||||||
|  |         if (!!this.viewedRoomId) { | ||||||
|  |             const storePanelThisRoom = convertToStorePanel(this.byRoom[this.viewedRoomId]); | ||||||
|  |             SettingsStore.setValue( | ||||||
|  |                 "RightPanel.phases", | ||||||
|  |                 this.viewedRoomId, | ||||||
|  |                 SettingLevel.ROOM_DEVICE, | ||||||
|  |                 storePanelThisRoom, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         this.emit(UPDATE_EVENT, null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private setRightPanelCache(card: IRightPanelCard, roomId?: string) { | ||||||
|  |         this.byRoom[roomId ?? this.viewedRoomId] = { | ||||||
|  |             history: [{ phase: card.phase, state: card.state ?? {} }], | ||||||
|  |             isOpen: true, | ||||||
|  |         }; | ||||||
|  |         this.emitAndUpdateSettings(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private getVerificationRedirect(card: IRightPanelCard): IRightPanelCard { | ||||||
|  |         if (card.phase === RightPanelPhases.RoomMemberInfo && card.state) { | ||||||
|  |             // RightPanelPhases.RoomMemberInfo -> needs to be changed to RightPanelPhases.EncryptionPanel if there is a pending verification request | ||||||
|  |             const { member } = card.state; | ||||||
|  |             const pendingRequest = pendingVerificationRequestForUser(member); | ||||||
|  |             if (pendingRequest) { | ||||||
|  |                 return { | ||||||
|  |                     phase: RightPanelPhases.EncryptionPanel, | ||||||
|  |                     state: { | ||||||
|  |                         verificationRequest: pendingRequest, | ||||||
|  |                         member, | ||||||
|  |                     }, | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private isPhaseActionIsValid(targetPhase) { | ||||||
|  |         if (!RightPanelPhases[targetPhase]) { | ||||||
|  |             logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         if (GROUP_PHASES.includes(targetPhase) && this.isViewingRoom) { | ||||||
|  |             logger.warn( | ||||||
|  |                 `Tried to switch right panel to a group phase: ${targetPhase}, ` + | ||||||
|  |                 `but we are currently not viewing a group`, | ||||||
|  |             ); | ||||||
|  |             return false; | ||||||
|  |         } else if (!GROUP_PHASES.includes(targetPhase) && !this.isViewingRoom) { | ||||||
|  |             logger.warn( | ||||||
|  |                 `Tried to switch right panel to a room phase: ${targetPhase}, ` + | ||||||
|  |                 `but we are currently not viewing a room`, | ||||||
|  |             ); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private onVerificationRequestUpdate = () => { | ||||||
|  |         const { member } = this.currentCard.state; | ||||||
|  |         const pendingRequest = pendingVerificationRequestForUser(member); | ||||||
|  |         if (pendingRequest) { | ||||||
|  |             this.currentCard.state.verificationRequest = pendingRequest; | ||||||
|  |             this.emitAndUpdateSettings(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     onRoomViewStoreUpdate() { | ||||||
|  |         // TODO: use this function instead of the onDispatch (the whole onDispatch can get removed!) as soon groups are removed | ||||||
|  |         // this.viewedRoomId = RoomViewStore.getRoomId(); | ||||||
|  |         // this.isViewingRoom = true; // Is viewing room will of course be removed when removing groups | ||||||
|  |         // // load values from byRoomCache with the viewedRoomId. | ||||||
|  |         // this.loadCacheFromSettings(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onDispatch(payload: ActionPayload) { | ||||||
|  |         switch (payload.action) { | ||||||
|  |             case 'view_group': | ||||||
|  |             case Action.ViewRoom: { | ||||||
|  |                 const _this = RightPanelStore.instance; | ||||||
|  |                 if (payload.room_id === _this.viewedRoomId) break; // skip this transition, probably a permalink | ||||||
|  |  | ||||||
|  |                 // Put group in the same/similar view to what was open from the previously viewed room | ||||||
|  |                 // Is contradictory to the new "per room" philosophy but it is the legacy behavior for groups. | ||||||
|  |                 if ((_this.isViewingRoom ? Action.ViewRoom : "view_group") != payload.action) { | ||||||
|  |                     if (payload.action == Action.ViewRoom && MEMBER_INFO_PHASES.includes(_this.currentCard?.phase)) { | ||||||
|  |                         // switch from group to room | ||||||
|  |                         _this.setRightPanelCache({ phase: RightPanelPhases.RoomMemberList, state: {} }); | ||||||
|  |                     } else if ( | ||||||
|  |                         payload.action == "view_group" && | ||||||
|  |                         _this.currentCard?.phase === RightPanelPhases.GroupMemberInfo | ||||||
|  |                     ) { | ||||||
|  |                         // switch from room to group | ||||||
|  |                         _this.setRightPanelCache({ phase: RightPanelPhases.GroupMemberList, state: {} }); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Update the current room here, so that all the other functions dont need to be room dependant. | ||||||
|  |                 // The right panel store always will return the state for the current room. | ||||||
|  |                 _this.viewedRoomId = payload.room_id; | ||||||
|  |                 _this.isViewingRoom = payload.action == Action.ViewRoom; | ||||||
|  |                 // load values from byRoomCache with the viewedRoomId. | ||||||
|  |                 if (!!_this.roomStoreToken) { | ||||||
|  |                     // skip loading here since we need the client to be ready to get the events form the ids of the settings | ||||||
|  |                     // this loading will be done in the onReady function | ||||||
|  |                     // all the logic in this case is not necessary anymore as soon as groups are dropped and we use: onRoomViewStoreUpdate | ||||||
|  |                     _this.loadCacheFromSettings(); | ||||||
|  |                     _this.emitAndUpdateSettings(); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static get instance(): RightPanelStore { | ||||||
|  |         if (!RightPanelStore.internalInstance) { | ||||||
|  |             RightPanelStore.internalInstance = new RightPanelStore(); | ||||||
|  |         } | ||||||
|  |         return RightPanelStore.internalInstance; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | window.mxRightPanelStore = RightPanelStore.instance; | ||||||
							
								
								
									
										136
									
								
								src/stores/right-panel/RightPanelStoreIPanelState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/stores/right-panel/RightPanelStoreIPanelState.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2021 The Matrix.org Foundation C.I.C. | ||||||
|  |  | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  |  | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | ||||||
|  | import { User } from "matrix-js-sdk/src/models/user"; | ||||||
|  | import { Room } from "matrix-js-sdk/src/models/room"; | ||||||
|  | import { RoomMember } from "matrix-js-sdk/src/models/room-member"; | ||||||
|  | import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; | ||||||
|  |  | ||||||
|  | import { GroupMember } from "../../components/views/right_panel/UserInfo"; | ||||||
|  | import { RightPanelPhases } from "./RightPanelStorePhases"; | ||||||
|  |  | ||||||
|  | export interface IRightPanelCardState { | ||||||
|  |     member?: RoomMember | User | GroupMember; | ||||||
|  |     verificationRequest?: VerificationRequest; | ||||||
|  |     verificationRequestPromise?: Promise<VerificationRequest>; | ||||||
|  |     // group | ||||||
|  |     groupId?: string; | ||||||
|  |     groupRoomId?: string; | ||||||
|  |     widgetId?: string; | ||||||
|  |     spaceId?: string; | ||||||
|  |     // Room3pidMemberInfo, Space3pidMemberInfo, | ||||||
|  |     memberInfoEvent?: MatrixEvent; | ||||||
|  |     // threads | ||||||
|  |     threadHeadEvent?: MatrixEvent; | ||||||
|  |     initialEvent?: MatrixEvent; | ||||||
|  |     isInitialEventHighlighted?: boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IRightPanelCardStateStored { | ||||||
|  |     memberId?: string; | ||||||
|  |     // we do not store the things associated with verification | ||||||
|  |     // group | ||||||
|  |     groupId?: string; | ||||||
|  |     groupRoomId?: string; | ||||||
|  |     widgetId?: string; | ||||||
|  |     spaceId?: string; | ||||||
|  |     // 3pidMemberInfo | ||||||
|  |     memberInfoEventId?: string; | ||||||
|  |     // threads | ||||||
|  |     threadHeadEventId?: string; | ||||||
|  |     initialEventId?: string; | ||||||
|  |     isInitialEventHighlighted?: boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IRightPanelCard { | ||||||
|  |     phase: RightPanelPhases; | ||||||
|  |     state?: IRightPanelCardState; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IRightPanelCardStored { | ||||||
|  |     phase: RightPanelPhases; | ||||||
|  |     state?: IRightPanelCardStateStored; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IRightPanelForRoom { | ||||||
|  |     isOpen: boolean; | ||||||
|  |     history: Array<IRightPanelCard>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | interface IRightPanelForRoomStored { | ||||||
|  |     isOpen: boolean; | ||||||
|  |     history: Array<IRightPanelCardStored>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function convertToStorePanel(cacheRoom: IRightPanelForRoom): IRightPanelForRoomStored { | ||||||
|  |     if (!cacheRoom) return cacheRoom; | ||||||
|  |     const storeHistory = [...cacheRoom.history].map(panelState => convertCardToStore(panelState)); | ||||||
|  |     return { isOpen: cacheRoom.isOpen, history: storeHistory }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function convertToStatePanel(storeRoom: IRightPanelForRoomStored, room: Room): IRightPanelForRoom { | ||||||
|  |     if (!storeRoom) return storeRoom; | ||||||
|  |     const stateHistory = [...storeRoom.history].map(panelStateStore => convertStoreToCard(panelStateStore, room)); | ||||||
|  |     return { history: stateHistory, isOpen: storeRoom.isOpen }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function convertCardToStore(panelState: IRightPanelCard): IRightPanelCardStored { | ||||||
|  |     const panelStateThisRoomStored = { ...panelState.state } as any; | ||||||
|  |     if (!!panelState?.state?.threadHeadEvent?.getId()) { | ||||||
|  |         panelStateThisRoomStored.threadHeadEventId = panelState.state.threadHeadEvent.getId(); | ||||||
|  |     } | ||||||
|  |     if (!!panelState?.state?.memberInfoEvent?.getId()) { | ||||||
|  |         panelStateThisRoomStored.memberInfoEventId = panelState.state.memberInfoEvent.getId(); | ||||||
|  |     } | ||||||
|  |     if (!!panelState?.state?.initialEvent?.getId()) { | ||||||
|  |         panelStateThisRoomStored.initialEventId = panelState.state.initialEvent.getId(); | ||||||
|  |     } | ||||||
|  |     if (!!panelState?.state?.member?.userId) { | ||||||
|  |         panelStateThisRoomStored.memberId = panelState.state.member.userId; | ||||||
|  |     } | ||||||
|  |     delete panelStateThisRoomStored.threadHeadEvent; | ||||||
|  |     delete panelStateThisRoomStored.initialEvent; | ||||||
|  |     delete panelStateThisRoomStored.memberInfoEvent; | ||||||
|  |     delete panelStateThisRoomStored.verificationRequest; | ||||||
|  |     delete panelStateThisRoomStored.verificationRequestPromise; | ||||||
|  |     delete panelStateThisRoomStored.member; | ||||||
|  |  | ||||||
|  |     const storedCard = { state: panelStateThisRoomStored as IRightPanelCardStored, phase: panelState.phase }; | ||||||
|  |     return storedCard as IRightPanelCardStored; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function convertStoreToCard(panelStateStore: IRightPanelCardStored, room: Room): IRightPanelCard { | ||||||
|  |     const panelStateThisRoom = { ...panelStateStore?.state } as any; | ||||||
|  |     if (!!panelStateThisRoom.threadHeadEventId) { | ||||||
|  |         panelStateThisRoom.threadHeadEvent = room.findEventById(panelStateThisRoom.threadHeadEventId); | ||||||
|  |     } | ||||||
|  |     if (!!panelStateThisRoom.memberInfoEventId) { | ||||||
|  |         panelStateThisRoom.memberInfoEvent = room.findEventById(panelStateThisRoom.memberInfoEventId); | ||||||
|  |     } | ||||||
|  |     if (!!panelStateThisRoom.initialEventId) { | ||||||
|  |         panelStateThisRoom.initialEvent = room.findEventById(panelStateThisRoom.initialEventId); | ||||||
|  |     } | ||||||
|  |     if (!!panelStateThisRoom.memberId) { | ||||||
|  |         panelStateThisRoom.member = room.getMember(panelStateThisRoom.memberId); | ||||||
|  |     } | ||||||
|  |     delete panelStateThisRoom.threadHeadEventId; | ||||||
|  |     delete panelStateThisRoom.initialEventId; | ||||||
|  |     delete panelStateThisRoom.memberInfoEventId; | ||||||
|  |     delete panelStateThisRoom.memberId; | ||||||
|  |  | ||||||
|  |     return { state: panelStateThisRoom as IRightPanelCardState, phase: panelStateStore.phase } as IRightPanelCard; | ||||||
|  | } | ||||||
| @@ -16,17 +16,18 @@ limitations under the License. | |||||||
|  |  | ||||||
| import { User } from "matrix-js-sdk/src/models/user"; | import { User } from "matrix-js-sdk/src/models/user"; | ||||||
| import { verificationMethods as VerificationMethods } from 'matrix-js-sdk/src/crypto'; | import { verificationMethods as VerificationMethods } from 'matrix-js-sdk/src/crypto'; | ||||||
|  | import { RoomMember } from "matrix-js-sdk/src/matrix"; | ||||||
|  |  | ||||||
| import { MatrixClientPeg } from './MatrixClientPeg'; | import { MatrixClientPeg } from './MatrixClientPeg'; | ||||||
| import dis from "./dispatcher/dispatcher"; | import dis from "./dispatcher/dispatcher"; | ||||||
| import Modal from './Modal'; | import Modal from './Modal'; | ||||||
| import { RightPanelPhases } from "./stores/RightPanelStorePhases"; | import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases"; | ||||||
| import { findDMForUser } from './createRoom'; | import { findDMForUser } from './createRoom'; | ||||||
| import { accessSecretStorage } from './SecurityManager'; | import { accessSecretStorage } from './SecurityManager'; | ||||||
| import { Action } from './dispatcher/actions'; |  | ||||||
| import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog"; | import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog"; | ||||||
| import { IDevice } from "./components/views/right_panel/UserInfo"; | import { GroupMember, IDevice } from "./components/views/right_panel/UserInfo"; | ||||||
| import ManualDeviceKeyVerificationDialog from "./components/views/dialogs/ManualDeviceKeyVerificationDialog"; | import ManualDeviceKeyVerificationDialog from "./components/views/dialogs/ManualDeviceKeyVerificationDialog"; | ||||||
|  | import RightPanelStore from "./stores/right-panel/RightPanelStore"; | ||||||
|  |  | ||||||
| async function enable4SIfNeeded() { | async function enable4SIfNeeded() { | ||||||
|     const cli = MatrixClientPeg.get(); |     const cli = MatrixClientPeg.get(); | ||||||
| @@ -65,10 +66,9 @@ export async function verifyDevice(user: User, device: IDevice) { | |||||||
|                     device.deviceId, |                     device.deviceId, | ||||||
|                     VerificationMethods.SAS, |                     VerificationMethods.SAS, | ||||||
|                 ); |                 ); | ||||||
|                 dis.dispatch({ |                 RightPanelStore.instance.setCard({ | ||||||
|                     action: Action.SetRightPanelPhase, |  | ||||||
|                     phase: RightPanelPhases.EncryptionPanel, |                     phase: RightPanelPhases.EncryptionPanel, | ||||||
|                     refireParams: { member: user, verificationRequestPromise }, |                     state: { member: user, verificationRequestPromise }, | ||||||
|                 }); |                 }); | ||||||
|             } else if (action === "legacy") { |             } else if (action === "legacy") { | ||||||
|                 Modal.createTrackedDialog("Legacy verify session", "legacy verify session", |                 Modal.createTrackedDialog("Legacy verify session", "legacy verify session", | ||||||
| @@ -96,10 +96,9 @@ export async function legacyVerifyUser(user: User) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     const verificationRequestPromise = cli.requestVerification(user.userId); |     const verificationRequestPromise = cli.requestVerification(user.userId); | ||||||
|     dis.dispatch({ |     RightPanelStore.instance.setCard({ | ||||||
|         action: Action.SetRightPanelPhase, |  | ||||||
|         phase: RightPanelPhases.EncryptionPanel, |         phase: RightPanelPhases.EncryptionPanel, | ||||||
|         refireParams: { member: user, verificationRequestPromise }, |         state: { member: user, verificationRequestPromise }, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -113,17 +112,13 @@ export async function verifyUser(user: User) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     const existingRequest = pendingVerificationRequestForUser(user); |     const existingRequest = pendingVerificationRequestForUser(user); | ||||||
|     dis.dispatch({ |     RightPanelStore.instance.setCard({ | ||||||
|         action: Action.SetRightPanelPhase, |  | ||||||
|         phase: RightPanelPhases.EncryptionPanel, |         phase: RightPanelPhases.EncryptionPanel, | ||||||
|         refireParams: { |         state: { member: user, verificationRequest: existingRequest }, | ||||||
|             member: user, |  | ||||||
|             verificationRequest: existingRequest, |  | ||||||
|         }, |  | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function pendingVerificationRequestForUser(user: User) { | export function pendingVerificationRequestForUser(user: User | RoomMember | GroupMember ) { | ||||||
|     const cli = MatrixClientPeg.get(); |     const cli = MatrixClientPeg.get(); | ||||||
|     const dmRoom = findDMForUser(cli, user.userId); |     const dmRoom = findDMForUser(cli, user.userId); | ||||||
|     if (dmRoom) { |     if (dmRoom) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user