Right panel store refactor (#7313)

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

View file

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