History based navigation with new right panel store (#7398)

Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
Timo 2022-01-05 17:25:41 +01:00 committed by GitHub
parent 6f89267a31
commit 4ab3470184
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 248 additions and 252 deletions

View file

@ -27,7 +27,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from '../../MatrixClientPeg'; import { MatrixClientPeg } from '../../MatrixClientPeg';
import EventIndexPeg from "../../indexing/EventIndexPeg"; import EventIndexPeg from "../../indexing/EventIndexPeg";
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice"; import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
import BaseCard from "../views/right_panel/BaseCard"; import BaseCard from "../views/right_panel/BaseCard";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
@ -221,7 +220,6 @@ class FilePanel extends React.Component<IProps, IState> {
return <BaseCard return <BaseCard
className="mx_FilePanel mx_RoomView_messageListWrapper" className="mx_FilePanel mx_RoomView_messageListWrapper"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
> >
<div className="mx_RoomView_empty"> <div className="mx_RoomView_empty">
{ _t("You must <a>register</a> to use this functionality", { _t("You must <a>register</a> to use this functionality",
@ -234,7 +232,6 @@ class FilePanel extends React.Component<IProps, IState> {
return <BaseCard return <BaseCard
className="mx_FilePanel mx_RoomView_messageListWrapper" className="mx_FilePanel mx_RoomView_messageListWrapper"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
> >
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div> <div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
</BaseCard>; </BaseCard>;
@ -258,7 +255,6 @@ class FilePanel extends React.Component<IProps, IState> {
<BaseCard <BaseCard
className="mx_FilePanel" className="mx_FilePanel"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer withoutScrollContainer
> >
<DesktopBuildsNotice isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} /> <DesktopBuildsNotice isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} />
@ -285,7 +281,6 @@ class FilePanel extends React.Component<IProps, IState> {
<BaseCard <BaseCard
className="mx_FilePanel" className="mx_FilePanel"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
> >
<Spinner /> <Spinner />
</BaseCard> </BaseCard>

View file

@ -27,12 +27,10 @@ import GroupStore from '../../stores/GroupStore';
import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases'; import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { Action } from "../../dispatcher/actions";
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
import WidgetCard from "../views/right_panel/WidgetCard"; import WidgetCard from "../views/right_panel/WidgetCard";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { ActionPayload } from "../../dispatcher/payloads";
import MemberList from "../views/rooms/MemberList"; import MemberList from "../views/rooms/MemberList";
import GroupMemberList from "../views/groups/GroupMemberList"; import GroupMemberList from "../views/groups/GroupMemberList";
import GroupRoomList from "../views/groups/GroupRoomList"; import GroupRoomList from "../views/groups/GroupRoomList";
@ -47,7 +45,6 @@ import ResizeNotifier from "../../utils/ResizeNotifier";
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import { E2EStatus } from '../../utils/ShieldUtils'; import { E2EStatus } from '../../utils/ShieldUtils';
import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads';
import TimelineCard from '../views/right_panel/TimelineCard'; import TimelineCard from '../views/right_panel/TimelineCard';
import { UPDATE_EVENT } from '../../stores/AsyncStore'; import { UPDATE_EVENT } from '../../stores/AsyncStore';
import { IRightPanelCard, IRightPanelCardState } from '../../stores/right-panel/RightPanelStoreIPanelState'; import { IRightPanelCard, IRightPanelCardState } from '../../stores/right-panel/RightPanelStoreIPanelState';
@ -72,8 +69,6 @@ interface IState {
export default class RightPanel extends React.Component<IProps, IState> { export default class RightPanel extends React.Component<IProps, IState> {
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
private dispatcherRef: string;
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
@ -90,7 +85,6 @@ export default class RightPanel extends React.Component<IProps, IState> {
}, 500, { leading: true, trailing: true }); }, 500, { leading: true, trailing: true });
public componentDidMount(): void { public componentDidMount(): void {
this.dispatcherRef = dis.register(this.onAction);
const cli = this.context; const cli = this.context;
cli.on("RoomState.members", this.onRoomStateMember); cli.on("RoomState.members", this.onRoomStateMember);
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
@ -98,7 +92,6 @@ export default class RightPanel extends React.Component<IProps, IState> {
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
dis.unregister(this.dispatcherRef);
if (this.context) { if (this.context) {
this.context.removeListener("RoomState.members", this.onRoomStateMember); this.context.removeListener("RoomState.members", this.onRoomStateMember);
} }
@ -153,14 +146,6 @@ export default class RightPanel extends React.Component<IProps, IState> {
}); });
}; };
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();
}
};
private onClose = () => { private onClose = () => {
// XXX: There are three different ways of 'closing' this panel depending on what state // XXX: There are three different ways of 'closing' this panel depending on what state
// things are in... this knows far more than it should do about the state of the rest // things are in... this knows far more than it should do about the state of the rest

View file

@ -94,7 +94,7 @@ import MessageComposer from '../views/rooms/MessageComposer';
import JumpToBottomButton from "../views/rooms/JumpToBottomButton"; import JumpToBottomButton from "../views/rooms/JumpToBottomButton";
import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar"; import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
import SpaceStore from "../../stores/spaces/SpaceStore"; import SpaceStore from "../../stores/spaces/SpaceStore";
import { dispatchShowThreadEvent } from '../../dispatcher/dispatch-actions/threads'; import { showThread } from '../../dispatcher/dispatch-actions/threads';
import { fetchInitialEvent } from "../../utils/EventUtils"; import { fetchInitialEvent } from "../../utils/EventUtils";
import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import AppsDrawer from '../views/rooms/AppsDrawer'; import AppsDrawer from '../views/rooms/AppsDrawer';
@ -338,6 +338,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) { if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) {
// Show chat in right panel when a widget is maximised // Show chat in right panel when a widget is maximised
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }); RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline });
} else if (
RightPanelStore.instance.isOpenForRoom &&
RightPanelStore.instance.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline))
) {
// hide chat in right panel when the widget is minimized
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary });
RightPanelStore.instance.togglePanel();
} }
this.checkWidgets(this.state.room); this.checkWidgets(this.state.room);
}; };
@ -424,21 +431,21 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const thread = initialEvent?.getThread(); const thread = initialEvent?.getThread();
if (thread && !initialEvent?.isThreadRoot) { if (thread && !initialEvent?.isThreadRoot) {
dispatchShowThreadEvent( showThread({
thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
RoomViewStore.isInitialEventHighlighted(), highlighted: RoomViewStore.isInitialEventHighlighted(),
); });
} else { } else {
newState.initialEventId = initialEventId; newState.initialEventId = initialEventId;
newState.isInitialEventHighlighted = RoomViewStore.isInitialEventHighlighted(); newState.isInitialEventHighlighted = RoomViewStore.isInitialEventHighlighted();
if (thread && initialEvent?.isThreadRoot) { if (thread && initialEvent?.isThreadRoot) {
dispatchShowThreadEvent( showThread({
thread.rootEvent, rootEvent: thread.rootEvent,
initialEvent, initialEvent,
RoomViewStore.isInitialEventHighlighted(), highlighted: RoomViewStore.isInitialEventHighlighted(),
); });
} }
} }
} }

