Merge pull request #5036 from swapnilraj/swapnilraj/right-panel-ts

Convert right_panel to TS
This commit is contained in:
Jorik Schellekens 2020-07-30 13:37:01 +01:00 committed by GitHub
commit b1f8fe40d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 568 additions and 387 deletions

View file

@ -54,6 +54,7 @@ import LeftPanel from "./LeftPanel";
import CallContainer from '../views/voip/CallContainer'; import CallContainer from '../views/voip/CallContainer';
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
import RoomListStore from "../../stores/room-list/RoomListStore"; import RoomListStore from "../../stores/room-list/RoomListStore";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
// We need to fetch each pinned message individually (if we don't already have it) // We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity. // so each pinned message may trigger a request. Limit the number per room for sanity.
@ -472,8 +473,8 @@ class LoggedInView extends React.Component<IProps, IState> {
case Key.PERIOD: case Key.PERIOD:
if (ctrlCmdOnly && (this.props.page_type === "room_view" || this.props.page_type === "group_view")) { if (ctrlCmdOnly && (this.props.page_type === "room_view" || this.props.page_type === "group_view")) {
dis.dispatch({ dis.dispatch<ToggleRightPanelPayload>({
action: 'toggle_right_panel', action: Action.ToggleRightPanel,
type: this.props.page_type === "room_view" ? "room" : "group", type: this.props.page_type === "room_view" ? "room" : "group",
}); });
handled = true; handled = true;

View file

@ -26,7 +26,7 @@ import dis from '../../dispatcher/dispatcher';
import RateLimitedFunc from '../../ratelimitedfunc'; import RateLimitedFunc from '../../ratelimitedfunc';
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
import GroupStore from '../../stores/GroupStore'; import GroupStore from '../../stores/GroupStore';
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases"; import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
import RightPanelStore from "../../stores/RightPanelStore"; import RightPanelStore from "../../stores/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import {Action} from "../../dispatcher/actions"; import {Action} from "../../dispatcher/actions";
@ -75,8 +75,8 @@ export default class RightPanel extends React.Component {
const userForPanel = this._getUserForPanel(); const userForPanel = this._getUserForPanel();
if (this.props.groupId) { if (this.props.groupId) {
if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.groupPanelPhase)) { if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.groupPanelPhase)) {
dis.dispatch({action: "set_right_panel_phase", phase: RIGHT_PANEL_PHASES.GroupMemberList}); dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.GroupMemberList});
return RIGHT_PANEL_PHASES.GroupMemberList; return RightPanelPhases.GroupMemberList;
} }
return rps.groupPanelPhase; return rps.groupPanelPhase;
} else if (userForPanel) { } else if (userForPanel) {
@ -98,11 +98,11 @@ export default class RightPanel extends React.Component {
) { ) {
return rps.roomPanelPhase; return rps.roomPanelPhase;
} }
return RIGHT_PANEL_PHASES.RoomMemberInfo; return RightPanelPhases.RoomMemberInfo;
} else { } else {
if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) { if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) {
dis.dispatch({action: "set_right_panel_phase", phase: RIGHT_PANEL_PHASES.RoomMemberList}); dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList});
return RIGHT_PANEL_PHASES.RoomMemberList; return RightPanelPhases.RoomMemberList;
} }
return rps.roomPanelPhase; return rps.roomPanelPhase;
} }
@ -149,7 +149,7 @@ export default class RightPanel extends React.Component {
onInviteToGroupButtonClick() { onInviteToGroupButtonClick() {
showGroupInviteDialog(this.props.groupId).then(() => { showGroupInviteDialog(this.props.groupId).then(() => {
this.setState({ this.setState({
phase: RIGHT_PANEL_PHASES.GroupMemberList, phase: RightPanelPhases.GroupMemberList,
}); });
}); });
} }
@ -165,9 +165,9 @@ export default class RightPanel extends React.Component {
return; return;
} }
// redraw the badge on the membership list // redraw the badge on the membership list
if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberList && member.roomId === this.props.roomId) { if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.roomId) {
this._delayedUpdate(); this._delayedUpdate();
} else if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo && member.roomId === this.props.roomId && } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) { member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level) // refresh the member info (e.g. new power level)
this._delayedUpdate(); this._delayedUpdate();
@ -175,7 +175,7 @@ export default class RightPanel extends React.Component {
} }
onAction(payload) { onAction(payload) {
if (payload.action === "after_right_panel_phase_change") { if (payload.action === Action.AfterRightPanelPhaseChange) {
this.setState({ this.setState({
phase: payload.phase, phase: payload.phase,
groupRoomId: payload.groupRoomId, groupRoomId: payload.groupRoomId,
@ -206,7 +206,7 @@ export default class RightPanel extends React.Component {
// or the member list if we were in the member panel... phew. // or the member list if we were in the member panel... phew.
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? this.state.member : null, member: this.state.phase === RightPanelPhases.EncryptionPanel ? this.state.member : null,
}); });
} }
}; };
@ -225,21 +225,21 @@ export default class RightPanel extends React.Component {
let panel = <div />; let panel = <div />;
switch (this.state.phase) { switch (this.state.phase) {
case RIGHT_PANEL_PHASES.RoomMemberList: case RightPanelPhases.RoomMemberList:
if (this.props.roomId) { if (this.props.roomId) {
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />; panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />;
} }
break; break;
case RIGHT_PANEL_PHASES.GroupMemberList: case RightPanelPhases.GroupMemberList:
if (this.props.groupId) { if (this.props.groupId) {
panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />; panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
} }
break; break;
case RIGHT_PANEL_PHASES.GroupRoomList: case RightPanelPhases.GroupRoomList:
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />; panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
break; break;
case RIGHT_PANEL_PHASES.RoomMemberInfo: case RightPanelPhases.RoomMemberInfo:
case RIGHT_PANEL_PHASES.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
panel = <UserInfo panel = <UserInfo
user={this.state.member} user={this.state.member}
roomId={this.props.roomId} roomId={this.props.roomId}
@ -250,26 +250,26 @@ export default class RightPanel extends React.Component {
verificationRequestPromise={this.state.verificationRequestPromise} verificationRequestPromise={this.state.verificationRequestPromise}
/>; />;
break; break;
case RIGHT_PANEL_PHASES.Room3pidMemberInfo: case RightPanelPhases.Room3pidMemberInfo:
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />; panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
break; break;
case RIGHT_PANEL_PHASES.GroupMemberInfo: case RightPanelPhases.GroupMemberInfo:
panel = <UserInfo panel = <UserInfo
user={this.state.member} user={this.state.member}
groupId={this.props.groupId} groupId={this.props.groupId}
key={this.state.member.userId} key={this.state.member.userId}
onClose={this.onCloseUserInfo} />; onClose={this.onCloseUserInfo} />;
break; break;
case RIGHT_PANEL_PHASES.GroupRoomInfo: case RightPanelPhases.GroupRoomInfo:
panel = <GroupRoomInfo panel = <GroupRoomInfo
groupRoomId={this.state.groupRoomId} groupRoomId={this.state.groupRoomId}
groupId={this.props.groupId} groupId={this.props.groupId}
key={this.state.groupRoomId} />; key={this.state.groupRoomId} />;
break; break;
case RIGHT_PANEL_PHASES.NotificationPanel: case RightPanelPhases.NotificationPanel:
panel = <NotificationPanel />; panel = <NotificationPanel />;
break; break;
case RIGHT_PANEL_PHASES.FilePanel: case RightPanelPhases.FilePanel:
panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />; panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />;
break; break;
} }

View file

@ -24,8 +24,9 @@ import GroupStore from '../../../stores/GroupStore';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { showGroupInviteDialog } from '../../../GroupAddressPicker'; import { showGroupInviteDialog } from '../../../GroupAddressPicker';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {Action} from "../../../dispatcher/actions";
const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
@ -164,9 +165,9 @@ export default createReactClass({
onInviteToGroupButtonClick() { onInviteToGroupButtonClick() {
showGroupInviteDialog(this.props.groupId).then(() => { showGroupInviteDialog(this.props.groupId).then(() => {
dis.dispatch({ dis.dispatch({
action: 'set_right_panel_phase', action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.GroupMemberList, phase: RightPanelPhases.GroupMemberList,
groupId: this.props.groupId, refireParams: { groupId: this.props.groupId },
}); });
}); });
}, },

View file

@ -22,7 +22,8 @@ import { _t } from '../../../languageHandler';
import {getNameForEventRoom, userLabelForEventRoom} import {getNameForEventRoom, userLabelForEventRoom}
from '../../../utils/KeyVerificationStateObserver'; from '../../../utils/KeyVerificationStateObserver';
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions";
export default class MKeyVerificationRequest extends React.Component { export default class MKeyVerificationRequest extends React.Component {
constructor(props) { constructor(props) {
@ -48,8 +49,8 @@ export default class MKeyVerificationRequest extends React.Component {
const {verificationRequest} = this.props.mxEvent; const {verificationRequest} = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
dis.dispatch({ dis.dispatch({
action: "set_right_panel_phase", action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
refireParams: {verificationRequest, member}, refireParams: {verificationRequest, member},
}); });
}; };

View file

@ -15,10 +15,10 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
export const PendingActionSpinner = ({text}) => { export const PendingActionSpinner = ({text}) => {
const Spinner = sdk.getComponent('elements.Spinner'); const Spinner = sdk.getComponent('elements.Spinner');
@ -28,7 +28,17 @@ export const PendingActionSpinner = ({text}) => {
</div>; </div>;
}; };
const EncryptionInfo = ({ interface IProps {
waitingForOtherParty: boolean;
waitingForNetwork: boolean;
member: RoomMember;
onStartVerification: () => Promise<void>;
isRoomEncrypted: boolean;
inDialog: boolean;
isSelfVerification: boolean;
}
const EncryptionInfo: React.FC<IProps> = ({
waitingForOtherParty, waitingForOtherParty,
waitingForNetwork, waitingForNetwork,
member, member,
@ -36,10 +46,10 @@ const EncryptionInfo = ({
isRoomEncrypted, isRoomEncrypted,
inDialog, inDialog,
isSelfVerification, isSelfVerification,
}) => { }: IProps) => {
let content; let content: JSX.Element;
if (waitingForOtherParty || waitingForNetwork) { if (waitingForOtherParty || waitingForNetwork) {
let text; let text: string;
if (waitingForOtherParty) { if (waitingForOtherParty) {
if (isSelfVerification) { if (isSelfVerification) {
text = _t("Waiting for you to accept on your other session…"); text = _t("Waiting for you to accept on your other session…");
@ -61,7 +71,7 @@ const EncryptionInfo = ({
); );
} }
let description; let description: JSX.Element;
if (isRoomEncrypted) { if (isRoomEncrypted) {
description = ( description = (
<div> <div>
@ -97,10 +107,5 @@ const EncryptionInfo = ({
</div> </div>
</React.Fragment>; </React.Fragment>;
}; };
EncryptionInfo.propTypes = {
member: PropTypes.object.isRequired,
onStartVerification: PropTypes.func.isRequired,
request: PropTypes.object,
};
export default EncryptionInfo; export default EncryptionInfo;

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import React, {useCallback, useEffect, useState} from "react"; import React, {useCallback, useEffect, useState} from "react";
import PropTypes from "prop-types";
import EncryptionInfo from "./EncryptionInfo"; import EncryptionInfo from "./EncryptionInfo";
import VerificationPanel from "./VerificationPanel"; import VerificationPanel from "./VerificationPanel";
@ -26,11 +25,23 @@ import Modal from "../../../Modal";
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
// cancellation codes which constitute a key mismatch // cancellation codes which constitute a key mismatch
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
const EncryptionPanel = (props) => { interface IProps {
member: RoomMember;
onClose: () => void;
verificationRequest: VerificationRequest;
verificationRequestPromise: Promise<VerificationRequest>;
layout: string;
inDialog: boolean;
isRoomEncrypted: boolean;
}
const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted} = props; const {verificationRequest, verificationRequestPromise, member, onClose, layout, isRoomEncrypted} = props;
const [request, setRequest] = useState(verificationRequest); const [request, setRequest] = useState(verificationRequest);
// state to show a spinner immediately after clicking "start verification", // state to show a spinner immediately after clicking "start verification",
@ -48,10 +59,10 @@ const EncryptionPanel = (props) => {
useEffect(() => { useEffect(() => {
async function awaitPromise() { async function awaitPromise() {
setRequesting(true); setRequesting(true);
const request = await verificationRequestPromise; const requestFromPromise = await verificationRequestPromise;
setRequesting(false); setRequesting(false);
setRequest(request); setRequest(requestFromPromise);
setPhase(request.phase); setPhase(requestFromPromise.phase);
} }
if (verificationRequestPromise) { if (verificationRequestPromise) {
awaitPromise(); awaitPromise();
@ -90,7 +101,7 @@ const EncryptionPanel = (props) => {
} }
}, [request]); }, [request]);
let cancelButton; let cancelButton: JSX.Element;
if (layout !== "dialog" && request && request.pending) { if (layout !== "dialog" && request && request.pending) {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
cancelButton = (<AccessibleButton cancelButton = (<AccessibleButton
@ -104,9 +115,9 @@ const EncryptionPanel = (props) => {
setRequesting(true); setRequesting(true);
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const roomId = await ensureDMExists(cli, member.userId); const roomId = await ensureDMExists(cli, member.userId);
const verificationRequest = await cli.requestVerificationDM(member.userId, roomId); const verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
setRequest(verificationRequest); setRequest(verificationRequest_);
setPhase(verificationRequest.phase); setPhase(verificationRequest_.phase);
}, [member.userId]); }, [member.userId]);
const requested = const requested =
@ -144,12 +155,5 @@ const EncryptionPanel = (props) => {
</React.Fragment>); </React.Fragment>);
} }
}; };
EncryptionPanel.propTypes = {
member: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
verificationRequest: PropTypes.object,
layout: PropTypes.string,
inDialog: PropTypes.bool,
};
export default EncryptionPanel; export default EncryptionPanel;

View file

@ -21,65 +21,68 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import HeaderButton from './HeaderButton'; import HeaderButton from './HeaderButton';
import HeaderButtons, {HEADER_KIND_GROUP} from './HeaderButtons'; import HeaderButtons, {HeaderKind} from './HeaderButtons';
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import {ActionPayload} from "../../../dispatcher/payloads"; import {ActionPayload} from "../../../dispatcher/payloads";
import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
const GROUP_PHASES = [ const GROUP_PHASES = [
RIGHT_PANEL_PHASES.GroupMemberInfo, RightPanelPhases.GroupMemberInfo,
RIGHT_PANEL_PHASES.GroupMemberList, RightPanelPhases.GroupMemberList,
]; ];
const ROOM_PHASES = [ const ROOM_PHASES = [
RIGHT_PANEL_PHASES.GroupRoomList, RightPanelPhases.GroupRoomList,
RIGHT_PANEL_PHASES.GroupRoomInfo, RightPanelPhases.GroupRoomInfo,
]; ];
interface IProps {}
export default class GroupHeaderButtons extends HeaderButtons { export default class GroupHeaderButtons extends HeaderButtons {
constructor(props) { constructor(props: IProps) {
super(props, HEADER_KIND_GROUP); super(props, HeaderKind.Group);
this._onMembersClicked = this._onMembersClicked.bind(this); this.onMembersClicked = this.onMembersClicked.bind(this);
this._onRoomsClicked = this._onRoomsClicked.bind(this); this.onRoomsClicked = this.onRoomsClicked.bind(this);
} }
onAction(payload: ActionPayload) { protected onAction(payload: ActionPayload) {
super.onAction(payload); super.onAction(payload);
if (payload.action === Action.ViewUser) { if (payload.action === Action.ViewUser) {
if (payload.member) { if ((payload as ViewUserPayload).member) {
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo, {member: payload.member}); this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member});
} else { } else {
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); this.setPhase(RightPanelPhases.GroupMemberList);
} }
} else if (payload.action === "view_group") { } else if (payload.action === "view_group") {
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); this.setPhase(RightPanelPhases.GroupMemberList);
} else if (payload.action === "view_group_room") { } else if (payload.action === "view_group_room") {
this.setPhase( this.setPhase(
RIGHT_PANEL_PHASES.GroupRoomInfo, RightPanelPhases.GroupRoomInfo,
{groupRoomId: payload.groupRoomId, groupId: payload.groupId}, {groupRoomId: payload.groupRoomId, groupId: payload.groupId},
); );
} else if (payload.action === "view_group_room_list") { } else if (payload.action === "view_group_room_list") {
this.setPhase(RIGHT_PANEL_PHASES.GroupRoomList); this.setPhase(RightPanelPhases.GroupRoomList);
} else if (payload.action === "view_group_member_list") { } else if (payload.action === "view_group_member_list") {
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); this.setPhase(RightPanelPhases.GroupMemberList);
} else if (payload.action === "view_group_user") { } else if (payload.action === "view_group_user") {
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo, {member: payload.member}); this.setPhase(RightPanelPhases.GroupMemberInfo, {member: payload.member});
} }
} }
_onMembersClicked() { private onMembersClicked() {
if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) { if (this.state.phase === RightPanelPhases.GroupMemberInfo) {
// send the active phase to trigger a toggle // send the active phase to trigger a toggle
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberInfo); this.setPhase(RightPanelPhases.GroupMemberInfo);
} else { } else {
// This toggles for us, if needed // This toggles for us, if needed
this.setPhase(RIGHT_PANEL_PHASES.GroupMemberList); this.setPhase(RightPanelPhases.GroupMemberList);
} }
} }
_onRoomsClicked() { private onRoomsClicked() {
// This toggles for us, if needed // This toggles for us, if needed
this.setPhase(RIGHT_PANEL_PHASES.GroupRoomList); this.setPhase(RightPanelPhases.GroupRoomList);
} }
renderButtons() { renderButtons() {
@ -87,13 +90,13 @@ export default class GroupHeaderButtons extends HeaderButtons {
<HeaderButton key="groupMembersButton" name="groupMembersButton" <HeaderButton key="groupMembersButton" name="groupMembersButton"
title={_t('Members')} title={_t('Members')}
isHighlighted={this.isPhase(GROUP_PHASES)} isHighlighted={this.isPhase(GROUP_PHASES)}
onClick={this._onMembersClicked} onClick={this.onMembersClicked}
analytics={['Right Panel', 'Group Member List Button', 'click']} analytics={['Right Panel', 'Group Member List Button', 'click']}
/>, />,
<HeaderButton key="roomsButton" name="roomsButton" <HeaderButton key="roomsButton" name="roomsButton"
title={_t('Rooms')} title={_t('Rooms')}
isHighlighted={this.isPhase(ROOM_PHASES)} isHighlighted={this.isPhase(ROOM_PHASES)}
onClick={this._onRoomsClicked} onClick={this.onRoomsClicked}
analytics={['Right Panel', 'Group Room List Button', 'click']} analytics={['Right Panel', 'Group Room List Button', 'click']}
/>, />,
]; ];

View file

@ -19,23 +19,38 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Analytics from '../../../Analytics'; import Analytics from '../../../Analytics';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default class HeaderButton extends React.Component { interface IProps {
constructor() { // Whether this button is highlighted
super(); isHighlighted: boolean;
// click handler
onClick: () => void;
// The badge to display above the icon
badge?: React.ReactNode;
// The parameters to track the click event
analytics: string[];
// Button name
name: string;
// Button title
title: string;
}
export default class HeaderButton extends React.Component<IProps> {
constructor(props: IProps) {
super(props);
this.onClick = this.onClick.bind(this); this.onClick = this.onClick.bind(this);
} }
onClick(ev) { private onClick() {
Analytics.trackEvent(...this.props.analytics); Analytics.trackEvent(...this.props.analytics);
this.props.onClick(); this.props.onClick();
} }
render() { public render() {
const classes = classNames({ const classes = classNames({
mx_RightPanel_headerButton: true, mx_RightPanel_headerButton: true,
mx_RightPanel_headerButton_highlight: this.props.isHighlighted, mx_RightPanel_headerButton_highlight: this.props.isHighlighted,
@ -51,19 +66,3 @@ export default class HeaderButton extends React.Component {
/>; />;
} }
} }
HeaderButton.propTypes = {
// Whether this button is highlighted
isHighlighted: PropTypes.bool.isRequired,
// click handler
onClick: PropTypes.func.isRequired,
// The badge to display above the icon
badge: PropTypes.node,
// The parameters to track the click event
analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
// Button name
name: PropTypes.string.isRequired,
// Button title
title: PropTypes.string.isRequired,
};

View file

@ -1,88 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 New Vector Ltd
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 React from 'react';
import dis from '../../../dispatcher/dispatcher';
import RightPanelStore from "../../../stores/RightPanelStore";
export const HEADER_KIND_ROOM = "room";
export const HEADER_KIND_GROUP = "group";
const HEADER_KINDS = [HEADER_KIND_GROUP, HEADER_KIND_ROOM];
export default class HeaderButtons extends React.Component {
constructor(props, kind) {
super(props);
if (!HEADER_KINDS.includes(kind)) throw new Error(`Invalid header kind: ${kind}`);
const rps = RightPanelStore.getSharedInstance();
this.state = {
headerKind: kind,
phase: kind === HEADER_KIND_ROOM ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase,
};
}
componentDidMount() {
this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this));
this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses
}
componentWillUnmount() {
if (this._storeToken) this._storeToken.remove();
if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
}
onAction(payload) {
// Ignore - intended to be overridden by subclasses
}
setPhase(phase, extras) {
dis.dispatch({
action: 'set_right_panel_phase',
phase: phase,
refireParams: extras,
});
}
isPhase(phases: string | string[]) {
if (Array.isArray(phases)) {
return phases.includes(this.state.phase);
} else {
return phases === this.state.phase;
}
}
onRightPanelUpdate() {
const rps = RightPanelStore.getSharedInstance();
if (this.state.headerKind === HEADER_KIND_ROOM) {
this.setState({phase: rps.visibleRoomPanelPhase});
} else if (this.state.headerKind === HEADER_KIND_GROUP) {
this.setState({phase: rps.visibleGroupPanelPhase});
}
}
render() {
// inline style as this will be swapped around in future commits
return <div className="mx_HeaderButtons" role="tablist">
{this.renderButtons()}
</div>;
}
}

View file

@ -0,0 +1,110 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Copyright 2018 New Vector Ltd
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 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 {EventSubscription} from "fbemitter";
export enum HeaderKind {
Room = "room",
Group = "group",
}
interface IState {
headerKind: HeaderKind;
phase: RightPanelPhases;
}
interface IProps {}
export default class HeaderButtons extends React.Component<IProps, IState> {
private storeToken: EventSubscription;
private dispatcherRef: string;
constructor(props: IProps, kind: HeaderKind) {
super(props);
const rps = RightPanelStore.getSharedInstance();
this.state = {
headerKind: kind,
phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase,
};
}
public componentDidMount() {
this.storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this));
this.dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses
}
public componentWillUnmount() {
if (this.storeToken) this.storeToken.remove();
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}
protected onAction(payload) {
// Ignore - intended to be overridden by subclasses
}
public setPhase(phase: RightPanelPhases, extras?: Partial<SetRightPanelPhaseRefireParams>) {
dis.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: phase,
refireParams: extras,
});
}
public isPhase(phases: string | string[]) {
if (Array.isArray(phases)) {
return phases.includes(this.state.phase);
} else {
return phases === this.state.phase;
}
}
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});
}
}
// XXX: Make renderButtons a prop
public renderButtons(): JSX.Element[] {
// Ignore - intended to be overridden by subclasses
// Return empty fragment to satisfy the type
return [
<React.Fragment>
</React.Fragment>
];
}
public render() {
// inline style as this will be swapped around in future commits
return <div className="mx_HeaderButtons" role="tablist">
{this.renderButtons()}
</div>;
}
}

View file

@ -21,82 +21,82 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import HeaderButton from './HeaderButton'; import HeaderButton from './HeaderButton';
import HeaderButtons, {HEADER_KIND_ROOM} from './HeaderButtons'; import HeaderButtons, {HeaderKind} from './HeaderButtons';
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import {ActionPayload} from "../../../dispatcher/payloads"; import {ActionPayload} from "../../../dispatcher/payloads";
const MEMBER_PHASES = [ const MEMBER_PHASES = [
RIGHT_PANEL_PHASES.RoomMemberList, RightPanelPhases.RoomMemberList,
RIGHT_PANEL_PHASES.RoomMemberInfo, RightPanelPhases.RoomMemberInfo,
RIGHT_PANEL_PHASES.EncryptionPanel, RightPanelPhases.EncryptionPanel,
RIGHT_PANEL_PHASES.Room3pidMemberInfo, RightPanelPhases.Room3pidMemberInfo,
]; ];
export default class RoomHeaderButtons extends HeaderButtons { export default class RoomHeaderButtons extends HeaderButtons {
constructor(props) { constructor(props) {
super(props, HEADER_KIND_ROOM); super(props, HeaderKind.Room);
this._onMembersClicked = this._onMembersClicked.bind(this); this.onMembersClicked = this.onMembersClicked.bind(this);
this._onFilesClicked = this._onFilesClicked.bind(this); this.onFilesClicked = this.onFilesClicked.bind(this);
this._onNotificationsClicked = this._onNotificationsClicked.bind(this); this.onNotificationsClicked = this.onNotificationsClicked.bind(this);
} }
onAction(payload: ActionPayload) { protected onAction(payload: ActionPayload) {
super.onAction(payload); super.onAction(payload);
if (payload.action === Action.ViewUser) { if (payload.action === Action.ViewUser) {
if (payload.member) { if (payload.member) {
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo, {member: payload.member}); this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member});
} else { } else {
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); this.setPhase(RightPanelPhases.RoomMemberList);
} }
} else if (payload.action === "view_3pid_invite") { } else if (payload.action === "view_3pid_invite") {
if (payload.event) { if (payload.event) {
this.setPhase(RIGHT_PANEL_PHASES.Room3pidMemberInfo, {event: payload.event}); this.setPhase(RightPanelPhases.Room3pidMemberInfo, {event: payload.event});
} else { } else {
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); this.setPhase(RightPanelPhases.RoomMemberList);
} }
} }
} }
_onMembersClicked() { private onMembersClicked() {
if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) { if (this.state.phase === RightPanelPhases.RoomMemberInfo) {
// send the active phase to trigger a toggle // send the active phase to trigger a toggle
// XXX: we should pass refireParams here but then it won't collapse as we desire it to // XXX: we should pass refireParams here but then it won't collapse as we desire it to
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo); this.setPhase(RightPanelPhases.RoomMemberInfo);
} else { } else {
// This toggles for us, if needed // This toggles for us, if needed
this.setPhase(RIGHT_PANEL_PHASES.RoomMemberList); this.setPhase(RightPanelPhases.RoomMemberList);
} }
} }
_onFilesClicked() { private onFilesClicked() {
// This toggles for us, if needed // This toggles for us, if needed
this.setPhase(RIGHT_PANEL_PHASES.FilePanel); this.setPhase(RightPanelPhases.FilePanel);
} }
_onNotificationsClicked() { private onNotificationsClicked() {
// This toggles for us, if needed // This toggles for us, if needed
this.setPhase(RIGHT_PANEL_PHASES.NotificationPanel); this.setPhase(RightPanelPhases.NotificationPanel);
} }
renderButtons() { public renderButtons() {
return [ return [
<HeaderButton key="membersButton" name="membersButton" <HeaderButton key="membersButton" name="membersButton"
title={_t('Members')} title={_t('Members')}
isHighlighted={this.isPhase(MEMBER_PHASES)} isHighlighted={this.isPhase(MEMBER_PHASES)}
onClick={this._onMembersClicked} onClick={this.onMembersClicked}
analytics={['Right Panel', 'Member List Button', 'click']} analytics={['Right Panel', 'Member List Button', 'click']}
/>, />,
<HeaderButton key="filesButton" name="filesButton" <HeaderButton key="filesButton" name="filesButton"
title={_t('Files')} title={_t('Files')}
isHighlighted={this.isPhase(RIGHT_PANEL_PHASES.FilePanel)} isHighlighted={this.isPhase(RightPanelPhases.FilePanel)}
onClick={this._onFilesClicked} onClick={this.onFilesClicked}
analytics={['Right Panel', 'File List Button', 'click']} analytics={['Right Panel', 'File List Button', 'click']}
/>, />,
<HeaderButton key="notifsButton" name="notifsButton" <HeaderButton key="notifsButton" name="notifsButton"
title={_t('Notifications')} title={_t('Notifications')}
isHighlighted={this.isPhase(RIGHT_PANEL_PHASES.NotificationPanel)} isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
onClick={this._onNotificationsClicked} onClick={this.onNotificationsClicked}
analytics={['Right Panel', 'Notification List Button', 'click']} analytics={['Right Panel', 'Notification List Button', 'click']}
/>, />,
]; ];

View file

@ -40,7 +40,7 @@ import E2EIcon from "../rooms/E2EIcon";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import {useEventEmitter} from "../../../hooks/useEventEmitter";
import {textualPowerLevel} from '../../../Roles'; import {textualPowerLevel} from '../../../Roles';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import EncryptionPanel from "./EncryptionPanel"; import EncryptionPanel from "./EncryptionPanel";
import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification';
@ -1480,7 +1480,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
</React.Fragment>; </React.Fragment>;
}; };
const UserInfo = ({user, groupId, roomId, onClose, phase=RIGHT_PANEL_PHASES.RoomMemberInfo, ...props}) => { const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
// Load room if we are given a room id and memoize it // Load room if we are given a room id and memoize it
@ -1500,8 +1500,8 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RIGHT_PANEL_PHASES.Room
let content; let content;
switch (phase) { switch (phase) {
case RIGHT_PANEL_PHASES.RoomMemberInfo: case RightPanelPhases.RoomMemberInfo:
case RIGHT_PANEL_PHASES.GroupMemberInfo: case RightPanelPhases.GroupMemberInfo:
content = ( content = (
<BasicUserInfo <BasicUserInfo
room={room} room={room}
@ -1511,7 +1511,7 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RIGHT_PANEL_PHASES.Room
isRoomEncrypted={isRoomEncrypted} /> isRoomEncrypted={isRoomEncrypted} />
); );
break; break;
case RIGHT_PANEL_PHASES.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
classes.push("mx_UserInfo_smallAvatar"); classes.push("mx_UserInfo_smallAvatar");
content = ( content = (
<EncryptionPanel {...props} member={member} onClose={onClose} isRoomEncrypted={isRoomEncrypted} /> <EncryptionPanel {...props} member={member} onClose={onClose} isRoomEncrypted={isRoomEncrypted} />

View file

@ -15,12 +15,15 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode"; import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {ReciprocateQRCode} from "matrix-js-sdk/src/crypto/verification/QRCode";
import {SAS} from "matrix-js-sdk/src/crypto/verification/SAS";
import VerificationQRCode from "../elements/crypto/VerificationQRCode"; import VerificationQRCode from "../elements/crypto/VerificationQRCode";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
@ -36,37 +39,51 @@ import {
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import Spinner from "../elements/Spinner"; import Spinner from "../elements/Spinner";
export default class VerificationPanel extends React.PureComponent { // XXX: Should be defined in matrix-js-sdk
static propTypes = { enum VerificationPhase {
layout: PropTypes.string,
request: PropTypes.object.isRequired,
member: PropTypes.object.isRequired,
phase: PropTypes.oneOf([
PHASE_UNSENT, PHASE_UNSENT,
PHASE_REQUESTED, PHASE_REQUESTED,
PHASE_READY, PHASE_READY,
PHASE_DONE,
PHASE_STARTED, PHASE_STARTED,
PHASE_CANCELLED, PHASE_CANCELLED,
PHASE_DONE, }
]).isRequired,
onClose: PropTypes.func.isRequired,
isRoomEncrypted: PropTypes.bool,
};
constructor(props) { interface IProps {
layout: string;
request: VerificationRequest;
member: RoomMember;
phase: VerificationPhase;
onClose: () => void;
isRoomEncrypted: boolean;
inDialog: boolean;
key: number;
}
interface IState {
sasEvent?: SAS;
emojiButtonClicked?: boolean;
reciprocateButtonClicked?: boolean;
reciprocateQREvent?: ReciprocateQRCode;
}
export default class VerificationPanel extends React.PureComponent<IProps, IState> {
private hasVerifier: boolean;
constructor(props: IProps) {
super(props); super(props);
this.state = {}; this.state = {};
this._hasVerifier = false; this.hasVerifier = false;
} }
renderQRPhase() { private renderQRPhase() {
const {member, request} = this.props; const {member, request} = this.props;
const showSAS = request.otherPartySupportsMethod(verificationMethods.SAS); const showSAS: boolean = request.otherPartySupportsMethod(verificationMethods.SAS);
const showQR = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD); const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const noCommonMethodError = !showSAS && !showQR ? const noCommonMethodError: JSX.Element = !showSAS && !showQR ?
<p>{_t( <p>{_t(
"The session you are trying to verify doesn't support scanning a " + "The session you are trying to verify doesn't support scanning a " +
"QR code or emoji verification, which is what %(brand)s supports. Try " + "QR code or emoji verification, which is what %(brand)s supports. Try " +
@ -77,41 +94,41 @@ export default class VerificationPanel extends React.PureComponent {
if (this.props.layout === 'dialog') { if (this.props.layout === 'dialog') {
// HACK: This is a terrible idea. // HACK: This is a terrible idea.
let qrBlock; let qrBlockDialog: JSX.Element;
let sasBlock; let sasBlockDialog: JSX.Element;
if (showQR) { if (showQR) {
qrBlock = qrBlockDialog =
<div className='mx_VerificationPanel_QRPhase_startOption'> <div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Scan this unique code")}</p> <p>{_t("Scan this unique code")}</p>
<VerificationQRCode qrCodeData={request.qrCodeData} /> <VerificationQRCode qrCodeData={request.qrCodeData} />
</div>; </div>;
} }
if (showSAS) { if (showSAS) {
sasBlock = sasBlockDialog =
<div className='mx_VerificationPanel_QRPhase_startOption'> <div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Compare unique emoji")}</p> <p>{_t("Compare unique emoji")}</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span> <span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this._startSAS} kind='primary'> <AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
{_t("Start")} {_t("Start")}
</AccessibleButton> </AccessibleButton>
</div>; </div>;
} }
const or = qrBlock && sasBlock ? const or = qrBlockDialog && sasBlockDialog ?
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null; <div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
return ( return (
<div> <div>
{_t("Verify this session by completing one of the following:")} {_t("Verify this session by completing one of the following:")}
<div className='mx_VerificationPanel_QRPhase_startOptions'> <div className='mx_VerificationPanel_QRPhase_startOptions'>
{qrBlock} {qrBlockDialog}
{or} {or}
{sasBlock} {sasBlockDialog}
{noCommonMethodError} {noCommonMethodError}
</div> </div>
</div> </div>
); );
} }
let qrBlock; let qrBlock: JSX.Element;
if (showQR) { if (showQR) {
qrBlock = <div className="mx_UserInfo_container"> qrBlock = <div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3> <h3>{_t("Verify by scanning")}</h3>
@ -125,7 +142,7 @@ export default class VerificationPanel extends React.PureComponent {
</div>; </div>;
} }
let sasBlock; let sasBlock: JSX.Element;
if (showSAS) { if (showSAS) {
const disabled = this.state.emojiButtonClicked; const disabled = this.state.emojiButtonClicked;
const sasLabel = showQR ? const sasLabel = showQR ?
@ -140,7 +157,7 @@ export default class VerificationPanel extends React.PureComponent {
disabled={disabled} disabled={disabled}
kind="primary" kind="primary"
className="mx_UserInfo_wideButton mx_VerificationPanel_verifyByEmojiButton" className="mx_UserInfo_wideButton mx_VerificationPanel_verifyByEmojiButton"
onClick={this._startSAS} onClick={this.startSAS}
> >
{_t("Verify by emoji")} {_t("Verify by emoji")}
</AccessibleButton> </AccessibleButton>
@ -159,22 +176,22 @@ export default class VerificationPanel extends React.PureComponent {
</React.Fragment>; </React.Fragment>;
} }
_onReciprocateYesClick = () => { private onReciprocateYesClick = () => {
this.setState({reciprocateButtonClicked: true}); this.setState({reciprocateButtonClicked: true});
this.state.reciprocateQREvent.confirm(); this.state.reciprocateQREvent.confirm();
}; };
_onReciprocateNoClick = () => { private onReciprocateNoClick = () => {
this.setState({reciprocateButtonClicked: true}); this.setState({reciprocateButtonClicked: true});
this.state.reciprocateQREvent.cancel(); this.state.reciprocateQREvent.cancel();
}; };
_getDevice() { private getDevice() {
const deviceId = this.props.request && this.props.request.channel.deviceId; const deviceId = this.props.request && this.props.request.channel.deviceId;
return MatrixClientPeg.get().getStoredDevice(MatrixClientPeg.get().getUserId(), deviceId); return MatrixClientPeg.get().getStoredDevice(MatrixClientPeg.get().getUserId(), deviceId);
} }
renderQRReciprocatePhase() { private renderQRReciprocatePhase() {
const {member, request} = this.props; const {member, request} = this.props;
let Button; let Button;
// a bit of a hack, but the FormButton should only be used in the right panel // a bit of a hack, but the FormButton should only be used in the right panel
@ -189,7 +206,7 @@ export default class VerificationPanel extends React.PureComponent {
_t("Almost there! Is %(displayName)s showing the same shield?", { _t("Almost there! Is %(displayName)s showing the same shield?", {
displayName: member.displayName || member.name || member.userId, displayName: member.displayName || member.name || member.userId,
}); });
let body; let body: JSX.Element;
if (this.state.reciprocateQREvent) { if (this.state.reciprocateQREvent) {
// riot web doesn't support scanning yet, so assume here we're the client being scanned. // riot web doesn't support scanning yet, so assume here we're the client being scanned.
// //
@ -202,11 +219,11 @@ export default class VerificationPanel extends React.PureComponent {
<Button <Button
label={_t("No")} kind="danger" label={_t("No")} kind="danger"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this._onReciprocateNoClick}>{_t("No")}</Button> onClick={this.onReciprocateNoClick}>{_t("No")}</Button>
<Button <Button
label={_t("Yes")} kind="primary" label={_t("Yes")} kind="primary"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this._onReciprocateYesClick}>{_t("Yes")}</Button> onClick={this.onReciprocateYesClick}>{_t("Yes")}</Button>
</div> </div>
</React.Fragment>; </React.Fragment>;
} else { } else {
@ -218,10 +235,10 @@ export default class VerificationPanel extends React.PureComponent {
</div>; </div>;
} }
renderVerifiedPhase() { private renderVerifiedPhase() {
const {member, request} = this.props; const {member, request} = this.props;
let text; let text: string;
if (!request.isSelfVerification) { if (!request.isSelfVerification) {
if (this.props.isRoomEncrypted) { if (this.props.isRoomEncrypted) {
text = _t("Verify all users in a room to ensure it's secure."); text = _t("Verify all users in a room to ensure it's secure.");
@ -230,9 +247,9 @@ export default class VerificationPanel extends React.PureComponent {
} }
} }
let description; let description: string;
if (request.isSelfVerification) { if (request.isSelfVerification) {
const device = this._getDevice(); const device = this.getDevice();
if (!device) { if (!device) {
// This can happen if the device is logged out while we're still showing verification // This can happen if the device is logged out while we're still showing verification
// UI for it. // UI for it.
@ -264,19 +281,19 @@ export default class VerificationPanel extends React.PureComponent {
); );
} }
renderCancelledPhase() { private renderCancelledPhase() {
const {member, request} = this.props; const {member, request} = this.props;
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let startAgainInstruction; let startAgainInstruction: string;
if (request.isSelfVerification) { if (request.isSelfVerification) {
startAgainInstruction = _t("Start verification again from the notification."); startAgainInstruction = _t("Start verification again from the notification.");
} else { } else {
startAgainInstruction = _t("Start verification again from their profile."); startAgainInstruction = _t("Start verification again from their profile.");
} }
let text; let text: string;
if (request.cancellationCode === "m.timeout") { if (request.cancellationCode === "m.timeout") {
text = _t("Verification timed out.") + ` ${startAgainInstruction}`; text = _t("Verification timed out.") + ` ${startAgainInstruction}`;
} else if (request.cancellingUserId === request.otherUserId) { } else if (request.cancellingUserId === request.otherUserId) {
@ -304,7 +321,7 @@ export default class VerificationPanel extends React.PureComponent {
); );
} }
render() { public render() {
const {member, phase, request} = this.props; const {member, phase, request} = this.props;
const displayName = member.displayName || member.name || member.userId; const displayName = member.displayName || member.name || member.userId;
@ -321,10 +338,10 @@ export default class VerificationPanel extends React.PureComponent {
const emojis = this.state.sasEvent ? const emojis = this.state.sasEvent ?
<VerificationShowSas <VerificationShowSas
displayName={displayName} displayName={displayName}
device={this._getDevice()} device={this.getDevice()}
sas={this.state.sasEvent.sas} sas={this.state.sasEvent.sas}
onCancel={this._onSasMismatchesClick} onCancel={this.onSasMismatchesClick}
onDone={this._onSasMatchesClick} onDone={this.onSasMatchesClick}
inDialog={this.props.inDialog} inDialog={this.props.inDialog}
isSelf={request.isSelfVerification} isSelf={request.isSelfVerification}
/> : <Spinner />; /> : <Spinner />;
@ -345,7 +362,7 @@ export default class VerificationPanel extends React.PureComponent {
return null; return null;
} }
_startSAS = async () => { private startSAS = async () => {
this.setState({emojiButtonClicked: true}); this.setState({emojiButtonClicked: true});
const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS);
try { try {
@ -355,31 +372,31 @@ export default class VerificationPanel extends React.PureComponent {
} }
}; };
_onSasMatchesClick = () => { private onSasMatchesClick = () => {
this.state.sasEvent.confirm(); this.state.sasEvent.confirm();
}; };
_onSasMismatchesClick = () => { private onSasMismatchesClick = () => {
this.state.sasEvent.mismatch(); this.state.sasEvent.mismatch();
}; };
_updateVerifierState = () => { private updateVerifierState = () => {
const {request} = this.props; const {request} = this.props;
const {sasEvent, reciprocateQREvent} = request.verifier; const {sasEvent, reciprocateQREvent} = request.verifier;
request.verifier.off('show_sas', this._updateVerifierState); request.verifier.off('show_sas', this.updateVerifierState);
request.verifier.off('show_reciprocate_qr', this._updateVerifierState); request.verifier.off('show_reciprocate_qr', this.updateVerifierState);
this.setState({sasEvent, reciprocateQREvent}); this.setState({sasEvent, reciprocateQREvent});
}; };
_onRequestChange = async () => { private onRequestChange = async () => {
const {request} = this.props; const {request} = this.props;
const hadVerifier = this._hasVerifier; const hadVerifier = this.hasVerifier;
this._hasVerifier = !!request.verifier; this.hasVerifier = !!request.verifier;
if (!hadVerifier && this._hasVerifier) { if (!hadVerifier && this.hasVerifier) {
request.verifier.on('show_sas', this._updateVerifierState); request.verifier.on('show_sas', this.updateVerifierState);
request.verifier.on('show_reciprocate_qr', this._updateVerifierState); request.verifier.on('show_reciprocate_qr', this.updateVerifierState);
try { try {
// on the requester side, this is also awaited in _startSAS, // on the requester side, this is also awaited in startSAS,
// but that's ok as verify should return the same promise. // but that's ok as verify should return the same promise.
await request.verifier.verify(); await request.verifier.verify();
} catch (err) { } catch (err) {
@ -388,23 +405,22 @@ export default class VerificationPanel extends React.PureComponent {
} }
}; };
componentDidMount() { public componentDidMount() {
const {request} = this.props; const {request} = this.props;
request.on("change", this._onRequestChange); request.on("change", this.onRequestChange);
if (request.verifier) { if (request.verifier) {
const {request} = this.props;
const {sasEvent, reciprocateQREvent} = request.verifier; const {sasEvent, reciprocateQREvent} = request.verifier;
this.setState({sasEvent, reciprocateQREvent}); this.setState({sasEvent, reciprocateQREvent});
} }
this._onRequestChange(); this.onRequestChange();
} }
componentWillUnmount() { public componentWillUnmount() {
const {request} = this.props; const {request} = this.props;
if (request.verifier) { if (request.verifier) {
request.verifier.off('show_sas', this._updateVerifierState); request.verifier.off('show_sas', this.updateVerifierState);
request.verifier.off('show_reciprocate_qr', this._updateVerifierState); request.verifier.off('show_reciprocate_qr', this.updateVerifierState);
} }
request.off("change", this._onRequestChange); request.off("change", this.onRequestChange);
} }
} }

View file

@ -28,6 +28,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import {ContextMenu} from "../../structures/ContextMenu"; import {ContextMenu} from "../../structures/ContextMenu";
import {WidgetType} from "../../../widgets/WidgetType"; import {WidgetType} from "../../../widgets/WidgetType";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {Action} from "../../../dispatcher/actions";
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000). // This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
// We sit in a context menu, so this should be given to the context menu. // We sit in a context menu, so this should be given to the context menu.
@ -181,7 +182,7 @@ export default class Stickerpicker extends React.Component {
case "stickerpicker_close": case "stickerpicker_close":
this.setState({showStickers: false}); this.setState({showStickers: false});
break; break;
case "after_right_panel_phase_change": case Action.AfterRightPanelPhaseChange:
case "show_left_panel": case "show_left_panel":
case "hide_left_panel": case "hide_left_panel":
this.setState({showStickers: false}); this.setState({showStickers: false});

View file

@ -19,7 +19,8 @@ import React from "react";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"
import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import ToastStore from "../../../stores/ToastStore"; import ToastStore from "../../../stores/ToastStore";
@ -27,6 +28,7 @@ import Modal from "../../../Modal";
import GenericToast from "./GenericToast"; import GenericToast from "./GenericToast";
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo"; import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo";
import {Action} from "../../../dispatcher/actions";
interface IProps { interface IProps {
toastKey: string; toastKey: string;
@ -104,9 +106,9 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
room_id: request.channel.roomId, room_id: request.channel.roomId,
should_peek: false, should_peek: false,
}); });
dis.dispatch({ dis.dispatch<SetRightPanelPhasePayload>({
action: "set_right_panel_phase", action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
refireParams: { refireParams: {
verificationRequest: request, verificationRequest: request,
member: cli.getUser(request.otherUserId), member: cli.getUser(request.otherUserId),

View file

@ -79,4 +79,19 @@ export enum Action {
* Changes room based on room list order and payload parameters. Should be used with ViewRoomDeltaPayload. * Changes room based on room list order and payload parameters. Should be used with ViewRoomDeltaPayload.
*/ */
ViewRoomDelta = "view_room_delta", ViewRoomDelta = "view_room_delta",
/**
* Sets the phase for the right panel. Should be used with SetRightPanelPhasePayload.
*/
SetRightPanelPhase = "set_right_panel_phase",
/**
* Toggles the right panel. Should be used with ToggleRightPanelPayload.
*/
ToggleRightPanel = "toggle_right_panel",
/**
* Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload.
*/
AfterRightPanelPhaseChange = "after_right_panel_phase_change",
} }

View file

@ -0,0 +1,30 @@
/*
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 { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { SetRightPanelPhaseRefireParams } from "./SetRightPanelPhasePayload";
import { ActionPayload } from "../payloads";
import { Action } from "../actions";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
interface AfterRightPanelPhaseChangeAction extends ActionPayload {
action: Action.AfterRightPanelPhaseChange;
phase: RightPanelPhases;
verificationRequestPromise?: Promise<VerificationRequest>;
}
export type AfterRightPanelPhaseChangePayload
= AfterRightPanelPhaseChangeAction & SetRightPanelPhaseRefireParams;

View file

@ -0,0 +1,37 @@
/*
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 { RoomMember } from "matrix-js-sdk/src/models/room-member";
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;
}
export interface SetRightPanelPhaseRefireParams {
member?: RoomMember;
verificationRequest?: VerificationRequest;
groupId?: string;
groupRoomId?: string;
// XXX: The type for event should 'view_3pid_invite' action's payload
event?: any;
}

View file

@ -0,0 +1,27 @@
/*
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

@ -28,7 +28,7 @@ import CustomStatusController from "./controllers/CustomStatusController";
import ThemeController from './controllers/ThemeController'; import ThemeController from './controllers/ThemeController';
import PushToMatrixClientController from './controllers/PushToMatrixClientController'; import PushToMatrixClientController from './controllers/PushToMatrixClientController';
import ReloadOnChangeController from "./controllers/ReloadOnChangeController"; import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
import {RIGHT_PANEL_PHASES} from "../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../stores/RightPanelStorePhases";
import FontSizeController from './controllers/FontSizeController'; import FontSizeController from './controllers/FontSizeController';
import SystemFontController from './controllers/SystemFontController'; import SystemFontController from './controllers/SystemFontController';
import UseSystemFontController from './controllers/UseSystemFontController'; import UseSystemFontController from './controllers/UseSystemFontController';
@ -534,11 +534,11 @@ export const SETTINGS = {
}, },
"lastRightPanelPhaseForRoom": { "lastRightPanelPhaseForRoom": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: RIGHT_PANEL_PHASES.RoomMemberInfo, default: RightPanelPhases.RoomMemberInfo,
}, },
"lastRightPanelPhaseForGroup": { "lastRightPanelPhaseForGroup": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: RIGHT_PANEL_PHASES.GroupMemberList, default: RightPanelPhases.GroupMemberList,
}, },
"enableEventIndexing": { "enableEventIndexing": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,

View file

@ -18,161 +18,177 @@ import dis from '../dispatcher/dispatcher';
import {pendingVerificationRequestForUser} from '../verification'; import {pendingVerificationRequestForUser} from '../verification';
import {Store} from 'flux/utils'; import {Store} from 'flux/utils';
import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases"; import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases";
import {ActionPayload} from "../dispatcher/payloads";
import {Action} from '../dispatcher/actions';
const INITIAL_STATE = { interface RightPanelStoreState {
// Whether or not to show the right panel at all. We split out rooms and groups // 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. // because they're different flows for the user to follow.
showRoomPanel: SettingsStore.getValue("showRightPanelInRoom"), showRoomPanel: boolean;
showGroupPanel: SettingsStore.getValue("showRightPanelInGroup"), showGroupPanel: boolean;
// The last phase (screen) the right panel was showing // The last phase (screen) the right panel was showing
lastRoomPhase: SettingsStore.getValue("lastRightPanelPhaseForRoom"), lastRoomPhase: RightPanelPhases;
lastGroupPhase: SettingsStore.getValue("lastRightPanelPhaseForGroup"), lastGroupPhase: RightPanelPhases;
// Extra information about the last phase // 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: {}, lastRoomPhaseParams: {},
}; };
const GROUP_PHASES = Object.keys(RIGHT_PANEL_PHASES).filter(k => k.startsWith("Group")); const GROUP_PHASES = [
RightPanelPhases.GroupMemberList,
RightPanelPhases.GroupRoomList,
RightPanelPhases.GroupRoomInfo,
RightPanelPhases.GroupMemberInfo,
];
const MEMBER_INFO_PHASES = [ const MEMBER_INFO_PHASES = [
RIGHT_PANEL_PHASES.RoomMemberInfo, RightPanelPhases.RoomMemberInfo,
RIGHT_PANEL_PHASES.Room3pidMemberInfo, RightPanelPhases.Room3pidMemberInfo,
RIGHT_PANEL_PHASES.EncryptionPanel, RightPanelPhases.EncryptionPanel,
]; ];
/** /**
* A class for tracking the state of the right panel between layouts and * A class for tracking the state of the right panel between layouts and
* sessions. * sessions.
*/ */
export default class RightPanelStore extends Store { export default class RightPanelStore extends Store<ActionPayload> {
static _instance; private static instance: RightPanelStore;
private state: RightPanelStoreState;
constructor() { constructor() {
super(dis); super(dis);
// Initialise state // Initialise state
this._state = INITIAL_STATE; this.state = INITIAL_STATE;
} }
get isOpenForRoom(): boolean { get isOpenForRoom(): boolean {
return this._state.showRoomPanel; return this.state.showRoomPanel;
} }
get isOpenForGroup(): boolean { get isOpenForGroup(): boolean {
return this._state.showGroupPanel; return this.state.showGroupPanel;
} }
get roomPanelPhase(): string { get roomPanelPhase(): RightPanelPhases {
return this._state.lastRoomPhase; return this.state.lastRoomPhase;
} }
get groupPanelPhase(): string { get groupPanelPhase(): RightPanelPhases {
return this._state.lastGroupPhase; return this.state.lastGroupPhase;
} }
get visibleRoomPanelPhase(): string { get visibleRoomPanelPhase(): RightPanelPhases {
return this.isOpenForRoom ? this.roomPanelPhase : null; return this.isOpenForRoom ? this.roomPanelPhase : null;
} }
get visibleGroupPanelPhase(): string { get visibleGroupPanelPhase(): RightPanelPhases {
return this.isOpenForGroup ? this.groupPanelPhase : null; return this.isOpenForGroup ? this.groupPanelPhase : null;
} }
get roomPanelPhaseParams(): any { get roomPanelPhaseParams(): any {
return this._state.lastRoomPhaseParams || {}; return this.state.lastRoomPhaseParams || {};
} }
_setState(newState) { private setState(newState: Partial<RightPanelStoreState>) {
this._state = Object.assign(this._state, newState); this.state = Object.assign(this.state, newState);
SettingsStore.setValue( SettingsStore.setValue(
"showRightPanelInRoom", "showRightPanelInRoom",
null, null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
this._state.showRoomPanel, this.state.showRoomPanel,
); );
SettingsStore.setValue( SettingsStore.setValue(
"showRightPanelInGroup", "showRightPanelInGroup",
null, null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
this._state.showGroupPanel, this.state.showGroupPanel,
); );
if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this._state.lastRoomPhase)) { if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastRoomPhase)) {
SettingsStore.setValue( SettingsStore.setValue(
"lastRightPanelPhaseForRoom", "lastRightPanelPhaseForRoom",
null, null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
this._state.lastRoomPhase, this.state.lastRoomPhase,
); );
} }
if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this._state.lastGroupPhase)) { if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastGroupPhase)) {
SettingsStore.setValue( SettingsStore.setValue(
"lastRightPanelPhaseForGroup", "lastRightPanelPhaseForGroup",
null, null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
this._state.lastGroupPhase, this.state.lastGroupPhase,
); );
} }
this.__emitChange(); this.__emitChange();
} }
__onDispatch(payload) { __onDispatch(payload: ActionPayload) {
switch (payload.action) { switch (payload.action) {
case 'view_room': case 'view_room':
case 'view_group': case 'view_group':
// Reset to the member list if we're viewing member info // Reset to the member list if we're viewing member info
if (MEMBER_INFO_PHASES.includes(this._state.lastRoomPhase)) { if (MEMBER_INFO_PHASES.includes(this.state.lastRoomPhase)) {
this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}}); this.setState({lastRoomPhase: RightPanelPhases.RoomMemberList, lastRoomPhaseParams: {}});
} }
// Do the same for groups // Do the same for groups
if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) { if (this.state.lastGroupPhase === RightPanelPhases.GroupMemberInfo) {
this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList}); this.setState({lastGroupPhase: RightPanelPhases.GroupMemberList});
} }
break; break;
case 'set_right_panel_phase': { case Action.SetRightPanelPhase: {
let targetPhase = payload.phase; let targetPhase = payload.phase;
let refireParams = payload.refireParams; let refireParams = payload.refireParams;
// redirect to EncryptionPanel if there is an ongoing verification request // redirect to EncryptionPanel if there is an ongoing verification request
if (targetPhase === RIGHT_PANEL_PHASES.RoomMemberInfo && payload.refireParams) { if (targetPhase === RightPanelPhases.RoomMemberInfo && payload.refireParams) {
const {member} = payload.refireParams; const {member} = payload.refireParams;
const pendingRequest = pendingVerificationRequestForUser(member); const pendingRequest = pendingVerificationRequestForUser(member);
if (pendingRequest) { if (pendingRequest) {
targetPhase = RIGHT_PANEL_PHASES.EncryptionPanel; targetPhase = RightPanelPhases.EncryptionPanel;
refireParams = { refireParams = {
verificationRequest: pendingRequest, verificationRequest: pendingRequest,
member, member,
}; };
} }
} }
if (!RIGHT_PANEL_PHASES[targetPhase]) { if (!RightPanelPhases[targetPhase]) {
console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`);
return; return;
} }
if (GROUP_PHASES.includes(targetPhase)) { if (GROUP_PHASES.includes(targetPhase)) {
if (targetPhase === this._state.lastGroupPhase) { if (targetPhase === this.state.lastGroupPhase) {
this._setState({ this.setState({
showGroupPanel: !this._state.showGroupPanel, showGroupPanel: !this.state.showGroupPanel,
}); });
} else { } else {
this._setState({ this.setState({
lastGroupPhase: targetPhase, lastGroupPhase: targetPhase,
showGroupPanel: true, showGroupPanel: true,
}); });
} }
} else { } else {
if (targetPhase === this._state.lastRoomPhase && !refireParams) { if (targetPhase === this.state.lastRoomPhase && !refireParams) {
this._setState({ this.setState({
showRoomPanel: !this._state.showRoomPanel, showRoomPanel: !this.state.showRoomPanel,
}); });
} else { } else {
this._setState({ this.setState({
lastRoomPhase: targetPhase, lastRoomPhase: targetPhase,
showRoomPanel: true, showRoomPanel: true,
lastRoomPhaseParams: refireParams || {}, lastRoomPhaseParams: refireParams || {},
@ -182,27 +198,27 @@ export default class RightPanelStore extends Store {
// Let things like the member info panel actually open to the right member. // Let things like the member info panel actually open to the right member.
dis.dispatch({ dis.dispatch({
action: 'after_right_panel_phase_change', action: Action.AfterRightPanelPhaseChange,
phase: targetPhase, phase: targetPhase,
...(refireParams || {}), ...(refireParams || {}),
}); });
break; break;
} }
case 'toggle_right_panel': case Action.ToggleRightPanel:
if (payload.type === "room") { if (payload.type === "room") {
this._setState({ showRoomPanel: !this._state.showRoomPanel }); this.setState({ showRoomPanel: !this.state.showRoomPanel });
} else { // group } else { // group
this._setState({ showGroupPanel: !this._state.showGroupPanel }); this.setState({ showGroupPanel: !this.state.showGroupPanel });
} }
break; break;
} }
} }
static getSharedInstance(): RightPanelStore { static getSharedInstance(): RightPanelStore {
if (!RightPanelStore._instance) { if (!RightPanelStore.instance) {
RightPanelStore._instance = new RightPanelStore(); RightPanelStore.instance = new RightPanelStore();
} }
return RightPanelStore._instance; return RightPanelStore.instance;
} }
} }

View file

@ -15,28 +15,28 @@ limitations under the License.
*/ */
// 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 const RIGHT_PANEL_PHASES = Object.freeze({ export enum RightPanelPhases {
// Room stuff // Room stuff
RoomMemberList: 'RoomMemberList', RoomMemberList = 'RoomMemberList',
FilePanel: 'FilePanel', FilePanel = 'FilePanel',
NotificationPanel: 'NotificationPanel', NotificationPanel = 'NotificationPanel',
RoomMemberInfo: 'RoomMemberInfo', RoomMemberInfo = 'RoomMemberInfo',
EncryptionPanel: 'EncryptionPanel', EncryptionPanel = 'EncryptionPanel',
Room3pidMemberInfo: 'Room3pidMemberInfo', Room3pidMemberInfo = 'Room3pidMemberInfo',
// Group stuff // Group stuff
GroupMemberList: 'GroupMemberList', GroupMemberList = 'GroupMemberList',
GroupRoomList: 'GroupRoomList', GroupRoomList = 'GroupRoomList',
GroupRoomInfo: 'GroupRoomInfo', GroupRoomInfo = 'GroupRoomInfo',
GroupMemberInfo: 'GroupMemberInfo', GroupMemberInfo = 'GroupMemberInfo',
}); }
// 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 = [
RIGHT_PANEL_PHASES.NotificationPanel, RightPanelPhases.NotificationPanel,
RIGHT_PANEL_PHASES.FilePanel, RightPanelPhases.FilePanel,
RIGHT_PANEL_PHASES.RoomMemberList, RightPanelPhases.RoomMemberList,
RIGHT_PANEL_PHASES.GroupMemberList, RightPanelPhases.GroupMemberList,
RIGHT_PANEL_PHASES.GroupRoomList, RightPanelPhases.GroupRoomList,
]; ];

View file

@ -19,10 +19,11 @@ import dis from "./dispatcher/dispatcher";
import Modal from './Modal'; import Modal from './Modal';
import * as sdk from './index'; import * as sdk from './index';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import {RIGHT_PANEL_PHASES} from "./stores/RightPanelStorePhases"; import {RightPanelPhases} from "./stores/RightPanelStorePhases";
import {findDMForUser} from './createRoom'; import {findDMForUser} from './createRoom';
import {accessSecretStorage} from './CrossSigningManager'; import {accessSecretStorage} from './CrossSigningManager';
import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {Action} from './dispatcher/actions';
async function enable4SIfNeeded() { async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -91,8 +92,8 @@ export async function verifyDevice(user, device) {
verificationMethods.SAS, verificationMethods.SAS,
); );
dis.dispatch({ dis.dispatch({
action: "set_right_panel_phase", action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
refireParams: {member: user, verificationRequestPromise}, refireParams: {member: user, verificationRequestPromise},
}); });
} else if (action === "legacy") { } else if (action === "legacy") {
@ -120,8 +121,8 @@ export async function legacyVerifyUser(user) {
} }
const verificationRequestPromise = cli.requestVerification(user.userId); const verificationRequestPromise = cli.requestVerification(user.userId);
dis.dispatch({ dis.dispatch({
action: "set_right_panel_phase", action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
refireParams: {member: user, verificationRequestPromise}, refireParams: {member: user, verificationRequestPromise},
}); });
} }
@ -132,8 +133,8 @@ export async function verifyUser(user) {
} }
const existingRequest = pendingVerificationRequestForUser(user); const existingRequest = pendingVerificationRequestForUser(user);
dis.dispatch({ dis.dispatch({
action: "set_right_panel_phase", action: Action.SetRightPanelPhase,
phase: RIGHT_PANEL_PHASES.EncryptionPanel, phase: RightPanelPhases.EncryptionPanel,
refireParams: { refireParams: {
member: user, member: user,
verificationRequest: existingRequest, verificationRequest: existingRequest,