Make the Keyboard Shortcuts dialog into a settings tab (#7198)

This commit is contained in:
Šimon Brandner 2022-01-17 12:53:10 +01:00 committed by GitHub
parent cb42173e11
commit 1f298250b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 191 additions and 155 deletions

View file

@ -95,7 +95,6 @@
@import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_InviteDialog.scss"; @import "./views/dialogs/_InviteDialog.scss";
@import "./views/dialogs/_JoinRuleDropdown.scss"; @import "./views/dialogs/_JoinRuleDropdown.scss";
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
@import "./views/dialogs/_LeaveSpaceDialog.scss"; @import "./views/dialogs/_LeaveSpaceDialog.scss";
@import "./views/dialogs/_LocationViewDialog.scss"; @import "./views/dialogs/_LocationViewDialog.scss";
@import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss"; @import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss";
@ -286,6 +285,7 @@
@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss"; @import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss";
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss"; @import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
@import "./views/settings/tabs/user/_HelpUserSettingsTab.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/_LabsUserSettingsTab.scss";
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss"; @import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; @import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss";

View file

@ -37,6 +37,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/settings/preference.svg'); 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 { .mx_UserSettingsDialog_sidebarIcon::before {
mask-image: url('$(res)/img/element-icons/settings/sidebar.svg'); mask-image: url('$(res)/img/element-icons/settings/sidebar.svg');
} }

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2020 The Matrix.org Foundation C.I.C. Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
.mx_KeyboardShortcutsDialog { .mx_KeyboardUserSettingsTab .mx_SettingsTab_section {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3333 3.33331H2.66659C1.93325 3.33331 1.33992 3.93331 1.33992 4.66665L1.33325 11.3333C1.33325 12.0666 1.93325 12.6666 2.66659 12.6666H13.3333C14.0666 12.6666 14.6666 12.0666 14.6666 11.3333V4.66665C14.6666 3.93331 14.0666 3.33331 13.3333 3.33331ZM7.33325 5.66665C7.33325 5.48255 7.48249 5.33331 7.66658 5.33331H8.33325C8.51735 5.33331 8.66658 5.48255 8.66658 5.66665V6.33331C8.66658 6.51741 8.51735 6.66665 8.33325 6.66665H7.66659C7.48249 6.66665 7.33325 6.51741 7.33325 6.33331V5.66665ZM7.33325 7.66665C7.33325 7.48255 7.48249 7.33331 7.66658 7.33331H8.33325C8.51735 7.33331 8.66658 7.48255 8.66658 7.66665V8.33331C8.66658 8.51741 8.51735 8.66665 8.33325 8.66665H7.66659C7.48249 8.66665 7.33325 8.51741 7.33325 8.33331V7.66665ZM5.33325 5.66665C5.33325 5.48255 5.48249 5.33331 5.66658 5.33331H6.33325C6.51735 5.33331 6.66658 5.48255 6.66658 5.66665V6.33331C6.66658 6.51741 6.51735 6.66665 6.33325 6.66665H5.66659C5.48249 6.66665 5.33325 6.51741 5.33325 6.33331V5.66665ZM5.33325 7.66665C5.33325 7.48255 5.48249 7.33331 5.66658 7.33331H6.33325C6.51735 7.33331 6.66658 7.48255 6.66658 7.66665V8.33331C6.66658 8.51741 6.51735 8.66665 6.33325 8.66665H5.66659C5.48249 8.66665 5.33325 8.51741 5.33325 8.33331V7.66665ZM4.66658 8.33331C4.66658 8.51741 4.51735 8.66665 4.33325 8.66665H3.66659C3.48249 8.66665 3.33325 8.51741 3.33325 8.33331V7.66665C3.33325 7.48255 3.48249 7.33331 3.66659 7.33331H4.33325C4.51735 7.33331 4.66658 7.48255 4.66658 7.66665V8.33331ZM4.66658 6.33331C4.66658 6.51741 4.51735 6.66665 4.33325 6.66665H3.66659C3.48249 6.66665 3.33325 6.51741 3.33325 6.33331V5.66665C3.33325 5.48255 3.48249 5.33331 3.66659 5.33331H4.33325C4.51735 5.33331 4.66658 5.48255 4.66658 5.66665V6.33331ZM10.6666 11C10.6666 11.1841 10.5173 11.3333 10.3333 11.3333H5.66659C5.48249 11.3333 5.33325 11.1841 5.33325 11V10.3333C5.33325 10.1492 5.48249 9.99998 5.66658 9.99998H10.3333C10.5173 9.99998 10.6666 10.1492 10.6666 10.3333V11ZM10.6666 8.33331C10.6666 8.51741 10.5173 8.66665 10.3333 8.66665H9.66658C9.48249 8.66665 9.33325 8.51741 9.33325 8.33331V7.66665C9.33325 7.48255 9.48249 7.33331 9.66658 7.33331H10.3333C10.5173 7.33331 10.6666 7.48255 10.6666 7.66665V8.33331ZM10.6666 6.33331C10.6666 6.51741 10.5173 6.66665 10.3333 6.66665H9.66658C9.48249 6.66665 9.33325 6.51741 9.33325 6.33331V5.66665C9.33325 5.48255 9.48249 5.33331 9.66658 5.33331H10.3333C10.5173 5.33331 10.6666 5.48255 10.6666 5.66665V6.33331ZM12.6666 8.33331C12.6666 8.51741 12.5173 8.66665 12.3333 8.66665H11.6666C11.4825 8.66665 11.3333 8.51741 11.3333 8.33331V7.66665C11.3333 7.48255 11.4825 7.33331 11.6666 7.33331H12.3333C12.5173 7.33331 12.6666 7.48255 12.6666 7.66665V8.33331ZM12.6666 6.33331C12.6666 6.51741 12.5173 6.66665 12.3333 6.66665H11.6666C11.4825 6.66665 11.3333 6.51741 11.3333 6.33331V5.66665C11.3333 5.48255 11.4825 5.33331 11.6666 5.33331H12.3333C12.5173 5.33331 12.6666 5.48255 12.6666 5.66665V6.33331Z" fill="#8D99A5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -355,14 +355,14 @@ const navigationBindings = (): KeyBinding<NavigationAction>[] => {
}, },
}, },
{ {
action: NavigationAction.ToggleShortCutDialog, action: NavigationAction.OpenShortCutDialog,
keyCombo: { keyCombo: {
key: Key.SLASH, key: Key.SLASH,
ctrlOrCmd: true, ctrlOrCmd: true,
}, },
}, },
{ {
action: NavigationAction.ToggleShortCutDialog, action: NavigationAction.OpenShortCutDialog,
keyCombo: { keyCombo: {
key: Key.SLASH, key: Key.SLASH,
ctrlOrCmd: true, ctrlOrCmd: true,

View file

@ -112,7 +112,7 @@ export enum NavigationAction {
/** Toggle the user menu */ /** Toggle the user menu */
ToggleUserMenu = 'ToggleUserMenu', ToggleUserMenu = 'ToggleUserMenu',
/** Toggle the short cut help dialog */ /** Toggle the short cut help dialog */
ToggleShortCutDialog = 'ToggleShortCutDialog', OpenShortCutDialog = 'OpenShortCutDialog',
/** Got to the Element home screen */ /** Got to the Element home screen */
GoToHome = 'GoToHome', GoToHome = 'GoToHome',
/** Select prev room */ /** Select prev room */

View file

@ -14,20 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as React from "react"; import { _td } from "../languageHandler";
import classNames from "classnames";
import Modal from "../Modal";
import { _t, _td } from "../languageHandler";
import { isMac, Key } from "../Keyboard"; 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 { export enum Categories {
NAVIGATION = "Navigation", NAVIGATION = "Navigation",
@ -38,13 +26,6 @@ export enum Categories {
AUTOCOMPLETE = "Autocomplete", 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 { export enum Modifiers {
ALT = "Alt", // Option on Mac and displayed as an Icon ALT = "Alt", // Option on Mac and displayed as an Icon
ALT_GR = "Alt Gr", ALT_GR = "Alt Gr",
@ -65,12 +46,12 @@ interface IKeybind {
key: string; // TS: fix this once Key is an enum key: string; // TS: fix this once Key is an enum
} }
interface IShortcut { export interface IShortcut {
keybinds: IKeybind[]; keybinds: IKeybind[];
description: string; description: string;
} }
const shortcuts: Record<Categories, IShortcut[]> = { export const shortcuts: Record<Categories, IShortcut[]> = {
[Categories.COMPOSER]: [ [Categories.COMPOSER]: [
{ {
keybinds: [{ keybinds: [{
@ -270,7 +251,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
modifiers: [CMD_OR_CTRL], modifiers: [CMD_OR_CTRL],
key: Key.SLASH, key: Key.SLASH,
}], }],
description: _td("Toggle this dialog"), description: _td("Open this settings tab"),
}, { }, {
keybinds: [{ keybinds: [{
modifiers: [Modifiers.CONTROL, isMac ? Modifiers.SHIFT : Modifiers.ALT], modifiers: [Modifiers.CONTROL, isMac ? Modifiers.SHIFT : Modifiers.ALT],
@ -297,107 +278,6 @@ const shortcuts: Record<Categories, IShortcut[]> = {
], ],
}; };
const categoryOrder = [
Categories.COMPOSER,
Categories.AUTOCOMPLETE,
Categories.ROOM,
Categories.ROOM_LIST,
Categories.NAVIGATION,
Categories.CALLS,
];
interface IModal {
close: () => void;
finished: Promise<any[]>;
}
const modifierIcon: Record<string, string> = {
[Modifiers.COMMAND]: "⌘",
};
if (isMac) {
modifierIcon[Modifiers.ALT] = "⌥";
}
const alternateKeyName: Record<string, string> = {
[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<string, string> = {
[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 <div className={classes}>
<h5>{ _t(shortcut.description) }</h5>
{ 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 <div key={s.key}>
{ s.modifiers?.map(m => {
return <React.Fragment key={m}>
<kbd>{ modifierIcon[m] || _t(m) }</kbd>+
</React.Fragment>;
}) }
<kbd>{ text }</kbd>
</div>;
}) }
</div>;
};
let activeModal: IModal = null;
export const toggleDialog = () => {
if (activeModal) {
activeModal.close();
activeModal = null;
return;
}
const sections = categoryOrder.map(category => {
const list = shortcuts[category];
return <div className="mx_KeyboardShortcutsDialog_category" key={category}>
<h3>{ _t(category) }</h3>
<div>{ list.map(shortcut => <Shortcut key={shortcut.description} shortcut={shortcut} />) }</div>
</div>;
});
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) => { export const registerShortcut = (category: Categories, defn: IShortcut) => {
shortcuts[category].push(defn); shortcuts[category].push(defn);
}; };

View file

@ -32,7 +32,6 @@ import SettingsStore from "../../settings/SettingsStore";
import ResizeHandle from '../views/elements/ResizeHandle'; import ResizeHandle from '../views/elements/ResizeHandle';
import { CollapseDistributor, Resizer } from '../../resizer'; import { CollapseDistributor, Resizer } from '../../resizer';
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
import HomePage from "./HomePage"; import HomePage from "./HomePage";
import ResizeNotifier from "../../utils/ResizeNotifier"; import ResizeNotifier from "../../utils/ResizeNotifier";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
@ -67,6 +66,8 @@ import GroupFilterPanel from './GroupFilterPanel';
import CustomRoomTagPanel from './CustomRoomTagPanel'; import CustomRoomTagPanel from './CustomRoomTagPanel';
import { mediaFromMxc } from "../../customisations/Media"; import { mediaFromMxc } from "../../customisations/Media";
import LegacyCommunityPreview from "./LegacyCommunityPreview"; import LegacyCommunityPreview from "./LegacyCommunityPreview";
import { UserTab } from "../views/dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from '../../stores/right-panel/RightPanelStore'; import RightPanelStore from '../../stores/right-panel/RightPanelStore';
// 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)
@ -472,8 +473,11 @@ class LoggedInView extends React.Component<IProps, IState> {
dis.fire(Action.ToggleUserMenu); dis.fire(Action.ToggleUserMenu);
handled = true; handled = true;
break; break;
case NavigationAction.ToggleShortCutDialog: case NavigationAction.OpenShortCutDialog:
KeyboardShortcuts.toggleDialog(); dis.dispatch<OpenToTabPayload>({
action: Action.ViewUserSettings,
initialTabId: UserTab.Keyboard,
});
handled = true; handled = true;
break; break;
case NavigationAction.GoToHome: case NavigationAction.GoToHome:

View file

@ -36,6 +36,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import { IDialogProps } from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab"; import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
export enum UserTab { export enum UserTab {
General = "USER_GENERAL_TAB", General = "USER_GENERAL_TAB",
@ -43,6 +44,7 @@ export enum UserTab {
Flair = "USER_FLAIR_TAB", Flair = "USER_FLAIR_TAB",
Notifications = "USER_NOTIFICATIONS_TAB", Notifications = "USER_NOTIFICATIONS_TAB",
Preferences = "USER_PREFERENCES_TAB", Preferences = "USER_PREFERENCES_TAB",
Keyboard = "USER_KEYBOARD_TAB",
Sidebar = "USER_SIDEBAR_TAB", Sidebar = "USER_SIDEBAR_TAB",
Voice = "USER_VOICE_TAB", Voice = "USER_VOICE_TAB",
Security = "USER_SECURITY_TAB", Security = "USER_SECURITY_TAB",
@ -119,6 +121,12 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
"mx_UserSettingsDialog_preferencesIcon", "mx_UserSettingsDialog_preferencesIcon",
<PreferencesUserSettingsTab closeSettingsFn={this.props.onFinished} />, <PreferencesUserSettingsTab closeSettingsFn={this.props.onFinished} />,
)); ));
tabs.push(new Tab(
UserTab.Keyboard,
_td("Keyboard"),
"mx_UserSettingsDialog_keyboardIcon",
<KeyboardUserSettingsTab />,
));
if (SettingsStore.getValue("feature_spaces_metaspaces")) { if (SettingsStore.getValue("feature_spaces_metaspaces")) {
tabs.push(new Tab( tabs.push(new Tab(

View file

@ -25,7 +25,6 @@ import SdkConfig from "../../../../../SdkConfig";
import createRoom from "../../../../../createRoom"; import createRoom from "../../../../../createRoom";
import Modal from "../../../../../Modal"; import Modal from "../../../../../Modal";
import PlatformPeg from "../../../../../PlatformPeg"; import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton"; import UpdateCheckButton from "../../UpdateCheckButton";
import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { copyPlaintext } from "../../../../../utils/strings"; import { copyPlaintext } from "../../../../../utils/strings";
@ -33,6 +32,10 @@ import * as ContextMenu from "../../../../structures/ContextMenu";
import { toRightOf } from "../../../../structures/ContextMenu"; import { toRightOf } from "../../../../structures/ContextMenu";
import BugReportDialog from '../../../dialogs/BugReportDialog'; import BugReportDialog from '../../../dialogs/BugReportDialog';
import GenericTextContextMenu from "../../../context_menus/GenericTextContextMenu"; 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 { interface IProps {
closeSettingsFn: () => void; closeSettingsFn: () => void;
@ -211,6 +214,13 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
this.copy(`${appVersion}\n${olmVersion}`, e); this.copy(`${appVersion}\n${olmVersion}`, e);
}; };
private onKeyboardShortcutsClicked = (): void => {
dis.dispatch<OpenToTabPayload>({
action: Action.ViewUserSettings,
initialTabId: UserTab.Keyboard,
});
};
render() { render() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
@ -307,7 +317,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
<div className='mx_SettingsTab_subsectionText'> <div className='mx_SettingsTab_subsectionText'>
{ faqText } { faqText }
</div> </div>
<AccessibleButton kind="primary" onClick={KeyboardShortcuts.toggleDialog}> <AccessibleButton kind="primary" onClick={this.onKeyboardShortcutsClicked}>
{ _t("Keyboard Shortcuts") } { _t("Keyboard Shortcuts") }
</AccessibleButton> </AccessibleButton>
</div> </div>

View file

@ -0,0 +1,117 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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<string, string> = {
[Modifiers.COMMAND]: "⌘",
};
if (isMac) {
modifierIcon[Modifiers.ALT] = "⌥";
}
const alternateKeyName: Record<string, string> = {
[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<string, string> = {
[Key.ARROW_UP]: "↑",
[Key.ARROW_DOWN]: "↓",
[Key.ARROW_LEFT]: "←",
[Key.ARROW_RIGHT]: "→",
};
interface IShortcutProps {
shortcut: IShortcut;
}
const Shortcut: React.FC<IShortcutProps> = ({ shortcut }) => {
const classes = classNames({
"mx_KeyboardShortcutsDialog_inline": shortcut.keybinds.every(k => !k.modifiers || k.modifiers.length === 0),
});
return <div className={classes}>
<h5>{ _t(shortcut.description) }</h5>
{ 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 <div key={s.key}>
{ s.modifiers && s.modifiers.map(m => {
return <React.Fragment key={m}>
<kbd>{ modifierIcon[m] || _t(m) }</kbd>+
</React.Fragment>;
}) }
<kbd>{ text }</kbd>
</div>;
}) }
</div>;
};
const KeyboardUserSettingsTab: React.FC = () => {
return <div className="mx_SettingsTab mx_KeyboardUserSettingsTab">
<div className="mx_SettingsTab_heading">{ _t("Keyboard") }</div>
<div className="mx_SettingsTab_section">
{ categoryOrder.map(category => {
const list = shortcuts[category];
return <div className="mx_KeyboardShortcutsDialog_category" key={category}>
<h3>{ _t(category) }</h3>
<div>{ list.map(shortcut => <Shortcut key={shortcut.description} shortcut={shortcut} />) }</div>
</div>;
}) }
</div>
</div>;
};
export default KeyboardUserSettingsTab;

View file

@ -26,7 +26,6 @@ import PlatformPeg from "../../../../../PlatformPeg";
import { SettingLevel } from "../../../../../settings/SettingLevel"; import { SettingLevel } from "../../../../../settings/SettingLevel";
import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import SettingsFlag from '../../../elements/SettingsFlag'; import SettingsFlag from '../../../elements/SettingsFlag';
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import GroupAvatar from "../../../avatars/GroupAvatar"; import GroupAvatar from "../../../avatars/GroupAvatar";
import dis from "../../../../../dispatcher/dispatcher"; import dis from "../../../../../dispatcher/dispatcher";
@ -36,6 +35,8 @@ import { useDispatcher } from "../../../../../hooks/useDispatcher";
import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog"; import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog";
import { createSpaceFromCommunity } from "../../../../../utils/space"; import { createSpaceFromCommunity } from "../../../../../utils/space";
import Spinner from "../../../elements/Spinner"; import Spinner from "../../../elements/Spinner";
import { UserTab } from "../../../dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
import { Action } from "../../../../../dispatcher/actions"; import { Action } from "../../../../../dispatcher/actions";
interface IProps { interface IProps {
@ -296,6 +297,13 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
}); });
} }
private onKeyboardShortcutsClicked = (): void => {
dis.dispatch<OpenToTabPayload>({
action: Action.ViewUserSettings,
initialTabId: UserTab.Keyboard,
});
};
getShowLocationIfEnabled(): string[] { getShowLocationIfEnabled(): string[] {
// TODO: when location sharing is out of labs, this can be deleted and // TODO: when location sharing is out of labs, this can be deleted and
// we can just add this to COMPOSER_SETTINGS // we can just add this to COMPOSER_SETTINGS
@ -372,7 +380,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
<span className="mx_SettingsTab_subheading">{ _t("Keyboard shortcuts") }</span> <span className="mx_SettingsTab_subheading">{ _t("Keyboard shortcuts") }</span>
<div className="mx_SettingsFlag"> <div className="mx_SettingsFlag">
{ _t("To view all keyboard shortcuts, <a>click here</a>.", {}, { { _t("To view all keyboard shortcuts, <a>click here</a>.", {}, {
a: sub => <AccessibleButton kind="link" onClick={KeyboardShortcuts.toggleDialog}> a: sub => <AccessibleButton kind="link" onClick={this.onKeyboardShortcutsClicked}>
{ sub } { sub }
</AccessibleButton>, </AccessibleButton>,
}) } }) }

View file

@ -1428,6 +1428,24 @@
"Access Token": "Access Token", "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.", "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", "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", "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. <a>Learn more</a>.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.", "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.",
"Ignored/Blocked": "Ignored/Blocked", "Ignored/Blocked": "Ignored/Blocked",
@ -1478,7 +1496,6 @@
"Keyboard shortcuts": "Keyboard shortcuts", "Keyboard shortcuts": "Keyboard shortcuts",
"To view all keyboard shortcuts, <a>click here</a>.": "To view all keyboard shortcuts, <a>click here</a>.", "To view all keyboard shortcuts, <a>click here</a>.": "To view all keyboard shortcuts, <a>click here</a>.",
"Displaying time": "Displaying time", "Displaying time": "Displaying time",
"Composer": "Composer",
"Code blocks": "Code blocks", "Code blocks": "Code blocks",
"Images, GIFs and videos": "Images, GIFs and videos", "Images, GIFs and videos": "Images, GIFs and videos",
"Timeline": "Timeline", "Timeline": "Timeline",
@ -2868,7 +2885,6 @@
"Mentions only": "Mentions only", "Mentions only": "Mentions only",
"See room timeline (devtools)": "See room timeline (devtools)", "See room timeline (devtools)": "See room timeline (devtools)",
"Room": "Room", "Room": "Room",
"Space": "Space",
"Manage & explore rooms": "Manage & explore rooms", "Manage & explore rooms": "Manage & explore rooms",
"Move up": "Move up", "Move up": "Move up",
"Move down": "Move down", "Move down": "Move down",
@ -3354,15 +3370,6 @@
"Failed to set direct chat tag": "Failed to set direct chat tag", "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 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", "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 Bold": "Toggle Bold",
"Toggle Italics": "Toggle Italics", "Toggle Italics": "Toggle Italics",
"Toggle Quote": "Toggle Quote", "Toggle Quote": "Toggle Quote",
@ -3391,14 +3398,8 @@
"Activate selected button": "Activate selected button", "Activate selected button": "Activate selected button",
"Toggle space panel": "Toggle space panel", "Toggle space panel": "Toggle space panel",
"Toggle right panel": "Toggle right 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", "Go to Home View": "Go to Home View",
"Move autocomplete selection up/down": "Move autocomplete selection up/down", "Move autocomplete selection up/down": "Move autocomplete selection up/down",
"Cancel autocomplete": "Cancel autocomplete", "Cancel autocomplete": "Cancel autocomplete"
"Page Up": "Page Up",
"Page Down": "Page Down",
"Esc": "Esc",
"Enter": "Enter",
"End": "End",
"[number]": "[number]"
} }