View file

@ -40,8 +40,6 @@ import UploadBar from './UploadBar';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu'; import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu';
import RightPanelStore from '../../stores/right-panel/RightPanelStore'; import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import SettingsStore from '../../settings/SettingsStore';
import { WidgetLayoutStore } from '../../stores/widgets/WidgetLayoutStore';
interface IProps { interface IProps {
room: Room; room: Room;
@ -194,24 +192,6 @@ export default class ThreadView extends React.Component<IProps, IState> {
event_id: this.state.thread?.id, event_id: this.state.thread?.id,
}; };
let previousPhase = RightPanelStore.instance.previousCard.phase;
if (!SettingsStore.getValue("feature_maximised_widgets")) {
previousPhase = RightPanelPhases.ThreadPanel;
}
// change the previous phase to the threadPanel in case there is no maximised widget anymore
if (!WidgetLayoutStore.instance.hasMaximisedWidget(this.props.room)) {
previousPhase = RightPanelPhases.ThreadPanel;
}
// Make sure the previous Phase is always one of the two: Timeline or ThreadPanel
if (![RightPanelPhases.ThreadPanel, RightPanelPhases.Timeline].includes(previousPhase)) {
previousPhase = RightPanelPhases.ThreadPanel;
}
const previousPhaseLabels = {};
previousPhaseLabels[RightPanelPhases.ThreadPanel] = _t("All threads");
previousPhaseLabels[RightPanelPhases.Timeline] = _t("Chat");
return ( return (
<RoomContext.Provider value={{ <RoomContext.Provider value={{
...this.context, ...this.context,
@ -222,8 +202,6 @@ export default class ThreadView extends React.Component<IProps, IState> {
<BaseCard <BaseCard
className="mx_ThreadView mx_ThreadPanel" className="mx_ThreadView mx_ThreadPanel"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={previousPhase}
previousPhaseLabel={previousPhaseLabels[previousPhase]}
withoutScrollContainer={true} withoutScrollContainer={true}
header={this.renderThreadViewHeader()} header={this.renderThreadViewHeader()}
> >

View file

@ -25,6 +25,7 @@ import { Action } from "../../../dispatcher/actions";
import BaseAvatar from "./BaseAvatar"; import BaseAvatar from "./BaseAvatar";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import { CardContext } from '../right_panel/BaseCard';
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> { interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
member: RoomMember; member: RoomMember;
@ -36,6 +37,7 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
onClick?: React.MouseEventHandler; onClick?: React.MouseEventHandler;
// Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser` // Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser`
viewUserOnClick?: boolean; viewUserOnClick?: boolean;
pushUserOnClick?: boolean;
title?: string; title?: string;
style?: any; style?: any;
} }
@ -99,6 +101,7 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.props.member, member: this.props.member,
push: this.context.isCard,
}); });
}; };
} }
@ -109,7 +112,10 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
title={this.state.title} title={this.state.title}
idName={userId} idName={userId}
url={this.state.imageUrl} url={this.state.imageUrl}
onClick={onClick} /> onClick={onClick}
/>
); );
} }
} }
MemberAvatar.contextType = CardContext;

View file

