Right panel store refactor (#7313)

Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
pull/21833/head
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 { Notifier } from "../Notifier";
import type { Renderer } from "react-dom";
import RightPanelStore from "../stores/RightPanelStore";
import RightPanelStore from "../stores/right-panel/RightPanelStore";
import WidgetStore from "../stores/WidgetStore";
import CallHandler from "../CallHandler";
import { Analytics } from "../Analytics";

View File

@ -27,12 +27,12 @@ import { isValid3pidInvite } from "./RoomInvite";
import SettingsStore from "./settings/SettingsStore";
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 { RightPanelPhases } from './stores/RightPanelStorePhases';
import { RightPanelPhases } from './stores/right-panel/RightPanelStorePhases';
import { Action } from './dispatcher/actions';
import defaultDispatcher from './dispatcher/dispatcher';
import { SetRightPanelPhasePayload } from './dispatcher/payloads/SetRightPanelPhasePayload';
import { MatrixClientPeg } from "./MatrixClientPeg";
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
// 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 => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.PinnedMessages,
allowClose: false,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
};
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 EventIndexPeg from "../../indexing/EventIndexPeg";
import { _t } from '../../languageHandler';
import BaseCard from "../views/right_panel/BaseCard";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
import BaseCard from "../views/right_panel/BaseCard";
import { replaceableComponent } from "../../utils/replaceableComponent";
import ResizeNotifier from '../../utils/ResizeNotifier';
import TimelinePanel from "./TimelinePanel";

View File

