diff --git a/res/css/_components.scss b/res/css/_components.scss index a6b9f9cc497..265eef7495b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -95,7 +95,6 @@ @import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_InviteDialog.scss"; @import "./views/dialogs/_JoinRuleDropdown.scss"; -@import "./views/dialogs/_KeyboardShortcutsDialog.scss"; @import "./views/dialogs/_LeaveSpaceDialog.scss"; @import "./views/dialogs/_LocationViewDialog.scss"; @import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss"; @@ -286,6 +285,7 @@ @import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss"; @import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss"; @import "./views/settings/tabs/user/_HelpUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_KeyboardUserSettingsTab.scss"; @import "./views/settings/tabs/user/_LabsUserSettingsTab.scss"; @import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss"; @import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index f7728eb69b7..b5331e40b1e 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -37,6 +37,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/settings/preference.svg'); } +.mx_UserSettingsDialog_keyboardIcon::before { + mask-image: url('$(res)/img/element-icons/settings/keyboard.svg'); +} + .mx_UserSettingsDialog_sidebarIcon::before { mask-image: url('$(res)/img/element-icons/settings/sidebar.svg'); } diff --git a/res/css/views/dialogs/_KeyboardShortcutsDialog.scss b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss similarity index 93% rename from res/css/views/dialogs/_KeyboardShortcutsDialog.scss rename to res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss index c2e299ba79c..6f5a0855ebe 100644 --- a/res/css/views/dialogs/_KeyboardShortcutsDialog.scss +++ b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss @@ -1,5 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_KeyboardShortcutsDialog { +.mx_KeyboardUserSettingsTab .mx_SettingsTab_section { display: flex; flex-wrap: wrap; -webkit-box-orient: vertical; diff --git a/res/img/element-icons/settings/keyboard.svg b/res/img/element-icons/settings/keyboard.svg new file mode 100644 index 00000000000..aec7a9ec822 --- /dev/null +++ b/res/img/element-icons/settings/keyboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts index 75222d91213..468d9989520 100644 --- a/src/KeyBindingsDefaults.ts +++ b/src/KeyBindingsDefaults.ts @@ -355,14 +355,14 @@ const navigationBindings = (): KeyBinding[] => { }, }, { - action: NavigationAction.ToggleShortCutDialog, + action: NavigationAction.OpenShortCutDialog, keyCombo: { key: Key.SLASH, ctrlOrCmd: true, }, }, { - action: NavigationAction.ToggleShortCutDialog, + action: NavigationAction.OpenShortCutDialog, keyCombo: { key: Key.SLASH, ctrlOrCmd: true, diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index 1eb6b689f06..047e0b74c24 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -112,7 +112,7 @@ export enum NavigationAction { /** Toggle the user menu */ ToggleUserMenu = 'ToggleUserMenu', /** Toggle the short cut help dialog */ - ToggleShortCutDialog = 'ToggleShortCutDialog', + OpenShortCutDialog = 'OpenShortCutDialog', /** Got to the Element home screen */ GoToHome = 'GoToHome', /** Select prev room */ diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.ts similarity index 69% rename from src/accessibility/KeyboardShortcuts.tsx rename to src/accessibility/KeyboardShortcuts.ts index d1834873299..868277ccc7b 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.ts @@ -14,20 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from "react"; -import classNames from "classnames"; - -import Modal from "../Modal"; -import { _t, _td } from "../languageHandler"; +import { _td } from "../languageHandler"; import { isMac, Key } from "../Keyboard"; -import InfoDialog from "../components/views/dialogs/InfoDialog"; - -// TS: once languageHandler is TS we can probably inline this into the enum -_td("Navigation"); -_td("Calls"); -_td("Composer"); -_td("Room List"); -_td("Autocomplete"); export enum Categories { NAVIGATION = "Navigation", @@ -38,13 +26,6 @@ export enum Categories { AUTOCOMPLETE = "Autocomplete", } -// TS: once languageHandler is TS we can probably inline this into the enum -_td("Alt"); -_td("Alt Gr"); -_td("Shift"); -_td("Super"); -_td("Ctrl"); - export enum Modifiers { ALT = "Alt", // Option on Mac and displayed as an Icon ALT_GR = "Alt Gr", @@ -65,12 +46,12 @@ interface IKeybind { key: string; // TS: fix this once Key is an enum } -interface IShortcut { +export interface IShortcut { keybinds: IKeybind[]; description: string; } -const shortcuts: Record = { +export const shortcuts: Record = { [Categories.COMPOSER]: [ { keybinds: [{ @@ -270,7 +251,7 @@ const shortcuts: Record = { modifiers: [CMD_OR_CTRL], key: Key.SLASH, }], - description: _td("Toggle this dialog"), + description: _td("Open this settings tab"), }, { keybinds: [{ modifiers: [Modifiers.CONTROL, isMac ? Modifiers.SHIFT : Modifiers.ALT], @@ -297,107 +278,6 @@ const shortcuts: Record = { ], }; -const categoryOrder = [ - Categories.COMPOSER, - Categories.AUTOCOMPLETE, - Categories.ROOM, - Categories.ROOM_LIST, - Categories.NAVIGATION, - Categories.CALLS, -]; - -interface IModal { - close: () => void; - finished: Promise; -} - -const modifierIcon: Record = { - [Modifiers.COMMAND]: "⌘", -}; - -if (isMac) { - modifierIcon[Modifiers.ALT] = "⌥"; -} - -const alternateKeyName: Record = { - [Key.PAGE_UP]: _td("Page Up"), - [Key.PAGE_DOWN]: _td("Page Down"), - [Key.ESCAPE]: _td("Esc"), - [Key.ENTER]: _td("Enter"), - [Key.SPACE]: _td("Space"), - [Key.HOME]: _td("Home"), - [Key.END]: _td("End"), - [DIGITS]: _td("[number]"), -}; -const keyIcon: Record = { - [Key.ARROW_UP]: "↑", - [Key.ARROW_DOWN]: "↓", - [Key.ARROW_LEFT]: "←", - [Key.ARROW_RIGHT]: "→", -}; - -const Shortcut: React.FC<{ - shortcut: IShortcut; -}> = ({ shortcut }) => { - const classes = classNames({ - "mx_KeyboardShortcutsDialog_inline": shortcut.keybinds.every(k => !k.modifiers || k.modifiers.length === 0), - }); - - return
-
{ _t(shortcut.description) }
- { shortcut.keybinds.map(s => { - let text = s.key; - if (alternateKeyName[s.key]) { - text = _t(alternateKeyName[s.key]); - } else if (keyIcon[s.key]) { - text = keyIcon[s.key]; - } - - return
- { s.modifiers?.map(m => { - return - { modifierIcon[m] || _t(m) }+ - ; - }) } - { text } -
; - }) } -
; -}; - -let activeModal: IModal = null; -export const toggleDialog = () => { - if (activeModal) { - activeModal.close(); - activeModal = null; - return; - } - - const sections = categoryOrder.map(category => { - const list = shortcuts[category]; - return
-

{ _t(category) }

-
{ list.map(shortcut => ) }
-
; - }); - - activeModal = Modal.createTrackedDialog("Keyboard Shortcuts", "", InfoDialog, { - className: "mx_KeyboardShortcutsDialog", - title: _t("Keyboard Shortcuts"), - description: sections, - hasCloseButton: true, - onKeyDown: (ev) => { - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey && ev.key === Key.SLASH) { // Ctrl + / - ev.stopPropagation(); - activeModal.close(); - } - }, - onFinished: () => { - activeModal = null; - }, - }); -}; - export const registerShortcut = (category: Categories, defn: IShortcut) => { shortcuts[category].push(defn); }; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index b1ecfbf2e37..ee25d585895 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -32,7 +32,6 @@ import SettingsStore from "../../settings/SettingsStore"; import ResizeHandle from '../views/elements/ResizeHandle'; import { CollapseDistributor, Resizer } from '../../resizer'; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts"; import HomePage from "./HomePage"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PlatformPeg from "../../PlatformPeg"; @@ -67,6 +66,8 @@ import GroupFilterPanel from './GroupFilterPanel'; import CustomRoomTagPanel from './CustomRoomTagPanel'; import { mediaFromMxc } from "../../customisations/Media"; import LegacyCommunityPreview from "./LegacyCommunityPreview"; +import { UserTab } from "../views/dialogs/UserSettingsDialog"; +import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import RightPanelStore from '../../stores/right-panel/RightPanelStore'; // We need to fetch each pinned message individually (if we don't already have it) @@ -472,8 +473,11 @@ class LoggedInView extends React.Component { dis.fire(Action.ToggleUserMenu); handled = true; break; - case NavigationAction.ToggleShortCutDialog: - KeyboardShortcuts.toggleDialog(); + case NavigationAction.OpenShortCutDialog: + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Keyboard, + }); handled = true; break; case NavigationAction.GoToHome: diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index e50c12e6fbe..09583bb6b12 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -36,6 +36,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "./BaseDialog"; import { IDialogProps } from "./IDialogProps"; import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab"; +import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab"; export enum UserTab { General = "USER_GENERAL_TAB", @@ -43,6 +44,7 @@ export enum UserTab { Flair = "USER_FLAIR_TAB", Notifications = "USER_NOTIFICATIONS_TAB", Preferences = "USER_PREFERENCES_TAB", + Keyboard = "USER_KEYBOARD_TAB", Sidebar = "USER_SIDEBAR_TAB", Voice = "USER_VOICE_TAB", Security = "USER_SECURITY_TAB", @@ -119,6 +121,12 @@ export default class UserSettingsDialog extends React.Component "mx_UserSettingsDialog_preferencesIcon", , )); + tabs.push(new Tab( + UserTab.Keyboard, + _td("Keyboard"), + "mx_UserSettingsDialog_keyboardIcon", + , + )); if (SettingsStore.getValue("feature_spaces_metaspaces")) { tabs.push(new Tab( diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 1f586a7121c..a2de7e81a96 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -25,7 +25,6 @@ import SdkConfig from "../../../../../SdkConfig"; import createRoom from "../../../../../createRoom"; import Modal from "../../../../../Modal"; import PlatformPeg from "../../../../../PlatformPeg"; -import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import UpdateCheckButton from "../../UpdateCheckButton"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { copyPlaintext } from "../../../../../utils/strings"; @@ -33,6 +32,10 @@ import * as ContextMenu from "../../../../structures/ContextMenu"; import { toRightOf } from "../../../../structures/ContextMenu"; import BugReportDialog from '../../../dialogs/BugReportDialog'; import GenericTextContextMenu from "../../../context_menus/GenericTextContextMenu"; +import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload"; +import { Action } from "../../../../../dispatcher/actions"; +import { UserTab } from "../../../dialogs/UserSettingsDialog"; +import dis from "../../../../../dispatcher/dispatcher"; interface IProps { closeSettingsFn: () => void; @@ -211,6 +214,13 @@ export default class HelpUserSettingsTab extends React.Component this.copy(`${appVersion}\n${olmVersion}`, e); }; + private onKeyboardShortcutsClicked = (): void => { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Keyboard, + }); + }; + render() { const brand = SdkConfig.get().brand; @@ -307,7 +317,7 @@ export default class HelpUserSettingsTab extends React.Component
{ faqText }
- + { _t("Keyboard Shortcuts") } diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx new file mode 100644 index 00000000000..11ef078424b --- /dev/null +++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx @@ -0,0 +1,117 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner + +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 classNames from "classnames"; +import React from "react"; + +import { Categories, DIGITS, IShortcut, Modifiers, shortcuts } from "../../../../../accessibility/KeyboardShortcuts"; +import { isMac, Key } from "../../../../../Keyboard"; +import { _t, _td } from "../../../../../languageHandler"; + +// TS: once languageHandler is TS we can probably inline this into the enum +_td("Alt"); +_td("Alt Gr"); +_td("Shift"); +_td("Super"); +_td("Ctrl"); +_td("Navigation"); +_td("Calls"); +_td("Composer"); +_td("Room List"); +_td("Autocomplete"); + +const categoryOrder = [ + Categories.COMPOSER, + Categories.AUTOCOMPLETE, + Categories.ROOM, + Categories.ROOM_LIST, + Categories.NAVIGATION, + Categories.CALLS, +]; + +const modifierIcon: Record = { + [Modifiers.COMMAND]: "⌘", +}; + +if (isMac) { + modifierIcon[Modifiers.ALT] = "⌥"; +} + +const alternateKeyName: Record = { + [Key.PAGE_UP]: _td("Page Up"), + [Key.PAGE_DOWN]: _td("Page Down"), + [Key.ESCAPE]: _td("Esc"), + [Key.ENTER]: _td("Enter"), + [Key.SPACE]: _td("Space"), + [Key.HOME]: _td("Home"), + [Key.END]: _td("End"), + [DIGITS]: _td("[number]"), +}; +const keyIcon: Record = { + [Key.ARROW_UP]: "↑", + [Key.ARROW_DOWN]: "↓", + [Key.ARROW_LEFT]: "←", + [Key.ARROW_RIGHT]: "→", +}; + +interface IShortcutProps { + shortcut: IShortcut; +} + +const Shortcut: React.FC = ({ shortcut }) => { + const classes = classNames({ + "mx_KeyboardShortcutsDialog_inline": shortcut.keybinds.every(k => !k.modifiers || k.modifiers.length === 0), + }); + + return
+
{ _t(shortcut.description) }
+ { shortcut.keybinds.map(s => { + let text = s.key; + if (alternateKeyName[s.key]) { + text = _t(alternateKeyName[s.key]); + } else if (keyIcon[s.key]) { + text = keyIcon[s.key]; + } + + return
+ { s.modifiers && s.modifiers.map(m => { + return + { modifierIcon[m] || _t(m) }+ + ; + }) } + { text } +
; + }) } +
; +}; + +const KeyboardUserSettingsTab: React.FC = () => { + return
+
{ _t("Keyboard") }
+
+ { categoryOrder.map(category => { + const list = shortcuts[category]; + return
+

{ _t(category) }

+
{ list.map(shortcut => ) }
+
; + }) } +
+
; +}; + +export default KeyboardUserSettingsTab; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index e8e9af7f0d9..5b35d17eeba 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -26,7 +26,6 @@ import PlatformPeg from "../../../../../PlatformPeg"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import SettingsFlag from '../../../elements/SettingsFlag'; -import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import AccessibleButton from "../../../elements/AccessibleButton"; import GroupAvatar from "../../../avatars/GroupAvatar"; import dis from "../../../../../dispatcher/dispatcher"; @@ -36,6 +35,8 @@ import { useDispatcher } from "../../../../../hooks/useDispatcher"; import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog"; import { createSpaceFromCommunity } from "../../../../../utils/space"; import Spinner from "../../../elements/Spinner"; +import { UserTab } from "../../../dialogs/UserSettingsDialog"; +import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload"; import { Action } from "../../../../../dispatcher/actions"; interface IProps { @@ -296,6 +297,13 @@ export default class PreferencesUserSettingsTab extends React.Component { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Keyboard, + }); + }; + getShowLocationIfEnabled(): string[] { // TODO: when location sharing is out of labs, this can be deleted and // we can just add this to COMPOSER_SETTINGS @@ -372,7 +380,7 @@ export default class PreferencesUserSettingsTab extends React.Component{ _t("Keyboard shortcuts") }
{ _t("To view all keyboard shortcuts, click here.", {}, { - a: sub => + a: sub => { sub } , }) } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 504834e1e5a..f5e257a811e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1428,6 +1428,24 @@ "Access Token": "Access Token", "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", "Clear cache and reload": "Clear cache and reload", + "Alt": "Alt", + "Alt Gr": "Alt Gr", + "Shift": "Shift", + "Super": "Super", + "Ctrl": "Ctrl", + "Navigation": "Navigation", + "Calls": "Calls", + "Composer": "Composer", + "Room List": "Room List", + "Autocomplete": "Autocomplete", + "Page Up": "Page Up", + "Page Down": "Page Down", + "Esc": "Esc", + "Enter": "Enter", + "Space": "Space", + "End": "End", + "[number]": "[number]", + "Keyboard": "Keyboard", "Labs": "Labs", "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.", "Ignored/Blocked": "Ignored/Blocked", @@ -1478,7 +1496,6 @@ "Keyboard shortcuts": "Keyboard shortcuts", "To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.", "Displaying time": "Displaying time", - "Composer": "Composer", "Code blocks": "Code blocks", "Images, GIFs and videos": "Images, GIFs and videos", "Timeline": "Timeline", @@ -2868,7 +2885,6 @@ "Mentions only": "Mentions only", "See room timeline (devtools)": "See room timeline (devtools)", "Room": "Room", - "Space": "Space", "Manage & explore rooms": "Manage & explore rooms", "Move up": "Move up", "Move down": "Move down", @@ -3354,15 +3370,6 @@ "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", - "Navigation": "Navigation", - "Calls": "Calls", - "Room List": "Room List", - "Autocomplete": "Autocomplete", - "Alt": "Alt", - "Alt Gr": "Alt Gr", - "Shift": "Shift", - "Super": "Super", - "Ctrl": "Ctrl", "Toggle Bold": "Toggle Bold", "Toggle Italics": "Toggle Italics", "Toggle Quote": "Toggle Quote", @@ -3391,14 +3398,8 @@ "Activate selected button": "Activate selected button", "Toggle space panel": "Toggle space panel", "Toggle right panel": "Toggle right panel", - "Toggle this dialog": "Toggle this dialog", + "Open this settings tab": "Open this settings tab", "Go to Home View": "Go to Home View", "Move autocomplete selection up/down": "Move autocomplete selection up/down", - "Cancel autocomplete": "Cancel autocomplete", - "Page Up": "Page Up", - "Page Down": "Page Down", - "Esc": "Esc", - "Enter": "Enter", - "End": "End", - "[number]": "[number]" + "Cancel autocomplete": "Cancel autocomplete" }