@ -51,10 +51,11 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
private openRequest = () => { private openRequest = () => {
const { verificationRequest } = this.props.mxEvent; const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
RightPanelStore.instance.setCard({ RightPanelStore.instance.setCards([
phase: RightPanelPhases.EncryptionPanel, { phase: RightPanelPhases.RoomSummary },
state: { verificationRequest, member }, { phase: RightPanelPhases.RoomMemberInfo, state: { member } },
}); { phase: RightPanelPhases.EncryptionPanel, state: { verificationRequest, member } },
]);
}; };
private onRequestChanged = () => { private onRequestChanged = () => {

View file

@ -39,8 +39,9 @@ import DownloadActionButton from "./DownloadActionButton";
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ReplyChain from '../elements/ReplyChain'; import ReplyChain from '../elements/ReplyChain';
import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/threads'; import { showThread } from '../../../dispatcher/dispatch-actions/threads';
import ReactionPicker from "../emojipicker/ReactionPicker"; import ReactionPicker from "../emojipicker/ReactionPicker";
import { CardContext } from '../right_panel/BaseCard';
interface IOptionsButtonProps { interface IOptionsButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -219,8 +220,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
}); });
}; };
private onThreadClick = (): void => { private onThreadClick = (isCard: boolean): void => {
dispatchShowThreadEvent(this.props.mxEvent); showThread({ rootEvent: this.props.mxEvent, push: isCard });
dis.dispatch({ dis.dispatch({
action: Action.FocusSendMessageComposer, action: Action.FocusSendMessageComposer,
context: TimelineRenderingType.Thread, context: TimelineRenderingType.Thread,
@ -303,6 +304,17 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
key="cancel" key="cancel"
/>; />;
const threadTooltipButton = <CardContext.Consumer>
{ context =>
<RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Reply in thread")}
onClick={this.onThreadClick.bind(null, context.isCard)}
key="thread"
/>
}
</CardContext.Consumer>;
// We show a different toolbar for failed events, so detect that first. // We show a different toolbar for failed events, so detect that first.
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
@ -335,12 +347,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
key="reply" key="reply"
/> />
{ (this.showReplyInThreadAction) && ( { (this.showReplyInThreadAction) && (
<RovingAccessibleTooltipButton threadTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Reply in thread")}
onClick={this.onThreadClick}
key="thread"
/>
) } ) }
</>); </>);
} }
@ -368,12 +375,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
this.props.mxEvent.getThread() && this.props.mxEvent.getThread() &&
!isContentActionable(this.props.mxEvent) !isContentActionable(this.props.mxEvent)
) { ) {
toolbarOpts.unshift(<RovingAccessibleTooltipButton toolbarOpts.unshift(threadTooltipButton);
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Reply in thread")}
onClick={this.onThreadClick}
key="thread"
/>);
} }
if (allowCancel) { if (allowCancel) {

View file

@ -20,16 +20,15 @@ import classNames from 'classnames';
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RightPanelStore from '../../../stores/right-panel/RightPanelStore'; import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import { backLabelForPhase } from '../../../stores/right-panel/RightPanelStorePhases';
export const CardContext = React.createContext({ isCard: false });
interface IProps { interface IProps {
header?: ReactNode; header?: ReactNode;
footer?: ReactNode; footer?: ReactNode;
className?: string; className?: string;
withoutScrollContainer?: boolean; withoutScrollContainer?: boolean;
previousPhase?: RightPanelPhases;
previousPhaseLabel?: string;
closeLabel?: string; closeLabel?: string;
onClose?(): void; onClose?(): void;
cardState?; cardState?;
@ -54,20 +53,16 @@ const BaseCard: React.FC<IProps> = ({
header, header,
footer, footer,
withoutScrollContainer, withoutScrollContainer,
previousPhase,
previousPhaseLabel,
children, children,
cardState,
}) => { }) => {
let backButton; let backButton;
if (previousPhase) { const cardHistory = RightPanelStore.instance.roomPhaseHistory;
if (cardHistory.length > 1) {
const prevCard = cardHistory[cardHistory.length - 2];
const onBackClick = () => { const onBackClick = () => {
// TODO RightPanelStore (will be addressed in a follow up PR): this should ideally be: RightPanelStore.instance.popCard();
// RightPanelStore.instance.popRightPanel();
RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState });
}; };
const label = previousPhaseLabel ?? _t("Back"); const label = backLabelForPhase(prevCard.phase) ?? _t("Back");
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />; backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />;
} }
@ -87,6 +82,7 @@ const BaseCard: React.FC<IProps> = ({
} }
return ( return (
<CardContext.Provider value={{ isCard: true }}>
<div className={classNames("mx_BaseCard", className)}> <div className={classNames("mx_BaseCard", className)}>
<div className="mx_BaseCard_header"> <div className="mx_BaseCard_header">
{ backButton } { backButton }
@ -96,6 +92,7 @@ const BaseCard: React.FC<IProps> = ({
{ children } { children }
{ footer && <div className="mx_BaseCard_footer">{ footer }</div> } { footer && <div className="mx_BaseCard_footer">{ footer }</div> }
</div> </div>
</CardContext.Provider>
); );
}; };

View file

@ -116,10 +116,13 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
setRequest(verificationRequest_); setRequest(verificationRequest_);
setPhase(verificationRequest_.phase); setPhase(verificationRequest_.phase);
// Notify the RightPanelStore about this // Notify the RightPanelStore about this
RightPanelStore.instance.setCard({ if (RightPanelStore.instance.currentCard.phase != RightPanelPhases.EncryptionPanel) {
RightPanelStore.instance.pushCard({
phase: RightPanelPhases.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
state: { member, verificationRequest: verificationRequest_ }, state: { member, verificationRequest: verificationRequest_ },
}); });
}
if (!RightPanelStore.instance.isOpenForRoom) RightPanelStore.instance.togglePanel();
}, [member]); }, [member]);
const requested = const requested =

View file

@ -28,6 +28,7 @@ import { Action } from "../../../dispatcher/actions";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
const GROUP_PHASES = [ const GROUP_PHASES = [
RightPanelPhases.GroupMemberInfo, RightPanelPhases.GroupMemberInfo,
@ -49,7 +50,11 @@ export default class GroupHeaderButtons extends HeaderButtons {
protected onAction(payload: ActionPayload) { protected onAction(payload: ActionPayload) {
if (payload.action === Action.ViewUser) { if (payload.action === Action.ViewUser) {
if ((payload as ViewUserPayload).member) { if ((payload as ViewUserPayload).member) {
this.setPhase(RightPanelPhases.RoomMemberInfo, { member: payload.member }); RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.GroupRoomInfo },
{ phase: RightPanelPhases.GroupMemberList },
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: payload.member } },
]);
} else { } else {
this.setPhase(RightPanelPhases.GroupMemberList); this.setPhase(RightPanelPhases.GroupMemberList);
} }

View file

@ -71,7 +71,13 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
protected abstract onAction(payload); protected abstract onAction(payload);
public setPhase(phase: RightPanelPhases, cardState?: Partial<IRightPanelCardState>) { public setPhase(phase: RightPanelPhases, cardState?: Partial<IRightPanelCardState>) {
const rps = RightPanelStore.instance;
if (rps.currentCard.phase == phase && !cardState && rps.isOpenForRoom) {
rps.togglePanel();
} else {
RightPanelStore.instance.setCard({ phase, state: cardState }); RightPanelStore.instance.setCard({ phase, state: cardState });
if (!rps.isOpenForRoom) rps.togglePanel();
}
} }
public isPhase(phases: string | string[]) { public isPhase(phases: string | string[]) {

View file

@ -32,7 +32,7 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads"; import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { NotificationColor } from "../../../stores/notifications/NotificationColor";
@ -154,7 +154,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
protected onAction(payload: ActionPayload) { protected onAction(payload: ActionPayload) {
if (payload.action === Action.ViewUser) { if (payload.action === Action.ViewUser) {
if (payload.member) { if (payload.member) {
this.setPhase(RightPanelPhases.RoomMemberInfo, { member: payload.member }); if (payload.push) {
RightPanelStore.instance.pushCard(
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: payload.member } },
);
} else {
RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.RoomSummary },
{ phase: RightPanelPhases.RoomMemberList },
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: payload.member } },
]);
}
} else { } else {
this.setPhase(RightPanelPhases.RoomMemberList); this.setPhase(RightPanelPhases.RoomMemberList);
} }
@ -199,7 +209,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
RightPanelStore.instance.togglePanel(); RightPanelStore.instance.togglePanel();
} else { } else {
dispatchShowThreadsPanelEvent(); showThreadPanel();
} }
}; };