@ -38,13 +38,14 @@ import GroupStore from '../../stores/GroupStore';
import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
import RightPanelStore from "../../stores/RightPanelStore";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { mediaFromMxc } from "../../customisations/Media";
import { replaceableComponent } from "../../utils/replaceableComponent";
import { createSpaceFromCommunity } from "../../utils/space";
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(
`<h1>HTML for your community's page</h1>
@ -427,7 +428,7 @@ export default class GroupView extends React.Component {
membershipBusy: false,
publicityBusy: false,
inviterProfile: null,
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
showRightPanel: RightPanelStore.instance.isOpenForGroup,
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._dispatcherRef = dis.register(this._onAction);
this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate);
RightPanelStore.instance.on(UPDATE_EVENT, this._onRightPanelStoreUpdate);
}
componentWillUnmount() {
@ -447,10 +448,7 @@ export default class GroupView extends React.Component {
this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
dis.unregister(this._dispatcherRef);
// Remove RightPanelStore listener
if (this._rightPanelStoreToken) {
this._rightPanelStoreToken.remove();
}
RightPanelStore.instance.off(UPDATE_EVENT, this._onRightPanelStoreUpdate);
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
@ -468,7 +466,7 @@ export default class GroupView extends React.Component {
_onRightPanelStoreUpdate = () => {
this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
showRightPanel: RightPanelStore.instance.isOpenForGroup,
});
};
@ -824,10 +822,7 @@ export default class GroupView extends React.Component {
};
_onAdminsLinkClick = () => {
dis.dispatch({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.GroupMemberList,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.GroupMemberList });
};
_getGroupSection() {

View File

@ -44,7 +44,6 @@ import CallContainer from '../views/voip/CallContainer';
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import Modal from "../../Modal";
import { ICollapseConfig } from "../../resizer/distributors/collapse";
@ -68,6 +67,7 @@ import GroupFilterPanel from './GroupFilterPanel';
import CustomRoomTagPanel from './CustomRoomTagPanel';
import { mediaFromMxc } from "../../customisations/Media";
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)
// 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;
case NavigationAction.ToggleRoomSidePanel:
if (this.props.page_type === "room_view" || this.props.page_type === "group_view") {
dis.dispatch<ToggleRightPanelPayload>({
action: Action.ToggleRightPanel,
type: this.props.page_type === "room_view" ? "room" : "group",
});
RightPanelStore.instance.togglePanel();
handled = true;
}
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 { RoomMember } from "matrix-js-sdk/src/models/room-member";
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 dis from '../../dispatcher/dispatcher';
import GroupStore from '../../stores/GroupStore';
import {
RIGHT_PANEL_PHASES_NO_ARGS,
RIGHT_PANEL_SPACE_PHASES,
RightPanelPhases,
} from "../../stores/RightPanelStorePhases";
import RightPanelStore from "../../stores/RightPanelStore";
import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { Action } from "../../dispatcher/actions";
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
@ -50,16 +45,17 @@ import ThreadPanel from "./ThreadPanel";
import NotificationPanel from "./NotificationPanel";
import ResizeNotifier from "../../utils/ResizeNotifier";
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import SpaceStore from "../../stores/spaces/SpaceStore";
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import { E2EStatus } from '../../utils/ShieldUtils';
import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads';
import TimelineCard from '../views/right_panel/TimelineCard';
import { UPDATE_EVENT } from '../../stores/AsyncStore';
import { IRightPanelCard, IRightPanelCardState } from '../../stores/right-panel/RightPanelStoreIPanelState';
interface IProps {
room?: Room; // if showing panels for a given room, 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;
permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
@ -68,17 +64,8 @@ interface IProps {
interface IState {
phase: RightPanelPhases;
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;
cardState?: IRightPanelCardState;
}
@replaceableComponent("structures.RightPanel")
@ -89,11 +76,11 @@ export default class RightPanel extends React.Component<IProps, IState> {
constructor(props, context) {
super(props, context);
this.state = {
...RightPanelStore.getSharedInstance().roomPanelPhaseParams,
phase: this.getPhaseFromProps(),
cardState: RightPanelStore.instance.currentCard?.state,
phase: RightPanelStore.instance.currentCard?.phase,
isUserPrivilegedInGroup: null,
member: this.getUserForPanel(),
searchQuery: "",
};
}
@ -102,56 +89,11 @@ export default class RightPanel extends React.Component<IProps, IState> {
this.forceUpdate();
}, 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 {
this.dispatcherRef = dis.register(this.onAction);
const cli = this.context;
cli.on("RoomState.members", this.onRoomStateMember);
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
this.initGroupStore(this.props.groupId);
}
@ -160,6 +102,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
if (this.context) {
this.context.removeListener("RoomState.members", this.onRoomStateMember);
}
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
this.unregisterGroupStore();
}
@ -193,42 +136,36 @@ export default class RightPanel extends React.Component<IProps, IState> {
// redraw the badge on the membership list
if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) {
this.delayedUpdate();
} else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId &&
member.userId === this.state.member.userId) {
} else if (
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)
this.delayedUpdate();
}
};
private onRightPanelStoreUpdate = () => {
const currentPanel = RightPanelStore.instance.currentCard;
this.setState({
cardState: currentPanel.state,
phase: currentPanel.phase,
});
};
private onAction = (payload: ActionPayload) => {
const isChangingRoom = payload.action === Action.ViewRoom && payload.room_id !== this.props.room.roomId;
const isViewingThread = this.state.phase === RightPanelPhases.ThreadView;
if (isChangingRoom && isViewingThread) {
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 = () => {
// XXX: There are three different ways of 'closing' this panel depending on what state
// things are in... this knows far more than it should do about the state of the rest
// of the app and is generally a bit silly.
if (this.props.member) {
if (this.props.overwriteCard?.state?.member) {
// If we have a user prop then we're displaying a user from the 'user' page type
// in LoggedInView, so need to change the page type to close the panel (we switch
// to the home page which is not obviously the correct thing to do, but I'm not sure
@ -238,16 +175,12 @@ export default class RightPanel extends React.Component<IProps, IState> {
});
} else if (
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
this.state.verificationRequest.cancel();
this.state.cardState.verificationRequest.cancel();
} else {
// the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here
dis.dispatch({
action: Action.ToggleRightPanel,
type: this.props.groupId ? "group" : "room",
});
RightPanelStore.instance.togglePanel();
}
};
@ -256,13 +189,14 @@ export default class RightPanel extends React.Component<IProps, IState> {
};
public render(): JSX.Element {
let panel = <div />;
let card = <div />;
const roomId = this.props.room ? this.props.room.roomId : undefined;
switch (this.state.phase) {
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
const cardState = this.props.overwriteCard?.state ?? this.state.cardState;
switch (phase) {
case RightPanelPhases.RoomMemberList:
if (roomId) {
panel = <MemberList
card = <MemberList
roomId={roomId}
key={roomId}
onClose={this.onClose}
@ -272,9 +206,9 @@ export default class RightPanel extends React.Component<IProps, IState> {
}
break;
case RightPanelPhases.SpaceMemberList:
panel = <MemberList
roomId={this.state.space ? this.state.space.roomId : roomId}
key={this.state.space ? this.state.space.roomId : roomId}
card = <MemberList
roomId={cardState.spaceId ? cardState.spaceId : roomId}
key={cardState.spaceId ? cardState.spaceId : roomId}
onClose={this.onClose}
searchQuery={this.state.searchQuery}
onSearchQueryChanged={this.onSearchQueryChanged}
@ -283,61 +217,66 @@ export default class RightPanel extends React.Component<IProps, IState> {
case RightPanelPhases.GroupMemberList:
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;
case RightPanelPhases.GroupRoomList:
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
card = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
break;
case RightPanelPhases.RoomMemberInfo:
case RightPanelPhases.SpaceMemberInfo:
case RightPanelPhases.EncryptionPanel:
panel = <UserInfo
user={this.state.member}
room={this.context.getRoom(this.state.member.roomId) ?? this.props.room}
key={roomId || this.state.member.userId}
case RightPanelPhases.EncryptionPanel: {
const roomMember = cardState.member instanceof RoomMember
? cardState.member
: undefined;
card = <UserInfo
user={cardState.member}
room={this.context.getRoom(roomMember?.roomId) ?? this.props.room}
key={roomId || cardState.member.userId}
onClose={this.onClose}
phase={this.state.phase}
verificationRequest={this.state.verificationRequest}
verificationRequestPromise={this.state.verificationRequestPromise}
phase={phase}
verificationRequest={cardState.verificationRequest}
verificationRequestPromise={cardState.verificationRequestPromise}
/>;
break;
}
case RightPanelPhases.Room3pidMemberInfo:
case RightPanelPhases.Space3pidMemberInfo:
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
card = <ThirdPartyMemberInfo event={cardState.memberInfoEvent} key={roomId} />;
break;
case RightPanelPhases.GroupMemberInfo:
panel = <UserInfo
user={this.state.member}
card = <UserInfo
user={cardState.member}
groupId={this.props.groupId}
key={this.state.member.userId}
phase={this.state.phase}
onClose={this.onClose} />;
key={cardState.member.userId}
phase={phase}
onClose={this.onClose}
/>;
break;
case RightPanelPhases.GroupRoomInfo:
panel = <GroupRoomInfo
groupRoomId={this.state.groupRoomId}
card = <GroupRoomInfo
groupRoomId={cardState.groupRoomId}
groupId={this.props.groupId}
key={this.state.groupRoomId} />;
key={cardState.groupRoomId}
/>;
break;
case RightPanelPhases.NotificationPanel:
panel = <NotificationPanel onClose={this.onClose} />;
card = <NotificationPanel onClose={this.onClose} />;
break;
case RightPanelPhases.PinnedMessages:
if (SettingsStore.getValue("feature_pinning")) {
panel = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />;
card = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />;
}
break;
case RightPanelPhases.Timeline:
if (!SettingsStore.getValue("feature_maximised_widgets")) break;
panel = <TimelineCard
card = <TimelineCard
classNames="mx_ThreadPanel mx_TimelineCard"
room={this.props.room}
timelineSet={this.props.room.getUnfilteredTimelineSet()}
@ -348,23 +287,24 @@ export default class RightPanel extends React.Component<IProps, IState> {
/>;
break;
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;
case RightPanelPhases.ThreadView:
panel = <ThreadView
card = <ThreadView
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
onClose={this.onClose}
mxEvent={this.state.event}
initialEvent={this.state.initialEvent}
initialEventHighlighted={this.state.initialEventHighlighted}
mxEvent={cardState.threadHeadEvent}
initialEvent={cardState.initialEvent}
isInitialEventHighlighted={cardState.isInitialEventHighlighted}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus} />;
e2eStatus={this.props.e2eStatus}
/>;
break;
case RightPanelPhases.ThreadPanel:
panel = <ThreadPanel
card = <ThreadPanel
roomId={roomId}
resizeNotifier={this.props.resizeNotifier}
onClose={this.onClose}
@ -373,17 +313,21 @@ export default class RightPanel extends React.Component<IProps, IState> {
break;
case RightPanelPhases.RoomSummary:
panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
card = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
break;
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;
}
return (
<aside className="mx_RightPanel dark-panel" id="mx_RightPanel">
{ panel }
{ card }
</aside>
);
}

View File

@ -84,6 +84,7 @@ interface IState {
@replaceableComponent("structures.RoomStatusBar")
export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
private unmounted = false;
public static contextType = MatrixClientContext;
constructor(props: IProps, context: typeof MatrixClientContext) {
@ -110,6 +111,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
}
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...
const client = this.context;
if (client) {
@ -122,6 +124,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
if (state === "SYNCING" && prevState === "SYNCING") {
return;
}
if (this.unmounted) return;
this.setState({
syncState: state,
syncStateData: data,

View File

@ -53,7 +53,7 @@ import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout";
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 RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import MatrixClientContext, { withMatrixClientHOC, MatrixClientProps } from "../../contexts/MatrixClientContext";
@ -98,8 +98,7 @@ import { dispatchShowThreadEvent } from '../../dispatcher/dispatch-actions/threa
import { fetchInitialEvent } from "../../utils/EventUtils";
import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import AppsDrawer from '../views/rooms/AppsDrawer';
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
import { RightPanelPhases } from '../../stores/RightPanelStorePhases';
import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases';
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -214,7 +213,6 @@ export interface IRoomState {
export class RoomView extends React.Component<IRoomProps, IRoomState> {
private readonly dispatcherRef: string;
private readonly roomStoreToken: EventSubscription;
private readonly rightPanelStoreToken: EventSubscription;
private settingWatchers: string[];
private unmounted = false;
@ -246,7 +244,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
canPeek: false,
showApps: false,
isPeeking: false,
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
showRightPanel: RightPanelStore.instance.isOpenForRoom,
joining: false,
atEndOfLiveTimeline: true,
atEndOfLiveTimelineInit: false,
@ -289,7 +287,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.context.on("Event.decrypted", this.onEventDecrypted);
// Start listening for RoomViewStore updates
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);
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)) {
// Show chat in right panel when a widget is maximised
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.Timeline,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline });
}
this.checkWidgets(this.state.room);
this.checkRightPanel(this.state.room);
};
private checkWidgets = (room) => {
@ -361,22 +356,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
: 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 = () => {
this.setState({
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
@ -754,11 +733,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.roomStoreToken) {
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);
WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
@ -793,7 +769,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private onRightPanelStoreUpdate = () => {
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.updatePermissions(room);
this.checkWidgets(room);
this.checkRightPanel(room);
this.setState({
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 { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventSubscription } from "fbemitter";
import { logger } from "matrix-js-sdk/src/logger";
import MatrixClientContext from "../../contexts/MatrixClientContext";
@ -43,9 +42,8 @@ import MainSplit from './MainSplit';
import ErrorBoundary from "../views/elements/ErrorBoundary";
import { ActionPayload } from "../../dispatcher/payloads";
import RightPanel from "./RightPanel";
import RightPanelStore from "../../stores/RightPanelStore";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
import { useStateArray } from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare";
import {
@ -85,6 +83,7 @@ import { useDispatcher } from "../../hooks/useDispatcher";
import { useRoomState } from "../../hooks/useRoomState";
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
import { UIComponent } from "../../settings/UIFeature";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
interface IProps {
space: Room;
@ -164,10 +163,9 @@ const SpaceInfo = ({ space }: { space: Room }) => {
kind="link"
className="mx_SpaceRoomView_info_memberCount"
onClick={() => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.RoomMemberList,
refireParams: { space },
state: { spaceId: space.roomId },
});
}}
>
@ -473,11 +471,7 @@ const SpaceLanding = ({ space }: { space: Room }) => {
}
const onMembersClick = () => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.RoomMemberList,
refireParams: { space },
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList, state: { spaceId: space.roomId } });
};
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 dispatcherRef: string;
private readonly rightPanelStoreToken: EventSubscription;
constructor(props, context) {
super(props, context);
@ -813,18 +806,18 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
this.state = {
phase,
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
showRightPanel: RightPanelStore.instance.isOpenForRoom,
myMembership: this.props.space.getMyMembership(),
};
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);
}
componentWillUnmount() {
defaultDispatcher.unregister(this.dispatcherRef);
this.rightPanelStoreToken.remove();
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
this.context.off("Room.myMembership", this.onMyMembership);
}
@ -836,7 +829,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
private onRightPanelStoreUpdate = () => {
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.member) {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.SpaceMemberInfo,
refireParams: {
space: this.props.space,
member: payload.member,
},
state: { spaceId: this.props.space.roomId, member: payload.member },
});
} else if (payload.action === "view_3pid_invite" && payload.event) {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.Space3pidMemberInfo,
refireParams: {
space: this.props.space,
event: payload.event,
},
state: { spaceId: this.props.space.roomId, member: payload.member },
});
} else {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
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 BaseCard from "../views/right_panel/BaseCard";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
import { replaceableComponent } from "../../utils/replaceableComponent";
import ResizeNotifier from '../../utils/ResizeNotifier';
import { TileShape } from '../views/rooms/EventTile';
@ -30,7 +30,6 @@ import { Layout } from '../../settings/enums/Layout';
import TimelinePanel from './TimelinePanel';
import dis from "../../dispatcher/dispatcher";
import { ActionPayload } from '../../dispatcher/payloads';
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
import { Action } from '../../dispatcher/actions';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils';
@ -40,7 +39,7 @@ import ContentMessages from '../../ContentMessages';
import UploadBar from './UploadBar';
import { _t } from '../../languageHandler';
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 { WidgetLayoutStore } from '../../stores/widgets/WidgetLayoutStore';
@ -52,7 +51,7 @@ interface IProps {
permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
initialEvent?: MatrixEvent;
initialEventHighlighted?: boolean;
isInitialEventHighlighted?: boolean;
}
interface IState {
thread?: Thread;
@ -94,10 +93,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
}
if (prevProps.room !== this.props.room) {
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.RoomSummary,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary });
}
}
@ -168,7 +164,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
private onScroll = (): void => {
if (this.props.initialEvent && this.props.initialEventHighlighted) {
if (this.props.initialEvent && this.props.isInitialEventHighlighted) {
dis.dispatch({
action: Action.ViewRoom,
room_id: this.props.room.roomId,
@ -189,7 +185,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
public render(): JSX.Element {
const highlightedEventId = this.props.initialEventHighlighted
const highlightedEventId = this.props.isInitialEventHighlighted
? this.props.initialEvent?.getId()
: null;
@ -198,7 +194,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
event_id: this.state.thread?.id,
};
let previousPhase = RightPanelStore.getSharedInstance().previousPhase;
let previousPhase = RightPanelStore.instance.previousCard.phase;
if (!SettingsStore.getValue("feature_maximised_widgets")) {
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 => {
if (this.unmounted) return;
this.setState({ clientSyncState });
};

View File

@ -29,6 +29,7 @@ import MainSplit from "./MainSplit";
import RightPanel from "./RightPanel";
import Spinner from "../views/elements/Spinner";
import ResizeNotifier from "../../utils/ResizeNotifier";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
interface IProps {
userId?: string;
@ -88,7 +89,10 @@ export default class UserView extends React.Component<IProps, IState> {
if (this.state.loading) {
return <Spinner />;
} 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}>
<HomePage />
</MainSplit>);

View File

@ -38,12 +38,10 @@ import Modal from "../../../Modal";
import ExportDialog from "../dialogs/ExportDialog";
import { onRoomFilesClick, onRoomMembersClick } from "../right_panel/RoomSummaryCard";
import RoomViewStore from "../../../stores/RoomViewStore";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import DMRoomMap from "../../../utils/DMRoomMap";
interface IProps extends IContextMenuProps {
@ -272,11 +270,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
ev.stopPropagation();
ensureViewingRoom();
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.RoomSummary,
allowClose: false,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary }, false);
onFinished();
}}
label={_t("Widgets")}

View File

@ -26,19 +26,13 @@ import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import { isValid3pidInvite } from "../../../RoomInvite";
import EventListSummary from "./EventListSummary";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import defaultDispatcher from '../../../dispatcher/dispatcher';
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
import { Action } from '../../../dispatcher/actions';
import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { jsxJoin } from '../../../utils/ReactUtils';
import { Layout } from '../../../settings/enums/Layout';
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
const onPinnedMessagesClick = (): void => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.PinnedMessages,
allowClose: false,
});
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
};
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 * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import GroupStore from '../../../stores/GroupStore';
import { showGroupInviteDialog } from '../../../GroupAddressPicker';
import AccessibleButton from '../elements/AccessibleButton';
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { Action } from "../../../dispatcher/actions";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
const INITIAL_LOAD_NUM_MEMBERS = 30;
@ -170,10 +169,9 @@ export default class GroupMemberList extends React.Component {
onInviteToGroupButtonClick = () => {
showGroupInviteDialog(this.props.groupId).then(() => {
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
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 { getNameForEventRoom, userLabelForEventRoom }
from '../../../utils/KeyVerificationStateObserver';
import dis from "../../../dispatcher/dispatcher";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import EventTileBubble from "./EventTileBubble";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import AccessibleButton from '../elements/AccessibleButton';
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
interface IProps {
mxEvent: MatrixEvent;
@ -52,10 +51,9 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
private openRequest = () => {
const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
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 { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
interface IProps {
header?: ReactNode;
@ -34,7 +32,7 @@ interface IProps {
previousPhaseLabel?: string;
closeLabel?: string;
onClose?(): void;
refireParams?;
cardState?;
}
interface IGroupProps {
@ -59,16 +57,15 @@ const BaseCard: React.FC<IProps> = ({
previousPhase,
previousPhaseLabel,
children,
refireParams,
cardState,
}) => {
let backButton;
if (previousPhase) {
const onBackClick = () => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: previousPhase,
refireParams: refireParams,
});
// TODO RightPanelStore (will be addressed in a follow up PR): this should ideally be:
// RightPanelStore.instance.popRightPanel();
RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState });
};
const label = previousPhaseLabel ?? _t("Back");
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 * as sdk from "../../../index";
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
// cancellation codes which constitute a key mismatch
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
@ -117,10 +116,9 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
setRequest(verificationRequest_);
setPhase(verificationRequest_.phase);
// Notify the RightPanelStore about this
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.EncryptionPanel,
refireParams: { member, verificationRequest: verificationRequest_ },
state: { member, verificationRequest: verificationRequest_ },
});
}, [member]);

View File

@ -23,7 +23,7 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import HeaderButton from './HeaderButton';
import HeaderButtons, { HeaderKind } from './HeaderButtons';
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { Action } from "../../../dispatcher/actions";
import { ActionPayload } from "../../../dispatcher/payloads";
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";

View File

@ -21,15 +21,11 @@ limitations under the License.
import React from 'react';
import dis from '../../../dispatcher/dispatcher';
import RightPanelStore from "../../../stores/RightPanelStore";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { Action } from '../../../dispatcher/actions';
import {
SetRightPanelPhasePayload,
SetRightPanelPhaseRefireParams,
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import type { EventSubscription } from "fbemitter";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
export enum HeaderKind {
@ -47,38 +43,35 @@ interface IProps {}
@replaceableComponent("views.right_panel.HeaderButtons")
export default abstract class HeaderButtons<P = {}> extends React.Component<IProps & P, IState> {
private storeToken: EventSubscription;
private unmounted = false;
private dispatcherRef: string;
constructor(props: IProps & P, kind: HeaderKind) {
super(props);
const rps = RightPanelStore.getSharedInstance();
const rps = RightPanelStore.instance;
this.state = {
headerKind: kind,
phase: rps.currentCard.phase,
threadNotificationColor: NotificationColor.None,
phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase,
};
}
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
}
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);
}
protected abstract onAction(payload);
public setPhase(phase: RightPanelPhases, extras?: Partial<SetRightPanelPhaseRefireParams>) {
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: phase,
refireParams: extras,
});
public setPhase(phase: RightPanelPhases, cardState?: Partial<IRightPanelCardState>) {
RightPanelStore.instance.setCard({ phase, state: cardState });
}
public isPhase(phases: string | string[]) {
@ -89,14 +82,12 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
}
}
private onRightPanelUpdate() {
const rps = RightPanelStore.getSharedInstance();
if (this.state.headerKind === HeaderKind.Room) {
this.setState({ phase: rps.visibleRoomPanelPhase });
} else if (this.state.headerKind === HeaderKind.Group) {
this.setState({ phase: rps.visibleGroupPanelPhase });
}
}
private onRightPanelStoreUpdate = () => {
if (this.unmounted) return;
let phase = RightPanelStore.instance.currentCard.phase;
if (!RightPanelStore.instance.isOpenForGroup) {phase = null;}
this.setState({ phase });
};
// XXX: Make renderButtons a prop
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 HeaderButton from './HeaderButton';
import HeaderButtons, { HeaderKind } from './HeaderButtons';
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { Action } from "../../../dispatcher/actions";
import { ActionPayload } from "../../../dispatcher/payloads";
import RightPanelStore from "../../../stores/RightPanelStore";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { useSettingValue } from "../../../hooks/useSettings";
import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads";
import SettingsStore from "../../../settings/SettingsStore";
import dis from "../../../dispatcher/dispatcher";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState";
@ -161,7 +160,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
}
} else if (payload.action === "view_3pid_invite") {
if (payload.event) {
this.setPhase(RightPanelPhases.Room3pidMemberInfo, { event: payload.event });
this.setPhase(RightPanelPhases.Room3pidMemberInfo, { memberInfoEvent: payload.event });
} else {
this.setPhase(RightPanelPhases.RoomMemberList);
}
@ -170,12 +169,12 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
private onRoomSummaryClicked = () => {
// use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close
const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase;
if (ROOM_INFO_PHASES.includes(lastPhase)) {
if (this.state.phase === lastPhase) {
this.setPhase(lastPhase);
const currentPhase = RightPanelStore.instance.currentCard.phase;
if (ROOM_INFO_PHASES.includes(currentPhase)) {
if (this.state.phase === currentPhase) {
this.setPhase(currentPhase);
} else {
this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams);
this.setPhase(currentPhase, RightPanelStore.instance.currentCard.state);
}
} else {
// This toggles for us, if needed
@ -198,10 +197,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
private onThreadsPanelClicked = () => {
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
dis.dispatch({
action: Action.ToggleRightPanel,
type: "room",
});
RightPanelStore.instance.togglePanel();
} else {
dispatchShowThreadsPanelEvent();
}
@ -227,6 +223,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
rightPanelPhaseButtons.set(RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_thread")
? <HeaderButton
key={RightPanelPhases.ThreadPanel}
name="threadsButton"
title={_t("Threads")}
onClick={this.onThreadsPanelClicked}

View File

@ -25,9 +25,7 @@ import { _t } from '../../../languageHandler';
import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleButton from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import Modal from "../../../Modal";
import ShareDialog from '../dialogs/ShareDialog';
import { useEventEmitter } from "../../../hooks/useEventEmitter";
@ -48,6 +46,7 @@ import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widget
import RoomName from "../elements/RoomName";
import UIStore from "../../../stores/UIStore";
import ExportDialog from "../dialogs/ExportDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
interface IProps {
room: Room;
@ -103,12 +102,10 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
}, [room.roomId]);
const onOpenWidgetClick = () => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
// TODO RightPanelStore (will be addressed in a follow up PR): should push the widget
RightPanelStore.instance.setCard({
phase: RightPanelPhases.Widget,
refireParams: {
widgetId: app.id,
},
state: { widgetId: app.id },
});
};
@ -237,19 +234,13 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
};
export const onRoomMembersClick = (allowClose = true) => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.RoomMemberList,
allowClose,
});
// TODO RightPanelStore (will be addressed in a follow up PR): should push the phase
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList }, allowClose);
};
export const onRoomFilesClick = (allowClose = true) => {
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.FilePanel,
allowClose,
});
// TODO RightPanelStore (will be addressed in a follow up PR): should push the phase
RightPanelStore.instance.setCard({ phase: RightPanelPhases.FilePanel }, allowClose);
};
const onRoomSettingsClick = () => {

View File

@ -55,7 +55,7 @@ interface IState {
editState?: EditorStateTransfer;
replyToEvent?: MatrixEvent;
initialEventId?: string;
initialEventHighlighted?: boolean;
isInitialEventHighlighted?: boolean;
// settings:
showReadReceipts?: boolean;
@ -103,7 +103,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
// roomLoadError: RoomViewStore.getRoomLoadError(),
initialEventId: RoomViewStore.getInitialEventId(),
initialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
replyToEvent: RoomViewStore.getQuotingEvent(),
};
@ -127,7 +127,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
};
private onScroll = (): void => {
if (this.state.initialEventId && this.state.initialEventHighlighted) {
if (this.state.initialEventId && this.state.isInitialEventHighlighted) {
dis.dispatch({
action: Action.ViewRoom,
room_id: this.props.room.roomId,
@ -145,7 +145,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
};
public render(): JSX.Element {
const highlightedEventId = this.state.initialEventHighlighted
const highlightedEventId = this.state.isInitialEventHighlighted
? this.state.initialEventId
: null;

View File

@ -44,7 +44,7 @@ import E2EIcon from "../rooms/E2EIcon";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { textualPowerLevel } from '../../../Roles';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import EncryptionPanel from "./EncryptionPanel";
import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification';
@ -63,7 +63,6 @@ import ErrorDialog from "../dialogs/ErrorDialog";
import QuestionDialog from "../dialogs/QuestionDialog";
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
import InfoDialog from "../dialogs/InfoDialog";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
import { mediaFromMxc } from "../../../customisations/Media";
@ -75,6 +74,8 @@ import { bulkSpaceBehaviour } from "../../../utils/space";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../settings/UIFeature";
import { TimelineRenderingType } from "../../../contexts/RoomContext";
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState';
import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage";
export interface IDevice {
@ -1649,25 +1650,22 @@ const UserInfo: React.FC<IProps> = ({
const classes = ["mx_UserInfo"];
let refireParams;
let cardState: IRightPanelCardState;
let previousPhase: RightPanelPhases;
// We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
if (room && phase === RightPanelPhases.EncryptionPanel) {
previousPhase = RightPanelPhases.RoomMemberInfo;
refireParams = { member };
cardState = { member };
} else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) {
previousPhase = previousPhase = RightPanelPhases.SpaceMemberList;
refireParams = { space: room };
previousPhase = RightPanelPhases.SpaceMemberList;
cardState = { spaceId: room.roomId };
} else if (room) {
previousPhase = RightPanelPhases.RoomMemberList;
}
const onEncryptionPanelClose = () => {
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: previousPhase,
refireParams: refireParams,
});
// TODO RightPanelStore (will be addressed in a follow up PR): here we want to pop the panel
RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState });
};
let content;
@ -1723,7 +1721,7 @@ const UserInfo: React.FC<IProps> = ({
onClose={onClose}
closeLabel={closeLabel}
previousPhase={previousPhase}
refireParams={refireParams}
cardState={cardState}
>
{ content }
</BaseCard>;

View File

@ -23,14 +23,12 @@ import WidgetUtils from "../../../utils/WidgetUtils";
import AppTile from "../elements/AppTile";
import { _t } from "../../../languageHandler";
import { useWidgets } from "./RoomSummaryCard";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import UIStore from "../../../stores/UIStore";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
interface IProps {
room: Room;
@ -50,10 +48,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
useEffect(() => {
if (!app || isPinned) {
// stop showing this card
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.RoomSummary,
});
//TODO RightPanelStore (will be addressed in a follow up PR): here we want to just pop the widget card.
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary });
}
}, [app, isPinned]);

View File

@ -56,6 +56,7 @@ interface IState {
@replaceableComponent("views.rooms.AppsDrawer")
export default class AppsDrawer extends React.Component<IProps, IState> {
private unmounted = false;
private resizeContainer: HTMLDivElement;
private resizer: Resizer;
private dispatcherRef: string;
@ -85,6 +86,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
this.unmounted = true;
ScalarMessaging.stopListening();
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
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 updateApps = (): void => {
if (this.unmounted) return;
this.setState({
apps: this.getApps(),
});

View File

@ -33,7 +33,7 @@ import { isValid3pidInvite } from "../../../RoomInvite";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import BaseCard from "../right_panel/BaseCard";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
import { replaceableComponent } from "../../../utils/replaceableComponent";

View File

@ -38,7 +38,7 @@ import { ContextMenuTooltipButton } from '../../structures/ContextMenu';
import RoomContextMenu from "../context_menus/RoomContextMenu";
import { contextMenuBelow } from './RoomTile';
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
export interface ISearchInfo {

View File

@ -36,7 +36,7 @@ import { ButtonEvent } from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import EditCommunityPrototypeDialog from "../dialogs/EditCommunityPrototypeDialog";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import ErrorDialog from "../dialogs/ErrorDialog";
import { showCommunityInviteDialog } from "../../../RoomInvite";
import { useDispatcher } from "../../../hooks/useDispatcher";
@ -50,6 +50,7 @@ import {
UPDATE_HOME_BEHAVIOUR,
UPDATE_SELECTED_SPACE,
} from "../../../stores/spaces";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import TooltipTarget from "../elements/TooltipTarget";
const contextMenuBelow = (elementRect: DOMRect) => {
@ -99,7 +100,7 @@ const PrototypeCommunityContextMenu = (props: ComponentProps<typeof SpaceContext
action: 'view_room',
room_id: chat.roomId,
}, true);
dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList });
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList }, undefined, chat.roomId);
} else {
// "This should never happen" clauses go here for the prototype.
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 { MatrixClientPeg } from '../../../MatrixClientPeg';
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
import dis from "../../../dispatcher/dispatcher";
import ToastStore from "../../../stores/ToastStore";
@ -31,6 +30,7 @@ import GenericToast from "./GenericToast";
import { Action } from "../../../dispatcher/actions";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import VerificationRequestDialog from "../dialogs/VerificationRequestDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
interface IProps {
toastKey: string;
@ -115,14 +115,14 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
room_id: request.channel.roomId,
should_peek: false,
});
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.EncryptionPanel,
refireParams: {
verificationRequest: request,
member: cli.getUser(request.otherUserId),
RightPanelStore.instance.setCard(
{
phase: RightPanelPhases.EncryptionPanel,
state: { verificationRequest: request, member: cli.getUser(request.otherUserId) },
},
});
undefined,
request.channel.roomId,
);
} else {
Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, {
verificationRequest: request,

View File

@ -102,16 +102,6 @@ export enum Action {
*/
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.
*/

View File

@ -15,31 +15,26 @@ limitations under the License.
*/
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { Action } from "../actions";
import dis from '../dispatcher';
import { SetRightPanelPhasePayload } from "../payloads/SetRightPanelPhasePayload";
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
export const dispatchShowThreadEvent = (
rootEvent: MatrixEvent,
initialEvent?: MatrixEvent,
highlighted?: boolean,
) => {
dis.dispatch({
action: Action.SetRightPanelPhase,
// TODO RightPanelStore (will be addressed in a follow up PR): this should really be a push!
RightPanelStore.instance.setCard({
phase: RightPanelPhases.ThreadView,
refireParams: {
event: rootEvent,
state: {
threadHeadEvent: rootEvent,
initialEvent,
highlighted,
isInitialEventHighlighted: highlighted,
},
});
};
export const dispatchShowThreadsPanelEvent = () => {
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.ThreadPanel,
});
RightPanelStore.instance.setCard({ 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 { SettingLevel } from "./SettingLevel";
import SettingController from "./controllers/SettingController";
import { RightPanelPhases } from "../stores/RightPanelStorePhases";
import { isMac } from '../Keyboard';
import UIFeatureController from "./controllers/UIFeatureController";
import { UIFeature } from "./UIFeature";
@ -771,21 +770,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
displayName: _td("Show previews/thumbnails for images"),
default: true,
},
"showRightPanelInRoom": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: false,
"RightPanel.phasesGlobal": {
supportedLevels: [SettingLevel.DEVICE],
default: null,
},
"showRightPanelInGroup": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: false,
},
"lastRightPanelPhaseForRoom": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: RightPanelPhases.RoomSummary,
},
"lastRightPanelPhaseForGroup": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: RightPanelPhases.GroupMemberList,
"RightPanel.phases": {
supportedLevels: [SettingLevel.ROOM_DEVICE],
default: null,
},
"enableEventIndexing": {
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
}
// 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
if (settingName === "layout") {
const settings = this.getSettings() || {};
@ -106,20 +95,6 @@ export default class DeviceSettingsHandler extends SettingsHandler {
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
if (settingName === "layout") {
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 { verificationMethods as VerificationMethods } from 'matrix-js-sdk/src/crypto';
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from "./dispatcher/dispatcher";
import Modal from './Modal';
import { RightPanelPhases } from "./stores/RightPanelStorePhases";
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import { findDMForUser } from './createRoom';
import { accessSecretStorage } from './SecurityManager';
import { Action } from './dispatcher/actions';
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 RightPanelStore from "./stores/right-panel/RightPanelStore";
async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get();
@ -65,10 +66,9 @@ export async function verifyDevice(user: User, device: IDevice) {
device.deviceId,
VerificationMethods.SAS,
);
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.EncryptionPanel,
refireParams: { member: user, verificationRequestPromise },
state: { member: user, verificationRequestPromise },
});
} else if (action === "legacy") {
Modal.createTrackedDialog("Legacy verify session", "legacy verify session",
@ -96,10 +96,9 @@ export async function legacyVerifyUser(user: User) {
}
}
const verificationRequestPromise = cli.requestVerification(user.userId);
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.EncryptionPanel,
refireParams: { member: user, verificationRequestPromise },
state: { member: user, verificationRequestPromise },
});
}
@ -113,17 +112,13 @@ export async function verifyUser(user: User) {
return;
}
const existingRequest = pendingVerificationRequestForUser(user);
dis.dispatch({
action: Action.SetRightPanelPhase,
RightPanelStore.instance.setCard({
phase: RightPanelPhases.EncryptionPanel,
refireParams: {
member: user,
verificationRequest: existingRequest,
},
state: { member: user, verificationRequest: existingRequest },
});
}
export function pendingVerificationRequestForUser(user: User) {
export function pendingVerificationRequestForUser(user: User | RoomMember | GroupMember ) {
const cli = MatrixClientPeg.get();
const dmRoom = findDMForUser(cli, user.userId);
if (dmRoom) {