diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index fd157b2b4c..d81da80e8d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -41,6 +41,8 @@ import { parseFragment as parseHtml } from "parse5"; import sendBugReport from "./rageshake/submit-rageshake"; import SdkConfig from "./SdkConfig"; import { ensureDMExists } from "./createRoom"; +import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; +import { Action } from "./dispatcher/actions"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -943,8 +945,10 @@ export const Commands = [ } const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); - dis.dispatch({ - action: 'view_user', + dis.dispatch({ + action: Action.ViewUser, + // XXX: We should be using a real member object and not assuming what the + // receiver wants. member: member || {userId}, }); return success(); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 973d301420..d368b2c23a 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -17,12 +17,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {createRef} from 'react'; -import {InvalidStoreError} from "matrix-js-sdk/src/errors"; -import {RoomMember} from "matrix-js-sdk/src/models/room-member"; -import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import React, { createRef } from 'react'; +import { InvalidStoreError } from "matrix-js-sdk/src/errors"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { isCryptoAvailable } from 'matrix-js-sdk/src/crypto'; - // focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss import 'focus-visible'; // what-input helps improve keyboard accessibility @@ -30,7 +29,7 @@ import 'what-input'; import Analytics from "../../Analytics"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; -import {MatrixClientPeg} from "../../MatrixClientPeg"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; import * as RoomListSorter from "../../RoomListSorter"; @@ -40,7 +39,7 @@ import Notifier from '../../Notifier'; import Modal from "../../Modal"; import Tinter from "../../Tinter"; import * as sdk from '../../index'; -import { showStartChatInviteDialog, showRoomInviteDialog } from '../../RoomInvite'; +import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite'; import * as Rooms from '../../Rooms'; import linkifyMatrix from "../../linkify-matrix"; import * as Lifecycle from '../../Lifecycle'; @@ -52,21 +51,22 @@ import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; -import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; +import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; import { startAnyRegistrationFlow } from "../../Registration.js"; import { messageForSyncError } from '../../utils/ErrorUtils'; import ResizeNotifier from "../../utils/ResizeNotifier"; -import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils"; -import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; +import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils"; import DMRoomMap from '../../utils/DMRoomMap'; import { countRoomsWithNotif } from '../../RoomNotifs'; import { ThemeWatcher } from "../../theme"; import { storeRoomAliasInCache } from '../../RoomAliasCache'; -import {defer, IDeferred} from "../../utils/promise"; +import { defer, IDeferred } from "../../utils/promise"; import ToastStore from "../../stores/ToastStore"; import * as StorageManager from "../../utils/StorageManager"; import type LoggedInViewType from "./LoggedInView"; +import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload"; +import { Action } from "../../dispatcher/actions"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1755,8 +1755,8 @@ export default class MatrixChat extends React.PureComponent { const member = new RoomMember(null, userId); if (!member) { return; } - dis.dispatch({ - action: 'view_user', + dis.dispatch({ + action: Action.ViewUser, member: member, }); } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index c6c330a202..56cc92a8f8 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -30,6 +30,7 @@ import SettingsStore from "../../settings/SettingsStore"; import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases"; import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; +import {Action} from "../../dispatcher/actions"; export default class RightPanel extends React.Component { static get propTypes() { @@ -237,7 +238,7 @@ export default class RightPanel extends React.Component { // within a room, so go back to the member panel if we were in the encryption panel, // or the member list if we were in the member panel... phew. dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? this.state.member : null, }); @@ -266,7 +267,7 @@ export default class RightPanel extends React.Component { if (SettingsStore.getValue("feature_cross_signing")) { const onClose = () => { dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: null, }); }; diff --git a/src/components/views/avatars/MemberAvatar.js b/src/components/views/avatars/MemberAvatar.js index 89314cfef7..d28d80e62a 100644 --- a/src/components/views/avatars/MemberAvatar.js +++ b/src/components/views/avatars/MemberAvatar.js @@ -21,6 +21,7 @@ import createReactClass from 'create-react-class'; import * as Avatar from '../../../Avatar'; import * as sdk from "../../../index"; import dis from "../../../dispatcher/dispatcher"; +import {Action} from "../../../dispatcher/actions"; export default createReactClass({ displayName: 'MemberAvatar', @@ -33,7 +34,7 @@ export default createReactClass({ resizeMethod: PropTypes.string, // The onClick to give the avatar onClick: PropTypes.func, - // Whether the onClick of the avatar should be overriden to dispatch 'view_user' + // Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser` viewUserOnClick: PropTypes.bool, title: PropTypes.string, }, @@ -85,7 +86,7 @@ export default createReactClass({ if (viewUserOnClick) { onClick = () => { dis.dispatch({ - action: 'view_user', + action: Action.ViewUser, member: this.props.member, }); }; diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index 3e4ddb99e8..03a1aeed85 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -26,6 +26,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import FlairStore from "../../../stores/FlairStore"; import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {Action} from "../../../dispatcher/actions"; // For URLs of matrix.to links in the timeline which have been reformatted by // HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`) @@ -191,7 +192,7 @@ const Pill = createReactClass({ onUserPillClicked: function() { dis.dispatch({ - action: 'view_user', + action: Action.ViewUser, member: this.state.member, }); }, diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index 0d08771676..2582cab573 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -28,6 +28,7 @@ import GroupStore from '../../../stores/GroupStore'; import AccessibleButton from '../elements/AccessibleButton'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {Action} from "../../../dispatcher/actions"; export default createReactClass({ displayName: 'GroupMemberInfo', @@ -103,7 +104,7 @@ export default createReactClass({ ).then(() => { // return to the user list dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: null, }); }).catch((e) => { @@ -124,7 +125,7 @@ export default createReactClass({ _onCancel: function(e) { // Go back to the user list dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: null, }); }, diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index f164b6c578..33d9325433 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -23,6 +23,8 @@ import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HEADER_KIND_GROUP} from './HeaderButtons'; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; +import {Action} from "../../../dispatcher/actions"; +import {ActionPayload} from "../../../dispatcher/payloads"; const GROUP_PHASES = [ RIGHT_PANEL_PHASES.GroupMemberInfo, @@ -40,10 +42,10 @@ export default class GroupHeaderButtons extends HeaderButtons { this._onRoomsClicked = this._onRoomsClicked.bind(this); } - onAction(payload) { + onAction(payload: ActionPayload) { super.onAction(payload); - if (payload.action === "view_user") { + if (payload.action === Action.ViewUser) { if (payload.member) { this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo, {member: payload.member}); } else { diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js index bad89e2dbe..838727981d 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.js +++ b/src/components/views/right_panel/RoomHeaderButtons.js @@ -23,6 +23,8 @@ import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HEADER_KIND_ROOM} from './HeaderButtons'; import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; +import {Action} from "../../../dispatcher/actions"; +import {ActionPayload} from "../../../dispatcher/payloads"; const MEMBER_PHASES = [ RIGHT_PANEL_PHASES.RoomMemberList, @@ -39,9 +41,9 @@ export default class RoomHeaderButtons extends HeaderButtons { this._onNotificationsClicked = this._onNotificationsClicked.bind(this); } - onAction(payload) { + onAction(payload: ActionPayload) { super.onAction(payload); - if (payload.action === "view_user") { + if (payload.action === Action.ViewUser) { if (payload.member) { this.setPhase(RIGHT_PANEL_PHASES.RoomMemberInfo, {member: payload.member}); } else { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index daf1a9490d..0392746c94 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -44,6 +44,7 @@ import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases"; import EncryptionPanel from "./EncryptionPanel"; import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; +import {Action} from "../../../dispatcher/actions"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -841,7 +842,7 @@ const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating, cli.removeUserFromGroup(groupId, groupMember.userId).then(() => { // return to the user list dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: null, }); }).catch((e) => { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 5d8e03a050..ed6c4ad748 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -48,6 +48,7 @@ import E2EIcon from "./E2EIcon"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {Action} from "../../../dispatcher/actions"; export default createReactClass({ displayName: 'MemberInfo', @@ -724,7 +725,7 @@ export default createReactClass({ onCancel: function(e) { dis.dispatch({ - action: "view_user", + action: Action.ViewUser, member: null, }); }, diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 6842e27ed3..2d290564c3 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -23,6 +23,7 @@ import * as sdk from "../../../index"; import dis from "../../../dispatcher/dispatcher"; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import {Action} from "../../../dispatcher/actions"; export default createReactClass({ displayName: 'MemberTile', @@ -185,7 +186,7 @@ export default createReactClass({ onClick: function(e) { dis.dispatch({ - action: 'view_user', + action: Action.ViewUser, member: this.props.member, }); }, diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index ff1650e4e5..880e7e62cd 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -19,5 +19,19 @@ export type DispatcherAction = Action | string; export enum Action { // TODO: Populate with actual actions + // This is lazily generated as it also includes fixing a bunch of references. Work + // that we don't really want to take on in a giant chunk. We should always define + // new actions here, and ideally when we touch existing ones we take some time to + // define them correctly. + + // When defining a new action, please use lower_scored_case with an optional class + // name prefix. For example, `RoomListStore.view_room` or `view_user_settings`. + // New definitions should also receive an accompanying interface in the payloads + // directory. + + /** + * View a user's profile. Should be used with a ViewUserPayload. + */ + ViewUser = "view_user", } diff --git a/src/dispatcher/dispatcher.ts b/src/dispatcher/dispatcher.ts index 6ef165c485..8330e5cd19 100644 --- a/src/dispatcher/dispatcher.ts +++ b/src/dispatcher/dispatcher.ts @@ -32,7 +32,7 @@ export class MatrixDispatcher extends Dispatcher { * an operation that the browser requires user interaction * for. Default false (async). */ - dispatch(payload: ActionPayload, sync = false) { + dispatch(payload: T, sync = false) { if (payload instanceof AsyncActionPayload) { payload.fn((action: ActionPayload) => { this.dispatch(action, sync); diff --git a/src/dispatcher/payloads/ViewUserPayload.ts b/src/dispatcher/payloads/ViewUserPayload.ts new file mode 100644 index 0000000000..ed602d4e24 --- /dev/null +++ b/src/dispatcher/payloads/ViewUserPayload.ts @@ -0,0 +1,29 @@ +/* +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 { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface ViewUserPayload extends ActionPayload { + action: Action.ViewUser, + + /** + * The member to view. May be null or falsy to indicate that no member + * should be shown (hide whichever relevant components). + */ + member?: RoomMember; +}