View file

@ -102,8 +102,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
}, [room.roomId]); }, [room.roomId]);
const onOpenWidgetClick = () => { const onOpenWidgetClick = () => {
// TODO RightPanelStore (will be addressed in a follow up PR): should push the widget RightPanelStore.instance.pushCard({
RightPanelStore.instance.setCard({
phase: RightPanelPhases.Widget, phase: RightPanelPhases.Widget,
state: { widgetId: app.id }, state: { widgetId: app.id },
}); });
@ -234,13 +233,11 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
}; };
export const onRoomMembersClick = (allowClose = true) => { export const onRoomMembersClick = (allowClose = true) => {
// TODO RightPanelStore (will be addressed in a follow up PR): should push the phase RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, allowClose);
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomMemberList }, allowClose);
}; };
export const onRoomFilesClick = (allowClose = true) => { export const onRoomFilesClick = (allowClose = true) => {
// TODO RightPanelStore (will be addressed in a follow up PR): should push the phase RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, allowClose);
RightPanelStore.instance.setCard({ phase: RightPanelPhases.FilePanel }, allowClose);
}; };
const onRoomSettingsClick = () => { const onRoomSettingsClick = () => {

View file

@ -1651,21 +1651,15 @@ const UserInfo: React.FC<IProps> = ({
const classes = ["mx_UserInfo"]; const classes = ["mx_UserInfo"];
let cardState: IRightPanelCardState; 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 // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
if (room && phase === RightPanelPhases.EncryptionPanel) { if (room && phase === RightPanelPhases.EncryptionPanel) {
previousPhase = RightPanelPhases.RoomMemberInfo;
cardState = { member }; cardState = { member };
} else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) { } else if (room?.isSpaceRoom() && SpaceStore.spacesEnabled) {
previousPhase = RightPanelPhases.SpaceMemberList;
cardState = { spaceId: room.roomId }; cardState = { spaceId: room.roomId };
} else if (room) {
previousPhase = RightPanelPhases.RoomMemberList;
} }
const onEncryptionPanelClose = () => { const onEncryptionPanelClose = () => {
// TODO RightPanelStore (will be addressed in a follow up PR): here we want to pop the panel RightPanelStore.instance.popCard();
RightPanelStore.instance.setCard({ phase: previousPhase, state: cardState });
}; };
let content; let content;
@ -1679,7 +1673,8 @@ const UserInfo: React.FC<IProps> = ({
member={member as User} member={member as User}
groupId={groupId as string} groupId={groupId as string}
devices={devices} devices={devices}
isRoomEncrypted={isRoomEncrypted} /> isRoomEncrypted={isRoomEncrypted}
/>
); );
break; break;
case RightPanelPhases.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
@ -1720,7 +1715,6 @@ const UserInfo: React.FC<IProps> = ({
header={header} header={header}
onClose={onClose} onClose={onClose}
closeLabel={closeLabel} closeLabel={closeLabel}
previousPhase={previousPhase}
cardState={cardState} cardState={cardState}
> >
{ content } { content }

View file

@ -23,7 +23,6 @@ import WidgetUtils from "../../../utils/WidgetUtils";
import AppTile from "../elements/AppTile"; import AppTile from "../elements/AppTile";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { useWidgets } from "./RoomSummaryCard"; import { useWidgets } from "./RoomSummaryCard";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu"; import { ChevronFace, ContextMenuButton, useContextMenu } from "../../structures/ContextMenu";
import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import WidgetContextMenu from "../context_menus/WidgetContextMenu";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
@ -48,9 +47,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
useEffect(() => { useEffect(() => {
if (!app || isPinned) { if (!app || isPinned) {
// stop showing this card // stop showing this card
RightPanelStore.instance.popCard();
//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]); }, [app, isPinned]);
@ -88,7 +85,6 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
header={header} header={header}
className="mx_WidgetCard" className="mx_WidgetCard"
onClose={onClose} onClose={onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer withoutScrollContainer
> >
<AppTile <AppTile

View file

@ -61,7 +61,7 @@ import ReactionsRow from '../messages/ReactionsRow';
import { getEventDisplayInfo } from '../../../utils/EventUtils'; import { getEventDisplayInfo } from '../../../utils/EventUtils';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion"; import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/threads'; import { showThread } from '../../../dispatcher/dispatch-actions/threads';
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore'; import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
import { TimelineRenderingType } from "../../../contexts/RoomContext"; import { TimelineRenderingType } from "../../../contexts/RoomContext";
import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../../utils/MediaEventHelper";
@ -72,6 +72,7 @@ import { ThreadNotificationState } from '../../../stores/notifications/ThreadNot
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore'; import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState'; import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
import { NotificationColor } from '../../../stores/notifications/NotificationColor'; import { NotificationColor } from '../../../stores/notifications/NotificationColor';
import { CardContext } from '../right_panel/BaseCard';
const eventTileTypes = { const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent', [EventType.RoomMessage]: 'messages.MessageEvent',
@ -670,12 +671,12 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
return ( return (
<CardContext.Consumer>
{ context =>
<div <div
className="mx_ThreadInfo" className="mx_ThreadInfo"
onClick={() => { onClick={() => {
dispatchShowThreadEvent( showThread({ rootEvent: this.props.mxEvent, push: context.isCard });
this.props.mxEvent,
);
}} }}
> >
<span className="mx_ThreadInfo_threads-amount"> <span className="mx_ThreadInfo_threads-amount">
@ -685,6 +686,8 @@ export default class EventTile extends React.Component<IProps, IState> {
</span> </span>
{ this.renderThreadLastMessagePreview() } { this.renderThreadLastMessagePreview() }
</div> </div>
}
</CardContext.Consumer>
); );
} }
@ -1411,7 +1414,7 @@ export default class EventTile extends React.Component<IProps, IState> {
"data-notification": this.state.threadNotification, "data-notification": this.state.threadNotification,
"onMouseEnter": () => this.setState({ hover: true }), "onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }), "onMouseLeave": () => this.setState({ hover: false }),
"onClick": () => dispatchShowThreadEvent(this.props.mxEvent), "onClick": () => showThread({ rootEvent: this.props.mxEvent, push: true }),
}, <> }, <>
{ sender } { sender }
{ avatar } { avatar }
@ -1430,7 +1433,7 @@ export default class EventTile extends React.Component<IProps, IState> {
<RovingAccessibleTooltipButton <RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton" className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Reply in thread")} title={_t("Reply in thread")}
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)} onClick={() => showThread({ rootEvent: this.props.mxEvent, push: true })}
key="thread" key="thread"
/> />
<RovingThreadListContextMenu <RovingThreadListContextMenu

View file

@ -33,7 +33,6 @@ import { isValid3pidInvite } from "../../../RoomInvite";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import BaseCard from "../right_panel/BaseCard"; import BaseCard from "../right_panel/BaseCard";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -513,7 +512,6 @@ export default class MemberList extends React.Component<IProps, IState> {
return <BaseCard return <BaseCard
className="mx_MemberList" className="mx_MemberList"
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
> >
<Spinner /> <Spinner />
</BaseCard>; </BaseCard>;
@ -567,11 +565,8 @@ export default class MemberList extends React.Component<IProps, IState> {
/> />
); );
let previousPhase = RightPanelPhases.RoomSummary;
// We have no previousPhase for when viewing a MemberList from a Space
let scopeHeader; let scopeHeader;
if (SpaceStore.spacesEnabled && room?.isSpaceRoom()) { if (SpaceStore.spacesEnabled && room?.isSpaceRoom()) {
previousPhase = undefined;
scopeHeader = <div className="mx_RightPanel_scopeHeader"> scopeHeader = <div className="mx_RightPanel_scopeHeader">
<RoomAvatar room={room} height={32} width={32} /> <RoomAvatar room={room} height={32} width={32} />
<RoomName room={room} /> <RoomName room={room} />
@ -586,7 +581,6 @@ export default class MemberList extends React.Component<IProps, IState> {
</React.Fragment>} </React.Fragment>}
footer={footer} footer={footer}
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={previousPhase}
> >
<div className="mx_MemberList_wrapper"> <div className="mx_MemberList_wrapper">
<TruncatedList <TruncatedList

View file

@ -199,6 +199,7 @@ export default class MemberTile extends React.Component<IProps, IState> {
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.props.member, member: this.props.member,
push: true,
}); });
}; };

View file

@ -115,11 +115,13 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
room_id: request.channel.roomId, room_id: request.channel.roomId,
should_peek: false, should_peek: false,
}); });
RightPanelStore.instance.setCard( const member = cli.getUser(request.otherUserId);
{ RightPanelStore.instance.setCards(
phase: RightPanelPhases.EncryptionPanel, [
state: { verificationRequest: request, member: cli.getUser(request.otherUserId) }, { phase: RightPanelPhases.RoomSummary },
}, { phase: RightPanelPhases.RoomMemberInfo, state: { member } },
{ phase: RightPanelPhases.EncryptionPanel, state: { verificationRequest: request, member } },
],
undefined, undefined,
request.channel.roomId, request.channel.roomId,
); );

