From 5b8d440406f6388b6ee46cae553f70e663b93e10 Mon Sep 17 00:00:00 2001 From: Kerry Date: Tue, 22 Feb 2022 11:04:27 +0100 Subject: [PATCH] type Actions (#7862) * type ViewHomPage action Signed-off-by: Kerry Archibald * type spacestore actions Signed-off-by: Kerry Archibald * lint Signed-off-by: Kerry Archibald * add action types Signed-off-by: Kerry Archibald * use new action types in stores Signed-off-by: Kerry Archibald * remove debug change Signed-off-by: Kerry Archibald * stricter keyboard shortcut types Signed-off-by: Kerry Archibald * action comments Signed-off-by: Kerry Archibald --- src/Registration.tsx | 3 +- src/accessibility/KeyboardShortcuts.ts | 21 +++++++++-- src/components/structures/GroupView.js | 4 +-- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/MatrixChat.tsx | 36 ++++++++++--------- src/components/structures/RightPanel.tsx | 3 +- src/components/structures/RoomView.tsx | 10 +++--- src/components/structures/UserMenu.tsx | 3 +- .../views/dialogs/RoomSettingsDialog.tsx | 3 +- .../views/dialogs/SpaceSettingsDialog.tsx | 5 +-- src/components/views/elements/AppTile.tsx | 3 +- .../tabs/user/KeyboardUserSettingsTab.tsx | 3 +- src/dispatcher/actions.ts | 26 ++++++++++++++ .../payloads/AfterLeaveRoomPayload.ts | 26 ++++++++++++++ .../payloads/DoAfterSyncPreparedPayload.ts | 24 +++++++++++++ .../payloads/JoinRoomErrorPayload.ts | 27 ++++++++++++++ .../payloads/SettingUpdatedPayload.ts | 3 +- .../payloads/ViewHomePagePayload.ts | 25 +++++++++++++ .../payloads/ViewRoomErrorPayload.ts | 29 +++++++++++++++ .../payloads/ViewStartChatOrReusePayload.ts | 26 ++++++++++++++ src/mjolnir/Mjolnir.ts | 6 ++-- src/settings/Settings.tsx | 13 +++++-- src/stores/BreadcrumbsStore.ts | 11 +++--- src/stores/LifecycleStore.ts | 6 ++-- src/stores/RoomViewStore.tsx | 14 ++++---- src/stores/local-echo/EchoStore.ts | 4 +-- .../RoomNotificationStateStore.ts | 3 +- src/stores/room-list/RoomListLayoutStore.ts | 3 +- src/stores/spaces/SpaceStore.ts | 23 ++++++++---- src/stores/widgets/WidgetMessagingStore.ts | 2 +- src/utils/membership.ts | 3 +- src/utils/space.tsx | 5 +-- test/stores/SpaceStore-test.ts | 2 +- 33 files changed, 304 insertions(+), 73 deletions(-) create mode 100644 src/dispatcher/payloads/AfterLeaveRoomPayload.ts create mode 100644 src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts create mode 100644 src/dispatcher/payloads/JoinRoomErrorPayload.ts create mode 100644 src/dispatcher/payloads/ViewHomePagePayload.ts create mode 100644 src/dispatcher/payloads/ViewRoomErrorPayload.ts create mode 100644 src/dispatcher/payloads/ViewStartChatOrReusePayload.ts diff --git a/src/Registration.tsx b/src/Registration.tsx index a90c5c069d..87efcd6557 100644 --- a/src/Registration.tsx +++ b/src/Registration.tsx @@ -26,6 +26,7 @@ import dis from './dispatcher/dispatcher'; import Modal from './Modal'; import { _t } from './languageHandler'; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; +import { Action } from "./dispatcher/actions"; // Regex for what a "safe" or "Matrix-looking" localpart would be. // TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514 @@ -69,7 +70,7 @@ export async function startAnyRegistrationFlow( if (proceed) { dis.dispatch({ action: 'start_registration', screenAfterLogin: options.screen_after }); } else if (options.go_home_on_cancel) { - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); } else if (options.go_welcome_on_cancel) { dis.dispatch({ action: 'view_welcome_page' }); } diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 254aedb89e..0bd3cffcad 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -17,7 +17,7 @@ limitations under the License. import { _td } from "../languageHandler"; import { isMac, Key } from "../Keyboard"; -import { ISetting } from "../settings/Settings"; +import { IBaseSetting } from "../settings/Settings"; import SettingsStore from "../settings/SettingsStore"; import IncompatibleController from "../settings/controllers/IncompatibleController"; @@ -119,9 +119,20 @@ export enum KeyBindingAction { ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility', } +export type KeyBindingConfig = { + key: string; + ctrlOrCmdKey?: boolean; + ctrlKey?: boolean; + altKey?: boolean; + shiftKey?: boolean; + metaKey?: boolean; +}; + +type KeyboardShortcutSetting = IBaseSetting; + type IKeyboardShortcuts = { // TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager - [k in (KeyBindingAction | string)]: ISetting; + [k in (KeyBindingAction | string)]: KeyboardShortcutSetting; }; export interface ICategory { @@ -614,7 +625,11 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => { }, {}); }; -export const registerShortcut = (shortcutName: string, categoryName: CategoryName, shortcut: ISetting): void => { +export const registerShortcut = ( + shortcutName: string, + categoryName: CategoryName, + shortcut: KeyboardShortcutSetting, +): void => { KEYBOARD_SHORTCUTS[shortcutName] = shortcut; CATEGORIES[categoryName].settingNames.push(shortcutName); }; diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index f891c4100e..d55be8e3cb 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -342,7 +342,7 @@ class FeaturedUser extends React.Component { e.stopPropagation(); dis.dispatch({ - action: 'view_start_chat_or_reuse', + action: Action.ViewStartChatOrReuse, user_id: this.props.summaryInfo.user_id, }); }; @@ -491,7 +491,7 @@ export default class GroupView extends React.Component { if (this._unmounted || groupId !== errorGroupId) return; if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN' && !willDoOnboarding) { dis.dispatch({ - action: 'do_after_sync_prepared', + action: Action.DoAfterSyncPrepared, deferred_action: { action: 'view_group', group_id: groupId, diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 667d59dd31..eef11174c4 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -490,7 +490,7 @@ class LoggedInView extends React.Component { break; case KeyBindingAction.GoToHome: dis.dispatch({ - action: 'view_home_page', + action: Action.ViewHomePage, }); Modal.closeCurrentModal("homeKeyboardShortcut"); handled = true; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index bd05a677c7..59d9b97ea5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -119,6 +119,10 @@ import { SummarizedNotificationState } from "../../stores/notifications/Summariz import GenericToast from '../views/toasts/GenericToast'; import Views from '../../Views'; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; +import { ViewHomePagePayload } from '../../dispatcher/payloads/ViewHomePagePayload'; +import { AfterLeaveRoomPayload } from '../../dispatcher/payloads/AfterLeaveRoomPayload'; +import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload'; +import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStartChatOrReusePayload'; // legacy export export { default as Views } from "../../Views"; @@ -541,7 +545,7 @@ export default class MatrixChat extends React.PureComponent { // will cause a full login and sync and finally the deferred // action will be dispatched. dis.dispatch({ - action: 'do_after_sync_prepared', + action: Action.DoAfterSyncPrepared, deferred_action: payload, }); dis.dispatch({ action: 'require_registration' }); @@ -632,7 +636,7 @@ export default class MatrixChat extends React.PureComponent { MatrixClientPeg.get().leave(payload.room_id).then(() => { modal.close(); if (this.state.currentRoomId === payload.room_id) { - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); } }, (err) => { modal.close(); @@ -705,10 +709,10 @@ export default class MatrixChat extends React.PureComponent { case 'view_welcome_page': this.viewWelcome(); break; - case 'view_home_page': + case Action.ViewHomePage: this.viewHome(payload.justRegistered); break; - case 'view_start_chat_or_reuse': + case Action.ViewStartChatOrReuse: this.chatCreateOrReuse(payload.user_id); break; case 'view_create_chat': @@ -1057,10 +1061,10 @@ export default class MatrixChat extends React.PureComponent { // No point in making 2 DMs with welcome bot. This assumes view_set_mxid will // result in a new DM with the welcome user. if (userId !== this.props.config.welcomeUserId) { - dis.dispatch({ - action: 'do_after_sync_prepared', + dis.dispatch>({ + action: Action.DoAfterSyncPrepared, deferred_action: { - action: 'view_start_chat_or_reuse', + action: Action.ViewStartChatOrReuse, user_id: userId, }, }); @@ -1162,8 +1166,8 @@ export default class MatrixChat extends React.PureComponent { if (shouldLeave) { leaveRoomBehaviour(roomId); - dis.dispatch({ - action: "after_leave_room", + dis.dispatch({ + action: Action.AfterLeaveRoom, room_id: roomId, }); } @@ -1176,7 +1180,7 @@ export default class MatrixChat extends React.PureComponent { MatrixClientPeg.get().forget(roomId).then(() => { // Switch to home page if we're currently viewing the forgotten room if (this.state.currentRoomId === roomId) { - dis.dispatch({ action: "view_home_page" }); + dis.dispatch({ action: Action.ViewHomePage }); } // We have to manually update the room list because the forgotten room will not @@ -1275,7 +1279,7 @@ export default class MatrixChat extends React.PureComponent { if (welcomeUserRoom === null) { // We didn't redirect to the welcome user room, so show // the homepage. - dis.dispatch({ action: 'view_home_page', justRegistered: true }); + dis.dispatch({ action: Action.ViewHomePage, justRegistered: true }); } } else if (ThreepidInviteStore.instance.pickBestInvite()) { // The user has a 3pid invite pending - show them that @@ -1288,7 +1292,7 @@ export default class MatrixChat extends React.PureComponent { } else { // The user has just logged in after registering, // so show the homepage. - dis.dispatch({ action: 'view_home_page', justRegistered: true }); + dis.dispatch({ action: Action.ViewHomePage, justRegistered: true }); } } else { this.showScreenAfterLogin(); @@ -1353,7 +1357,7 @@ export default class MatrixChat extends React.PureComponent { if (MatrixClientPeg.get().isGuest()) { dis.dispatch({ action: 'view_welcome_page' }); } else { - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); } } } @@ -1666,7 +1670,7 @@ export default class MatrixChat extends React.PureComponent { const isLoggedOutOrGuest = !cli || cli.isGuest(); if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) { // user is logged in and landing on an auth page which will uproot their session, redirect them home instead - dis.dispatch({ action: "view_home_page" }); + dis.dispatch({ action: Action.ViewHomePage }); return; } @@ -1714,7 +1718,7 @@ export default class MatrixChat extends React.PureComponent { }); } else if (screen === 'home') { dis.dispatch({ - action: 'view_home_page', + action: Action.ViewHomePage, }); } else if (screen === 'start') { this.showScreen('home'); @@ -1737,7 +1741,7 @@ export default class MatrixChat extends React.PureComponent { PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin()); } else if (screen === 'groups') { if (SpaceStore.spacesEnabled) { - dis.dispatch({ action: "view_home_page" }); + dis.dispatch({ action: Action.ViewHomePage }); return; } dis.dispatch({ diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 360e478153..0779a9a52b 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -47,6 +47,7 @@ import { E2EStatus } from '../../utils/ShieldUtils'; import TimelineCard from '../views/right_panel/TimelineCard'; import { UPDATE_EVENT } from '../../stores/AsyncStore'; import { IRightPanelCard, IRightPanelCardState } from '../../stores/right-panel/RightPanelStoreIPanelState'; +import { Action } from '../../dispatcher/actions'; interface IProps { room?: Room; // if showing panels for a given room, this is set @@ -134,7 +135,7 @@ export default class RightPanel extends React.Component { // to the home page which is not obviously the correct thing to do, but I'm not sure // anything else is - we could hide the close button altogether?) dis.dispatch({ - action: "view_home_page", + action: Action.ViewHomePage, }); } else if ( this.state.phase === RightPanelPhases.EncryptionPanel && diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 53a7262fee..73d0dad0e0 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -103,6 +103,7 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { JoinRoomPayload } from "../../dispatcher/payloads/JoinRoomPayload"; +import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -1273,11 +1274,12 @@ export class RoomView extends React.Component { if (this.context && this.context.isGuest()) { // Join this room once the user has registered and logged in // (If we failed to peek, we may not have a valid room object.) - dis.dispatch({ - action: 'do_after_sync_prepared', + dis.dispatch>({ + action: Action.DoAfterSyncPrepared, deferred_action: { action: Action.ViewRoom, room_id: this.getRoomId(), + metricsTrigger: undefined, }, }); dis.dispatch({ action: 'require_registration' }); @@ -1568,7 +1570,7 @@ export class RoomView extends React.Component { rejecting: true, }); this.context.leave(this.state.roomId).then(() => { - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); this.setState({ rejecting: false, }); @@ -1601,7 +1603,7 @@ export class RoomView extends React.Component { await this.context.setIgnoredUsers(ignoredUsers); await this.context.leave(this.state.roomId); - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); this.setState({ rejecting: false, }); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index e8d5db75e2..7c336ec0e3 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -61,6 +61,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; import UserIdentifierCustomisations from "../../customisations/UserIdentifier"; import PosthogTrackers from "../../PosthogTrackers"; +import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; const CustomStatusSection = () => { const cli = useContext(MatrixClientContext); @@ -360,7 +361,7 @@ export default class UserMenu extends React.Component { ev.preventDefault(); ev.stopPropagation(); - defaultDispatcher.dispatch({ action: 'view_home_page' }); + defaultDispatcher.dispatch({ action: Action.ViewHomePage }); this.setState({ contextMenuPosition: null }); // also close the menu }; diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 452a7580a8..dd67e508d2 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -31,6 +31,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "./BaseDialog"; +import { Action } from '../../../dispatcher/actions'; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB"; @@ -75,7 +76,7 @@ export default class RoomSettingsDialog extends React.Component private onAction = (payload): void => { // When view changes below us, close the room settings // whilst the modal is open this can only be triggered when someone hits Leave Room - if (payload.action === 'view_home_page') { + if (payload.action === Action.ViewHomePage) { this.props.onFinished(true); } }; diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx index 7abcb0eceb..f0fe2b3852 100644 --- a/src/components/views/dialogs/SpaceSettingsDialog.tsx +++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -30,6 +30,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab"; import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab"; +import { Action } from '../../../dispatcher/actions'; export enum SpaceSettingsTab { General = "SPACE_GENERAL_TAB", @@ -44,8 +45,8 @@ interface IProps extends IDialogProps { } const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFinished }) => { - useDispatcher(defaultDispatcher, ({ action, ...params }) => { - if (action === "after_leave_room" && params.room_id === space.roomId) { + useDispatcher(defaultDispatcher, (payload) => { + if (payload.action === Action.AfterLeaveRoom && payload.room_id === space.roomId) { onFinished(false); } }); diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index 68e514fc0b..fda6c732c1 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -49,6 +49,7 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import WidgetUtils from '../../../utils/WidgetUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { ActionPayload } from "../../../dispatcher/payloads"; +import { Action } from '../../../dispatcher/actions'; interface IProps { app: IApp; @@ -447,7 +448,7 @@ export default class AppTile extends React.Component { } break; - case "after_leave_room": + case Action.AfterLeaveRoom: if (payload.room_id === this.props.room?.roomId) { // call this before we get it echoed down /sync, so it doesn't hang around as long and look jarring this.onUserLeftRoom(); diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx index 0efb11fd6c..a67e67a46b 100644 --- a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx @@ -24,13 +24,14 @@ import { ICategory, CATEGORIES, CategoryName, + KeyBindingConfig, } from "../../../../../accessibility/KeyboardShortcuts"; import SdkConfig from "../../../../../SdkConfig"; import { isMac, Key } from "../../../../../Keyboard"; import { _t } from "../../../../../languageHandler"; // TODO: This should return KeyCombo but it has ctrlOrCmd instead of ctrlOrCmdKey -const getKeyboardShortcutValue = (name: string) => { +const getKeyboardShortcutValue = (name: string): KeyBindingConfig => { return getKeyboardShortcuts()[name]?.default; }; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 113cc6e8bf..9a4341cfc0 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -45,6 +45,16 @@ export enum Action { */ ViewRoomDirectory = "view_room_directory", + /** + * Fires when viewing room by room_alias fails to find room + */ + ViewRoomError = "view_room_error", + + /** + * Navigates to app home + */ + ViewHomePage = "view_home_page", + /** * Forces the theme to reload. No additional payload information required. */ @@ -224,4 +234,20 @@ export enum Action { * Used to trigger auto rageshakes when configured */ ReportKeyBackupNotEnabled = "report_key_backup_not_enabled", + + /** + * Dispatched after leave room or space is finished + */ + AfterLeaveRoom = "after_leave_room", + + /** + * Used to defer actions until after sync is complete + * LifecycleStore will emit deferredAction payload after 'MatrixActions.sync' + */ + DoAfterSyncPrepared = "do_after_sync_prepared", + + /** + * Fired when clicking user name from group view + */ + ViewStartChatOrReuse = "view_start_chat_or_reuse", } diff --git a/src/dispatcher/payloads/AfterLeaveRoomPayload.ts b/src/dispatcher/payloads/AfterLeaveRoomPayload.ts new file mode 100644 index 0000000000..942b5d9800 --- /dev/null +++ b/src/dispatcher/payloads/AfterLeaveRoomPayload.ts @@ -0,0 +1,26 @@ +/* +Copyright 2022 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 { Room } from "matrix-js-sdk"; + +import { Action } from "../actions"; +import { ActionPayload } from "../payloads"; + +export interface AfterLeaveRoomPayload extends ActionPayload { + action: Action.AfterLeaveRoom; + // eslint-disable-next-line camelcase + room_id?: Room["roomId"]; +} diff --git a/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts b/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts new file mode 100644 index 0000000000..6f3a9a6730 --- /dev/null +++ b/src/dispatcher/payloads/DoAfterSyncPreparedPayload.ts @@ -0,0 +1,24 @@ +/* +Copyright 2022 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 DoAfterSyncPreparedPayload extends Pick { + action: Action.DoAfterSyncPrepared; + // eslint-disable-next-line camelcase + deferred_action: T; +} diff --git a/src/dispatcher/payloads/JoinRoomErrorPayload.ts b/src/dispatcher/payloads/JoinRoomErrorPayload.ts new file mode 100644 index 0000000000..c130401f4b --- /dev/null +++ b/src/dispatcher/payloads/JoinRoomErrorPayload.ts @@ -0,0 +1,27 @@ +/* +Copyright 2022 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 { MatrixError } from "matrix-js-sdk"; + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface JoinRoomErrorPayload extends Pick { + action: Action.JoinRoomError; + + roomId: string; + err?: MatrixError; +} diff --git a/src/dispatcher/payloads/SettingUpdatedPayload.ts b/src/dispatcher/payloads/SettingUpdatedPayload.ts index 8d457facfb..3e3f42e8a1 100644 --- a/src/dispatcher/payloads/SettingUpdatedPayload.ts +++ b/src/dispatcher/payloads/SettingUpdatedPayload.ts @@ -17,6 +17,7 @@ limitations under the License. import { ActionPayload } from "../payloads"; import { Action } from "../actions"; import { SettingLevel } from "../../settings/SettingLevel"; +import { SettingValueType } from "../../settings/Settings"; export interface SettingUpdatedPayload extends ActionPayload { action: Action.SettingUpdated; @@ -25,5 +26,5 @@ export interface SettingUpdatedPayload extends ActionPayload { roomId: string; level: SettingLevel; newValueAtLevel: SettingLevel; - newValue: any; + newValue: SettingValueType; } diff --git a/src/dispatcher/payloads/ViewHomePagePayload.ts b/src/dispatcher/payloads/ViewHomePagePayload.ts new file mode 100644 index 0000000000..2a0d0077b3 --- /dev/null +++ b/src/dispatcher/payloads/ViewHomePagePayload.ts @@ -0,0 +1,25 @@ +/* +Copyright 2022 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 { Action } from "../actions"; +import { ActionPayload } from "../payloads"; + +export interface ViewHomePagePayload extends ActionPayload { + action: Action.ViewHomePage; + // eslint-disable-next-line camelcase + context_switch?: boolean; + justRegistered?: boolean; +} diff --git a/src/dispatcher/payloads/ViewRoomErrorPayload.ts b/src/dispatcher/payloads/ViewRoomErrorPayload.ts new file mode 100644 index 0000000000..c044e150ca --- /dev/null +++ b/src/dispatcher/payloads/ViewRoomErrorPayload.ts @@ -0,0 +1,29 @@ +/* +Copyright 2022 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 { MatrixError, Room } from "matrix-js-sdk"; + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface ViewRoomErrorPayload extends Pick { + action: Action.ViewRoomError; + // eslint-disable-next-line camelcase + room_id: Room["roomId"]; + // eslint-disable-next-line camelcase + room_alias?: string; + err?: MatrixError; +} diff --git a/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts b/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts new file mode 100644 index 0000000000..4a54aaf4ad --- /dev/null +++ b/src/dispatcher/payloads/ViewStartChatOrReusePayload.ts @@ -0,0 +1,26 @@ +/* +Copyright 2022 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 { User } from "matrix-js-sdk"; + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface ViewStartChatOrReusePayload extends Pick { + action: Action.ViewStartChatOrReuse; + // eslint-disable-next-line camelcase + user_id: User["userId"]; +} diff --git a/src/mjolnir/Mjolnir.ts b/src/mjolnir/Mjolnir.ts index 7da37fbc10..85a32914e3 100644 --- a/src/mjolnir/Mjolnir.ts +++ b/src/mjolnir/Mjolnir.ts @@ -25,6 +25,8 @@ import { _t } from "../languageHandler"; import dis from "../dispatcher/dispatcher"; import { SettingLevel } from "../settings/SettingLevel"; import { ActionPayload } from "../dispatcher/payloads"; +import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload"; +import { Action } from "../dispatcher/actions"; // TODO: Move this and related files to the js-sdk or something once finalized. @@ -48,8 +50,8 @@ export class Mjolnir { this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this)); this.dispatcherRef = dis.register(this.onAction); - dis.dispatch({ - action: 'do_after_sync_prepared', + dis.dispatch>({ + action: Action.DoAfterSyncPrepared, deferred_action: { action: 'setup_mjolnir' }, }); } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index a98dc51f47..649fcad056 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -114,7 +114,14 @@ export const labGroupNames: Record = { [LabGroup.Developer]: _td("Developer"), }; -interface IBaseSetting { +export type SettingValueType = boolean | + number | + string | + number[] | + string[] | + Record; + +export interface IBaseSetting { isFeature?: false | undefined; // Display names are strongly recommended for clarity. @@ -134,7 +141,7 @@ interface IBaseSetting { // Required. Can be any data type. The value specified here should match // the data being stored (ie: if a boolean is used, the setting should // represent a boolean). - default: any; + default: T; // Optional settings controller. See SettingsController for more information. controller?: SettingController; @@ -166,7 +173,7 @@ interface IBaseSetting { }; } -export interface IFeature extends Omit { +export interface IFeature extends Omit, "isFeature"> { // Must be set to true for features. isFeature: true; labsGroup: LabGroup; diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index b9cf33c6f6..e5eb6be675 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -18,13 +18,13 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import SettingsStore from "../settings/SettingsStore"; -import { ActionPayload } from "../dispatcher/payloads"; import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import { arrayHasDiff } from "../utils/arrays"; import { SettingLevel } from "../settings/SettingLevel"; import { Action } from "../dispatcher/actions"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; +import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; const MAX_ROOMS = 20; // arbitrary const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up @@ -64,15 +64,14 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { return this.matrixClient?.getVisibleRooms().length >= 20; } - protected async onAction(payload: ActionPayload) { + protected async onAction(payload: SettingUpdatedPayload | ViewRoomPayload) { if (!this.matrixClient) return; if (payload.action === Action.SettingUpdated) { - const settingUpdatedPayload = payload as SettingUpdatedPayload; - if (settingUpdatedPayload.settingName === 'breadcrumb_rooms') { + if (payload.settingName === 'breadcrumb_rooms') { await this.updateRooms(); - } else if (settingUpdatedPayload.settingName === 'breadcrumbs' || - settingUpdatedPayload.settingName === 'feature_breadcrumbs_v2' + } else if (payload.settingName === 'breadcrumbs' || + payload.settingName === 'feature_breadcrumbs_v2' ) { await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) }); } diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts index 51203b3582..618f4b4162 100644 --- a/src/stores/LifecycleStore.ts +++ b/src/stores/LifecycleStore.ts @@ -16,8 +16,10 @@ limitations under the License. import { Store } from 'flux/utils'; +import { Action } from '../dispatcher/actions'; import dis from '../dispatcher/dispatcher'; import { ActionPayload } from "../dispatcher/payloads"; +import { DoAfterSyncPreparedPayload } from '../dispatcher/payloads/DoAfterSyncPreparedPayload'; interface IState { deferredAction: any; @@ -44,9 +46,9 @@ class LifecycleStore extends Store { this.__emitChange(); } - protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention + protected __onDispatch(payload: ActionPayload | DoAfterSyncPreparedPayload) { // eslint-disable-line @typescript-eslint/naming-convention switch (payload.action) { - case 'do_after_sync_prepared': + case Action.DoAfterSyncPrepared: this.setState({ deferredAction: payload.deferred_action, }); diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 2b87b3ab73..d17029ae8e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -42,6 +42,8 @@ import SpaceStore from "./spaces/SpaceStore"; import { isMetaSpace, MetaSpace } from "./spaces"; import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload"; import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload"; +import { JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload"; +import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload"; const NUM_JOIN_RETRY = 5; @@ -122,7 +124,7 @@ class RoomViewStore extends Store { // for these events blank out the roomId as we are no longer in the RoomView case 'view_create_group': case 'view_welcome_page': - case 'view_home_page': + case Action.ViewHomePage: case 'view_my_groups': case 'view_group': this.setState({ @@ -132,7 +134,7 @@ class RoomViewStore extends Store { wasContextSwitch: false, }); break; - case 'view_room_error': + case Action.ViewRoomError: this.viewRoomError(payload); break; case 'will_join': @@ -306,8 +308,8 @@ class RoomViewStore extends Store { roomId = result.room_id; } catch (err) { logger.error("RVS failed to get room id for alias: ", err); - dis.dispatch({ - action: 'view_room_error', + dis.dispatch({ + action: Action.ViewRoomError, room_id: null, room_alias: payload.room_alias, err, @@ -324,7 +326,7 @@ class RoomViewStore extends Store { } } - private viewRoomError(payload: ActionPayload) { + private viewRoomError(payload: ViewRoomErrorPayload) { this.setState({ roomId: payload.room_id, roomAlias: payload.room_alias, @@ -411,7 +413,7 @@ class RoomViewStore extends Store { }); } - private joinRoomError(payload: ActionPayload) { + private joinRoomError(payload: JoinRoomErrorPayload) { this.setState({ joining: false, joinError: payload.err, diff --git a/src/stores/local-echo/EchoStore.ts b/src/stores/local-echo/EchoStore.ts index b52a73ab7f..8b6ae48c68 100644 --- a/src/stores/local-echo/EchoStore.ts +++ b/src/stores/local-echo/EchoStore.ts @@ -98,8 +98,6 @@ export class EchoStore extends AsyncStoreWithClient { } } - protected async onAction(payload: ActionPayload): Promise { - // We have nothing to actually listen for - return Promise.resolve(); + protected async onAction(payload: ActionPayload): Promise { } } diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts index 99a1f2068b..ed032a331d 100644 --- a/src/stores/notifications/RoomNotificationStateStore.ts +++ b/src/stores/notifications/RoomNotificationStateStore.ts @@ -140,7 +140,6 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { } // We don't need this, but our contract says we do. - protected async onAction(payload: ActionPayload) { - return Promise.resolve(); + protected async onAction(payload: ActionPayload): Promise { } } diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts index fce230779e..79ebe3d280 100644 --- a/src/stores/room-list/RoomListLayoutStore.ts +++ b/src/stores/room-list/RoomListLayoutStore.ts @@ -67,8 +67,7 @@ export default class RoomListLayoutStore extends AsyncStoreWithClient { } // We don't need this function, but our contract says we do - protected async onAction(payload: ActionPayload): Promise { - return Promise.resolve(); + protected async onAction(payload: ActionPayload): Promise { } } diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 6addeec354..15b1362f76 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -24,7 +24,6 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher from "../../dispatcher/dispatcher"; -import { ActionPayload } from "../../dispatcher/payloads"; import RoomListStore from "../room-list/RoomListStore"; import SettingsStore from "../../settings/SettingsStore"; import DMRoomMap from "../../utils/DMRoomMap"; @@ -61,6 +60,9 @@ import { } from "./flattenSpaceHierarchy"; import { PosthogAnalytics } from "../../PosthogAnalytics"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; +import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; +import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload"; +import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload"; interface IState { } @@ -92,7 +94,7 @@ const validOrder = (order: string): string | undefined => { }; // For sorting space children using a validated `order`, `origin_server_ts`, `room_id` -export const getChildOrder = (order: string, ts: number, roomId: string): Array>> => { +export const getChildOrder = (order: string, ts: number, roomId: string): Array>> => { return [validOrder(order) ?? NaN, ts, roomId]; // NaN has lodash sort it at the end in asc }; @@ -100,6 +102,13 @@ const getRoomFn: FetchRoomFn = (room: Room) => { return RoomNotificationStateStore.instance.getRoomState(room); }; +type SpaceStoreActions = + | SettingUpdatedPayload + | ViewRoomPayload + | ViewHomePagePayload + | SwitchSpacePayload + | AfterLeaveRoomPayload; + export class SpaceStoreClass extends AsyncStoreWithClient { // The spaces representing the roots of the various tree-like hierarchies private rootSpaces: Room[] = []; @@ -254,8 +263,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { metricsTrigger: "WebSpaceContextSwitch", }); } else { - defaultDispatcher.dispatch({ - action: "view_home_page", + defaultDispatcher.dispatch({ + action: Action.ViewHomePage, context_switch: true, }); } @@ -1097,7 +1106,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, contextSwitch); } - protected async onAction(payload: ActionPayload) { + protected async onAction(payload: SpaceStoreActions) { if (!spacesEnabled || !this.matrixClient) return; switch (payload.action) { @@ -1129,14 +1138,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { break; } - case "view_home_page": + case Action.ViewHomePage: if (!payload.context_switch && this.enabledMetaSpaces.includes(MetaSpace.Home)) { this.setActiveSpace(MetaSpace.Home, false); window.localStorage.setItem(getSpaceContextKey(this.activeSpace), ""); } break; - case "after_leave_room": + case Action.AfterLeaveRoom: if (!isMetaSpace(this._activeSpace) && payload.room_id === this._activeSpace) { // User has left the current space, go to first space this.goToFirstSpace(true); diff --git a/src/stores/widgets/WidgetMessagingStore.ts b/src/stores/widgets/WidgetMessagingStore.ts index 824132b5c3..044832997e 100644 --- a/src/stores/widgets/WidgetMessagingStore.ts +++ b/src/stores/widgets/WidgetMessagingStore.ts @@ -40,7 +40,7 @@ export class WidgetMessagingStore extends AsyncStoreWithClient { return WidgetMessagingStore.internalInstance; } - protected async onAction(payload: ActionPayload): Promise { + protected async onAction(payload: ActionPayload): Promise { // nothing to do } diff --git a/src/utils/membership.ts b/src/utils/membership.ts index cc977fda9d..e03b575e57 100644 --- a/src/utils/membership.ts +++ b/src/utils/membership.ts @@ -30,6 +30,7 @@ import { isMetaSpace } from "../stores/spaces"; import SpaceStore from "../stores/spaces/SpaceStore"; import { Action } from "../dispatcher/actions"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; +import { ViewHomePagePayload } from "../dispatcher/payloads/ViewHomePagePayload"; /** * Approximation of a membership status for a given room. @@ -194,6 +195,6 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = metricsTrigger: undefined, // other }); } else { - dis.dispatch({ action: 'view_home_page' }); + dis.dispatch({ action: Action.ViewHomePage }); } } diff --git a/src/utils/space.tsx b/src/utils/space.tsx index 958128e625..338ddec811 100644 --- a/src/utils/space.tsx +++ b/src/utils/space.tsx @@ -43,6 +43,7 @@ import CreateSpaceFromCommunityDialog from "../components/views/dialogs/CreateSp import SpacePreferencesDialog, { SpacePreferenceTab } from "../components/views/dialogs/SpacePreferencesDialog"; import PosthogTrackers from "../PosthogTrackers"; import { ButtonEvent } from "../components/views/elements/AccessibleButton"; +import { AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload"; export const shouldShowSpaceSettings = (space: Room) => { const userId = space.client.getUserId(); @@ -189,8 +190,8 @@ export const leaveSpace = (space: Room) => { if (!leave) return; await bulkSpaceBehaviour(space, rooms, room => leaveRoomBehaviour(room.roomId)); - dis.dispatch({ - action: "after_leave_room", + dis.dispatch({ + action: Action.AfterLeaveRoom, room_id: space.roomId, }); }, diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 49e0144990..d6c4fa8915 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -853,7 +853,7 @@ describe("SpaceStore", () => { await run(); dispatcherRef = defaultDispatcher.register(payload => { - if (payload.action === Action.ViewRoom || payload.action === "view_home_page") { + if (payload.action === Action.ViewRoom || payload.action === Action.ViewHomePage) { currentRoom = payload.room_id || null; } });