Improve the look of the keyboard settings tab (#7562)

* First cut of new keyboard shortcuts

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Remove unused code

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* i18n

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Amend shortcuts

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Improve CATEGORIES struct

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Add tests for registerShortcut()

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Simplifie code tiny bit

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Translate ALTERNATE_KEY_NAME

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Fix `key` usage

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Export components for tests

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Write snapshot tests

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2022-01-24 12:33:27 +01:00 committed by GitHub
parent 55ec1bdc85
commit 5f18e4888c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 932 additions and 383 deletions

View file

@ -1,6 +1,6 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
Copyright 2021 - 2022 Š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.
@ -15,102 +15,94 @@ 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 {
KEYBOARD_SHORTCUTS,
ALTERNATE_KEY_NAME,
KEY_ICON,
ICategory,
CATEGORIES,
CategoryName,
} from "../../../../../accessibility/KeyboardShortcuts";
import { isMac, Key } from "../../../../../Keyboard";
import { _t, _td } from "../../../../../languageHandler";
import { _t } 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] = "⌥";
interface IKeyboardKeyProps {
name: string;
last?: boolean;
}
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]: "→",
export const KeyboardKey: React.FC<IKeyboardKeyProps> = ({ name, last }) => {
const icon = KEY_ICON[name];
const alternateName = ALTERNATE_KEY_NAME[name];
return <React.Fragment>
<kbd> { icon || (alternateName && _t(alternateName)) || name } </kbd>
{ !last && "+" }
</React.Fragment>;
};
interface IShortcutProps {
shortcut: IShortcut;
interface IKeyboardShortcutProps {
name: string;
}
const Shortcut: React.FC<IShortcutProps> = ({ shortcut }) => {
const classes = classNames({
"mx_KeyboardShortcutsDialog_inline": shortcut.keybinds.every(k => !k.modifiers || k.modifiers.length === 0),
});
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ name }) => {
const value = KEYBOARD_SHORTCUTS[name]?.default;
if (!value) return null;
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];
}
const modifiersElement = [];
if (value.ctrlOrCmdKey) {
modifiersElement.push(<KeyboardKey key="ctrlOrCmdKey" name={isMac ? Key.META : Key.CONTROL} />);
} else if (value.ctrlKey) {
modifiersElement.push(<KeyboardKey key="ctrlKey" name={Key.CONTROL} />);
} else if (value.metaKey) {
modifiersElement.push(<KeyboardKey key="metaKey" name={Key.META} />);
}
if (value.altKey) {
modifiersElement.push(<KeyboardKey key="altKey" name={Key.ALT} />);
}
if (value.shiftKey) {
modifiersElement.push(<KeyboardKey key="shiftKey" name={Key.SHIFT} />);
}
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>;
}) }
return <div>
{ modifiersElement }
<KeyboardKey name={value.key} last />
</div>;
};
interface IKeyboardShortcutRowProps {
name: string;
}
const KeyboardShortcutRow: React.FC<IKeyboardShortcutRowProps> = ({ name }) => {
return <div className="mx_KeyboardShortcut_shortcutRow">
{ KEYBOARD_SHORTCUTS[name].displayName }
<KeyboardShortcut name={name} />
</div>;
};
interface IKeyboardShortcutSectionProps {
categoryName: CategoryName;
category: ICategory;
}
const KeyboardShortcutSection: React.FC<IKeyboardShortcutSectionProps> = ({ categoryName, category }) => {
return <div className="mx_SettingsTab_section" key={categoryName}>
<div className="mx_SettingsTab_subheading">{ _t(category.categoryLabel) }</div>
<div> { category.settingNames.map((shortcutName) => {
return <KeyboardShortcutRow key={shortcutName} name={shortcutName} />;
}) } </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>
{ Object.entries(CATEGORIES).map(([categoryName, category]: [CategoryName, ICategory]) => {
return <KeyboardShortcutSection key={categoryName} categoryName={categoryName} category={category} />;
}) }
</div>;
};