View file

@ -18,23 +18,32 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import RightPanelStore from "../../stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
export const dispatchShowThreadEvent = ( export const showThread = (props: {
rootEvent: MatrixEvent, rootEvent: MatrixEvent;
initialEvent?: MatrixEvent, initialEvent?: MatrixEvent;
highlighted?: boolean, highlighted?: boolean;
) => { push?: boolean;
// TODO RightPanelStore (will be addressed in a follow up PR): this should really be a push! }) => {
RightPanelStore.instance.setCard({ const push = props.push ?? false;
const threadViewCard = {
phase: RightPanelPhases.ThreadView, phase: RightPanelPhases.ThreadView,
state: { state: {
threadHeadEvent: rootEvent, threadHeadEvent: props.rootEvent,
initialEvent, initialEvent: props.initialEvent,
isInitialEventHighlighted: highlighted, isInitialEventHighlighted: props.highlighted,
}, },
}); };
if (push) {
RightPanelStore.instance.pushCard(threadViewCard);
} else {
RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.ThreadPanel },
threadViewCard,
]);
}
}; };
export const dispatchShowThreadsPanelEvent = () => { export const showThreadPanel = () => {
RightPanelStore.instance.setCard({ phase: RightPanelPhases.ThreadPanel }); RightPanelStore.instance.setCard({ phase: RightPanelPhases.ThreadPanel });
}; };

View file

