Right panel store refactor (#7313)

Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
Timo 2022-01-05 16:14:44 +01:00 committed by GitHub
parent 8e881336ab
commit 325e2ba99b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 765 additions and 811 deletions

View file

@ -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";

View file

@ -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 {

View file

@ -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";

View file

@ -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() {

View file

@ -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;

View file

@ -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>
); );
} }

View file

@ -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,

View file

@ -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(),

View file

@ -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 },
}); });
} }
}; };

View file

@ -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;
} }

View file

@ -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 });
}; };

View file

@ -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>);

View file

@ -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")}

View file

@ -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];

View file

@ -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 },
}); });
}); });
}; };

View file

@ -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 },
}); });
}; };

View file

@ -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} />;

View file

@ -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]);

View file

@ -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";

View file

@ -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;

View file

@ -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}

View file

@ -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 = () => {

View file

@ -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;

View file

@ -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>;

View file

@ -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]);

View file

@ -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(),
}); });

View file

@ -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";

View file

@ -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 {

View file

@ -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, {

View file

@ -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,

View file

@ -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.
*/ */

View file

@ -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,
});
}; };

View file

@ -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;

View file

@ -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;
}

View file

@ -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";
}

View file

@ -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,

View file

@ -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() || {};

View file

@ -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();

View 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;

View 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;
}

View file

@ -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) {