@ -843,6 +843,11 @@
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s", "%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"Threads": "Threads",
"Back to chat": "Back to chat",
"Room information": "Room information",
"Room members": "Room members",
"Back to thread": "Back to thread",
"Change notification settings": "Change notification settings", "Change notification settings": "Change notification settings",
"Messaging": "Messaging", "Messaging": "Messaging",
"Profile": "Profile", "Profile": "Profile",
@ -1507,7 +1512,6 @@
"this room": "this room", "this room": "this room",
"View older messages in %(roomName)s.": "View older messages in %(roomName)s.", "View older messages in %(roomName)s.": "View older messages in %(roomName)s.",
"Space information": "Space information", "Space information": "Space information",
"Room information": "Room information",
"Internal room ID:": "Internal room ID:", "Internal room ID:": "Internal room ID:",
"Room version": "Room version", "Room version": "Room version",
"Room version:": "Room version:", "Room version:": "Room version:",
@ -1920,7 +1924,6 @@
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.", "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
"Pinned messages": "Pinned messages", "Pinned messages": "Pinned messages",
"Chat": "Chat", "Chat": "Chat",
"Threads": "Threads",
"Room Info": "Room Info", "Room Info": "Room Info",
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
"Maximise widget": "Maximise widget", "Maximise widget": "Maximise widget",

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { EventSubscription } from 'fbemitter';
import defaultDispatcher from '../../dispatcher/dispatcher'; import defaultDispatcher from '../../dispatcher/dispatcher';
import { pendingVerificationRequestForUser } from '../../verification'; import { pendingVerificationRequestForUser } from '../../verification';
@ -31,7 +30,6 @@ import {
convertToStatePanel, convertToStatePanel,
convertToStorePanel, convertToStorePanel,
IRightPanelForRoom, IRightPanelForRoom,
convertCardToStore,
} from './RightPanelStoreIPanelState'; } from './RightPanelStoreIPanelState';
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
// import RoomViewStore from '../RoomViewStore'; // import RoomViewStore from '../RoomViewStore';
@ -63,7 +61,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
private viewedRoomId: string; private viewedRoomId: string;
private isViewingRoom?: boolean; private isViewingRoom?: boolean;
private dispatcherRefRightPanelStore: string; private dispatcherRefRightPanelStore: string;
private roomStoreToken: EventSubscription; private isReady = false;
private global?: IRightPanelForRoom = null; private global?: IRightPanelForRoom = null;
private byRoom: { private byRoom: {
@ -76,6 +74,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
protected async onReady(): Promise<any> { protected async onReady(): Promise<any> {
this.isReady = true;
// TODO RightPanelStore (will be addressed when dropping groups): This should be used instead of the onDispatch callback when groups are removed. // 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); // RoomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequestUpdate); MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequestUpdate);
@ -90,9 +89,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
protected async onNotReady(): Promise<any> { protected async onNotReady(): Promise<any> {
if (this.roomStoreToken) { this.isReady = false;
this.roomStoreToken.remove();
}
MatrixClientPeg.get().off("crypto.verification.request", this.onVerificationRequestUpdate); MatrixClientPeg.get().off("crypto.verification.request", this.onVerificationRequestUpdate);
// TODO RightPanelStore (will be addressed when dropping groups): User this instead of the dispatcher. // TODO RightPanelStore (will be addressed when dropping groups): User this instead of the dispatcher.
// RoomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); // RoomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
@ -140,8 +137,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
// Setters // Setters
public setCard(card: IRightPanelCard, allowClose = true, roomId?: string) { public setCard(card: IRightPanelCard, allowClose = true, roomId?: string) {
const rId = roomId ?? this.viewedRoomId; const rId = roomId ?? this.viewedRoomId;
// this was previously a very multifunctional command: // This function behaves as following:
// Toggle panel: if the same phase is send but without a state
// Update state: if the same phase is send but with 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) // 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 redirect = this.getVerificationRedirect(card);
@ -149,32 +145,29 @@ export default class RightPanelStore extends ReadyWatchingStore {
const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); const cardState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state);
// Checks for wrong SetRightPanelPhase requests // Checks for wrong SetRightPanelPhase requests
if (!this.isPhaseActionIsValid(targetPhase)) return; if (!this.isPhaseActionValid(targetPhase)) return;
if (targetPhase === this.currentCard?.phase && if ((targetPhase === this.currentCardForRoom(rId)?.phase && !!cardState)) {
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...) // 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 ?? []; const hist = this.byRoom[rId]?.history ?? [];
hist[hist.length - 1].state = cardState; hist[hist.length - 1].state = cardState;
this.emitAndUpdateSettings(); this.emitAndUpdateSettings();
return; return;
} else if (targetPhase !== this.currentCard?.phase) { }
if (targetPhase !== this.currentCard?.phase) {
// Set right panel and erase history. // Set right panel and erase history.
this.setRightPanelCache({ phase: targetPhase, state: cardState ?? {} }, rId); this.setRightPanelCache({ phase: targetPhase, state: cardState ?? {} }, rId);
} }
} }
public setCards(cards: IRightPanelCard[], allowClose = true, roomId: string = null) {
const rId = roomId ?? this.viewedRoomId;
const history = cards.map(c => ({ phase: c.phase, state: c.state ?? {} }));
this.byRoom[rId] = { history, isOpen: true };
this.emitAndUpdateSettings();
}
public pushCard( public pushCard(
card: IRightPanelCard, card: IRightPanelCard,
allowClose = true, allowClose = true,
@ -186,7 +179,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
const pState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state); const pState = redirect?.state ?? (Object.keys(card.state ?? {}).length === 0 ? null : card.state);
// Checks for wrong SetRightPanelPhase requests // Checks for wrong SetRightPanelPhase requests
if (!this.isPhaseActionIsValid(targetPhase)) return; if (!this.isPhaseActionValid(targetPhase)) return;
let roomCache = this.byRoom[rId]; let roomCache = this.byRoom[rId];
if (!!roomCache) { if (!!roomCache) {
@ -236,10 +229,6 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
} }
private compareCards(a: IRightPanelCard, b: IRightPanelCard): boolean {
return JSON.stringify(convertCardToStore(a)) == JSON.stringify(convertCardToStore(b));
}
private emitAndUpdateSettings() { private emitAndUpdateSettings() {
const storePanelGlobal = convertToStorePanel(this.global); const storePanelGlobal = convertToStorePanel(this.global);
SettingsStore.setValue("RightPanel.phasesGlobal", null, SettingLevel.DEVICE, storePanelGlobal); SettingsStore.setValue("RightPanel.phasesGlobal", null, SettingLevel.DEVICE, storePanelGlobal);
@ -257,10 +246,8 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
private setRightPanelCache(card: IRightPanelCard, roomId?: string) { private setRightPanelCache(card: IRightPanelCard, roomId?: string) {
this.byRoom[roomId ?? this.viewedRoomId] = { const history = [{ phase: card.phase, state: card.state ?? {} }];
history: [{ phase: card.phase, state: card.state ?? {} }], this.byRoom[roomId ?? this.viewedRoomId] = { history, isOpen: true };
isOpen: true,
};
this.emitAndUpdateSettings(); this.emitAndUpdateSettings();
} }
@ -282,7 +269,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
return null; return null;
} }
private isPhaseActionIsValid(targetPhase) { private isPhaseActionValid(targetPhase) {
if (!RightPanelPhases[targetPhase]) { if (!RightPanelPhases[targetPhase]) {
logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`);
return false; return false;
@ -305,6 +292,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
private onVerificationRequestUpdate = () => { private onVerificationRequestUpdate = () => {
const { member } = this.currentCard.state; const { member } = this.currentCard.state;
if (!member) return;
const pendingRequest = pendingVerificationRequestForUser(member); const pendingRequest = pendingVerificationRequestForUser(member);
if (pendingRequest) { if (pendingRequest) {
this.currentCard.state.verificationRequest = pendingRequest; this.currentCard.state.verificationRequest = pendingRequest;
@ -312,13 +300,13 @@ export default class RightPanelStore extends ReadyWatchingStore {
} }
}; };
onRoomViewStoreUpdate() { onRoomViewStoreUpdate = () => {
// TODO: use this function instead of the onDispatch (the whole onDispatch can get removed!) as soon groups are removed // TODO: use this function instead of the onDispatch (the whole onDispatch can get removed!) as soon groups are removed
// this.viewedRoomId = RoomViewStore.getRoomId(); // this.viewedRoomId = RoomViewStore.getRoomId();
// this.isViewingRoom = true; // Is viewing room will of course be removed when removing groups // this.isViewingRoom = true; // Is viewing room will of course be removed when removing groups
// // load values from byRoomCache with the viewedRoomId. // // load values from byRoomCache with the viewedRoomId.
// this.loadCacheFromSettings(); // this.loadCacheFromSettings();
} };
onDispatch(payload: ActionPayload) { onDispatch(payload: ActionPayload) {
switch (payload.action) { switch (payload.action) {
@ -347,9 +335,9 @@ export default class RightPanelStore extends ReadyWatchingStore {
_this.viewedRoomId = payload.room_id; _this.viewedRoomId = payload.room_id;
_this.isViewingRoom = payload.action == Action.ViewRoom; _this.isViewingRoom = payload.action == Action.ViewRoom;
// load values from byRoomCache with the viewedRoomId. // load values from byRoomCache with the viewedRoomId.
if (!!_this.roomStoreToken) { if (_this.isReady) {
// skip loading here since we need the client to be ready to get the events form the ids of the settings // 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 // the loading will be done in the onReady function (to catch up with the changes done here before it was ready)
// all the logic in this case is not necessary anymore as soon as groups are dropped and we use: onRoomViewStoreUpdate // all the logic in this case is not necessary anymore as soon as groups are dropped and we use: onRoomViewStoreUpdate
_this.loadCacheFromSettings(); _this.loadCacheFromSettings();
_this.emitAndUpdateSettings(); _this.emitAndUpdateSettings();

View file

@ -89,48 +89,43 @@ export function convertToStatePanel(storeRoom: IRightPanelForRoomStored, room: R
} }
export function convertCardToStore(panelState: IRightPanelCard): IRightPanelCardStored { export function convertCardToStore(panelState: IRightPanelCard): IRightPanelCardStored {
const panelStateThisRoomStored = { ...panelState.state } as any; const state = panelState.state ?? {};
if (!!panelState?.state?.threadHeadEvent?.getId()) { const stateStored: IRightPanelCardStateStored = {
panelStateThisRoomStored.threadHeadEventId = panelState.state.threadHeadEvent.getId(); groupId: state.groupId,
} groupRoomId: state.groupRoomId,
if (!!panelState?.state?.memberInfoEvent?.getId()) { widgetId: state.widgetId,
panelStateThisRoomStored.memberInfoEventId = panelState.state.memberInfoEvent.getId(); spaceId: state.spaceId,
} isInitialEventHighlighted: state.isInitialEventHighlighted,
if (!!panelState?.state?.initialEvent?.getId()) { threadHeadEventId: !!state?.threadHeadEvent?.getId() ?
panelStateThisRoomStored.initialEventId = panelState.state.initialEvent.getId(); panelState.state.threadHeadEvent.getId() : undefined,
} memberInfoEventId: !!state?.memberInfoEvent?.getId() ?
if (!!panelState?.state?.member?.userId) { panelState.state.memberInfoEvent.getId() : undefined,
panelStateThisRoomStored.memberId = panelState.state.member.userId; initialEventId: !!state?.initialEvent?.getId() ?
} panelState.state.initialEvent.getId() : undefined,
delete panelStateThisRoomStored.threadHeadEvent; memberId: !!state?.member?.userId ?
delete panelStateThisRoomStored.initialEvent; panelState.state.member.userId : undefined,
delete panelStateThisRoomStored.memberInfoEvent; };
delete panelStateThisRoomStored.verificationRequest;
delete panelStateThisRoomStored.verificationRequestPromise;
delete panelStateThisRoomStored.member;
const storedCard = { state: panelStateThisRoomStored as IRightPanelCardStored, phase: panelState.phase }; return { state: stateStored, phase: panelState.phase };
return storedCard as IRightPanelCardStored;
} }
function convertStoreToCard(panelStateStore: IRightPanelCardStored, room: Room): IRightPanelCard { function convertStoreToCard(panelStateStore: IRightPanelCardStored, room: Room): IRightPanelCard {
const panelStateThisRoom = { ...panelStateStore?.state } as any; const stateStored = panelStateStore.state ?? {};
if (!!panelStateThisRoom.threadHeadEventId) { const state: IRightPanelCardState = {
panelStateThisRoom.threadHeadEvent = room.findEventById(panelStateThisRoom.threadHeadEventId); groupId: stateStored.groupId,
} groupRoomId: stateStored.groupRoomId,
if (!!panelStateThisRoom.memberInfoEventId) { widgetId: stateStored.widgetId,
panelStateThisRoom.memberInfoEvent = room.findEventById(panelStateThisRoom.memberInfoEventId); spaceId: stateStored.spaceId,
} isInitialEventHighlighted: stateStored.isInitialEventHighlighted,
if (!!panelStateThisRoom.initialEventId) { threadHeadEvent: !!stateStored?.threadHeadEventId ?
panelStateThisRoom.initialEvent = room.findEventById(panelStateThisRoom.initialEventId); room.findEventById(stateStored.threadHeadEventId) : undefined,
} memberInfoEvent: !!stateStored?.memberInfoEventId ?
if (!!panelStateThisRoom.memberId) { room.findEventById(stateStored.memberInfoEventId) : undefined,
panelStateThisRoom.member = room.getMember(panelStateThisRoom.memberId); initialEvent: !!stateStored?.initialEventId ?
} room.findEventById(stateStored.initialEventId) : undefined,
delete panelStateThisRoom.threadHeadEventId; member: !!stateStored?.memberId ?
delete panelStateThisRoom.initialEventId; room.getMember(stateStored.memberId) : undefined,
delete panelStateThisRoom.memberInfoEventId; };
delete panelStateThisRoom.memberId;
return { state: panelStateThisRoom as IRightPanelCardState, phase: panelStateStore.phase } as IRightPanelCard; return { state: state, phase: panelStateStore.phase };
} }

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { _t } from "../../languageHandler";
// These are in their own file because of circular imports being a problem. // These are in their own file because of circular imports being a problem.
export enum RightPanelPhases { export enum RightPanelPhases {
// Room stuff // Room stuff
@ -44,6 +46,17 @@ export enum RightPanelPhases {
ThreadPanel = "ThreadPanel", ThreadPanel = "ThreadPanel",
} }
export function backLabelForPhase(phase: RightPanelPhases) {
switch (phase) {
case RightPanelPhases.ThreadPanel: return _t("Threads");
case RightPanelPhases.Timeline: return _t("Back to chat");
case RightPanelPhases.RoomSummary: return _t("Room information");
case RightPanelPhases.RoomMemberList: return _t("Room members");
case RightPanelPhases.ThreadView: return _t("Back to thread");
}
return null;
}
// These are the phases that are safe to persist (the ones that don't require additional // These are the phases that are safe to persist (the ones that don't require additional
// arguments). // arguments).
export const RIGHT_PANEL_PHASES_NO_ARGS = [ export const RIGHT_PANEL_PHASES_NO_ARGS = [

View file

@ -28,6 +28,7 @@ import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDia
import { GroupMember, IDevice } from "./components/views/right_panel/UserInfo"; import { GroupMember, IDevice } from "./components/views/right_panel/UserInfo";
import ManualDeviceKeyVerificationDialog from "./components/views/dialogs/ManualDeviceKeyVerificationDialog"; import ManualDeviceKeyVerificationDialog from "./components/views/dialogs/ManualDeviceKeyVerificationDialog";
import RightPanelStore from "./stores/right-panel/RightPanelStore"; import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
async function enable4SIfNeeded() { async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -66,10 +67,7 @@ export async function verifyDevice(user: User, device: IDevice) {
device.deviceId, device.deviceId,
VerificationMethods.SAS, VerificationMethods.SAS,
); );
RightPanelStore.instance.setCard({ setRightPanel({ member: user, verificationRequestPromise });
phase: RightPanelPhases.EncryptionPanel,
state: { member: user, verificationRequestPromise },
});
} else if (action === "legacy") { } else if (action === "legacy") {
Modal.createTrackedDialog("Legacy verify session", "legacy verify session", Modal.createTrackedDialog("Legacy verify session", "legacy verify session",
ManualDeviceKeyVerificationDialog, ManualDeviceKeyVerificationDialog,
@ -96,10 +94,7 @@ export async function legacyVerifyUser(user: User) {
} }
} }
const verificationRequestPromise = cli.requestVerification(user.userId); const verificationRequestPromise = cli.requestVerification(user.userId);
RightPanelStore.instance.setCard({ setRightPanel({ member: user, verificationRequestPromise });
phase: RightPanelPhases.EncryptionPanel,
state: { member: user, verificationRequestPromise },
});
} }
export async function verifyUser(user: User) { export async function verifyUser(user: User) {
@ -112,10 +107,21 @@ export async function verifyUser(user: User) {
return; return;
} }
const existingRequest = pendingVerificationRequestForUser(user); const existingRequest = pendingVerificationRequestForUser(user);
RightPanelStore.instance.setCard({ setRightPanel({ member: user, verificationRequest: existingRequest });
phase: RightPanelPhases.EncryptionPanel, }
state: { member: user, verificationRequest: existingRequest },
}); function setRightPanel(state: IRightPanelCardState) {
if (RightPanelStore.instance.roomPhaseHistory.some((card) => (card.phase == RightPanelPhases.RoomSummary))) {
RightPanelStore.instance.pushCard(
{ phase: RightPanelPhases.EncryptionPanel, state },
);
} else {
RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.RoomSummary },
{ phase: RightPanelPhases.RoomMemberInfo, state: { member: state.member } },
{ phase: RightPanelPhases.EncryptionPanel, state },
]);
}
} }
export function pendingVerificationRequestForUser(user: User | RoomMember | GroupMember ) { export function pendingVerificationRequestForUser(user: User | RoomMember | GroupMember ) {