parent
e725f14651
commit
8d13e238b9
10 changed files with 462 additions and 351 deletions
|
@ -26,9 +26,9 @@ import {
|
||||||
import {
|
import {
|
||||||
CATEGORIES,
|
CATEGORIES,
|
||||||
CategoryName,
|
CategoryName,
|
||||||
getKeyboardShortcuts,
|
|
||||||
KeyBindingAction,
|
KeyBindingAction,
|
||||||
} from "./accessibility/KeyboardShortcuts";
|
} from "./accessibility/KeyboardShortcuts";
|
||||||
|
import { getKeyboardShortcuts } from "./accessibility/KeyboardShortcutUtils";
|
||||||
|
|
||||||
export const getBindingsByCategory = (category: CategoryName): KeyBinding[] => {
|
export const getBindingsByCategory = (category: CategoryName): KeyBinding[] => {
|
||||||
return CATEGORIES[category].settingNames.reduce((bindings, name) => {
|
return CATEGORIES[category].settingNames.reduce((bindings, name) => {
|
||||||
|
|
131
src/accessibility/KeyboardShortcutUtils.ts
Normal file
131
src/accessibility/KeyboardShortcutUtils.ts
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
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 { KeyCombo } from "../KeyBindingsManager";
|
||||||
|
import { isMac, Key } from "../Keyboard";
|
||||||
|
import { _t, _td } from "../languageHandler";
|
||||||
|
import PlatformPeg from "../PlatformPeg";
|
||||||
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
import {
|
||||||
|
DESKTOP_SHORTCUTS,
|
||||||
|
DIGITS,
|
||||||
|
IKeyboardShortcuts,
|
||||||
|
KeyBindingAction,
|
||||||
|
KEYBOARD_SHORTCUTS,
|
||||||
|
MAC_ONLY_SHORTCUTS,
|
||||||
|
} from "./KeyboardShortcuts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function gets the keyboard shortcuts that should be presented in the UI
|
||||||
|
* but they shouldn't be consumed by KeyBindingDefaults. That means that these
|
||||||
|
* have to be manually mirrored in KeyBindingDefaults.
|
||||||
|
*/
|
||||||
|
const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
|
||||||
|
const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');
|
||||||
|
|
||||||
|
const keyboardShortcuts: IKeyboardShortcuts = {
|
||||||
|
[KeyBindingAction.SendMessage]: {
|
||||||
|
default: {
|
||||||
|
key: Key.ENTER,
|
||||||
|
ctrlOrCmdKey: ctrlEnterToSend,
|
||||||
|
},
|
||||||
|
displayName: _td("Send message"),
|
||||||
|
},
|
||||||
|
[KeyBindingAction.NewLine]: {
|
||||||
|
default: {
|
||||||
|
key: Key.ENTER,
|
||||||
|
shiftKey: !ctrlEnterToSend,
|
||||||
|
},
|
||||||
|
displayName: _td("New line"),
|
||||||
|
},
|
||||||
|
[KeyBindingAction.CompleteAutocomplete]: {
|
||||||
|
default: {
|
||||||
|
key: Key.ENTER,
|
||||||
|
},
|
||||||
|
displayName: _td("Complete"),
|
||||||
|
},
|
||||||
|
[KeyBindingAction.ForceCompleteAutocomplete]: {
|
||||||
|
default: {
|
||||||
|
key: Key.TAB,
|
||||||
|
},
|
||||||
|
displayName: _td("Force complete"),
|
||||||
|
},
|
||||||
|
[KeyBindingAction.SearchInRoom]: {
|
||||||
|
default: {
|
||||||
|
ctrlOrCmdKey: true,
|
||||||
|
key: Key.F,
|
||||||
|
},
|
||||||
|
displayName: _td("Search (must be enabled)"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (PlatformPeg.get().overrideBrowserShortcuts()) {
|
||||||
|
// XXX: This keyboard shortcut isn't manually added to
|
||||||
|
// KeyBindingDefaults as it can't be easily handled by the
|
||||||
|
// KeyBindingManager
|
||||||
|
keyboardShortcuts[KeyBindingAction.SwitchToSpaceByNumber] = {
|
||||||
|
default: {
|
||||||
|
ctrlOrCmdKey: true,
|
||||||
|
key: DIGITS,
|
||||||
|
},
|
||||||
|
displayName: _td("Switch to space by number"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyboardShortcuts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults.
|
||||||
|
*/
|
||||||
|
export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
||||||
|
const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts();
|
||||||
|
|
||||||
|
return Object.keys(KEYBOARD_SHORTCUTS).filter((k: KeyBindingAction) => {
|
||||||
|
if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
|
||||||
|
if (MAC_ONLY_SHORTCUTS.includes(k) && !isMac) return false;
|
||||||
|
if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).reduce((o, key) => {
|
||||||
|
o[key] = KEYBOARD_SHORTCUTS[key];
|
||||||
|
return o;
|
||||||
|
}, {} as IKeyboardShortcuts);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
||||||
|
*/
|
||||||
|
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
||||||
|
const entries = [
|
||||||
|
...Object.entries(getUIOnlyShortcuts()),
|
||||||
|
...Object.entries(getKeyboardShortcuts()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return entries.reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value;
|
||||||
|
return acc;
|
||||||
|
}, {} as IKeyboardShortcuts);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKeyboardShortcutValue = (name: string): KeyCombo => {
|
||||||
|
return getKeyboardShortcutsForUI()[name]?.default;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKeyboardShortcutDisplayName = (name: string): string | null => {
|
||||||
|
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
|
||||||
|
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
|
||||||
|
};
|
|
@ -18,9 +18,8 @@ limitations under the License.
|
||||||
import { _td } from "../languageHandler";
|
import { _td } from "../languageHandler";
|
||||||
import { isMac, Key } from "../Keyboard";
|
import { isMac, Key } from "../Keyboard";
|
||||||
import { IBaseSetting } from "../settings/Settings";
|
import { IBaseSetting } from "../settings/Settings";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
|
||||||
import IncompatibleController from "../settings/controllers/IncompatibleController";
|
import IncompatibleController from "../settings/controllers/IncompatibleController";
|
||||||
import PlatformPeg from "../PlatformPeg";
|
import { KeyCombo } from "../KeyBindingsManager";
|
||||||
|
|
||||||
export enum KeyBindingAction {
|
export enum KeyBindingAction {
|
||||||
/** Send a message */
|
/** Send a message */
|
||||||
|
@ -151,18 +150,9 @@ export enum KeyBindingAction {
|
||||||
ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility',
|
ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KeyBindingConfig = {
|
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
|
||||||
key: string;
|
|
||||||
ctrlOrCmdKey?: boolean;
|
|
||||||
ctrlKey?: boolean;
|
|
||||||
altKey?: boolean;
|
|
||||||
shiftKey?: boolean;
|
|
||||||
metaKey?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type KeyboardShortcutSetting = IBaseSetting<KeyBindingConfig>;
|
export type IKeyboardShortcuts = {
|
||||||
|
|
||||||
type IKeyboardShortcuts = {
|
|
||||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||||
[k in (KeyBindingAction)]?: KeyboardShortcutSetting;
|
[k in (KeyBindingAction)]?: KeyboardShortcutSetting;
|
||||||
};
|
};
|
||||||
|
@ -310,14 +300,14 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DESKTOP_SHORTCUTS = [
|
export const DESKTOP_SHORTCUTS = [
|
||||||
KeyBindingAction.OpenUserSettings,
|
KeyBindingAction.OpenUserSettings,
|
||||||
KeyBindingAction.SwitchToSpaceByNumber,
|
KeyBindingAction.SwitchToSpaceByNumber,
|
||||||
KeyBindingAction.PreviousVisitedRoomOrCommunity,
|
KeyBindingAction.PreviousVisitedRoomOrCommunity,
|
||||||
KeyBindingAction.NextVisitedRoomOrCommunity,
|
KeyBindingAction.NextVisitedRoomOrCommunity,
|
||||||
];
|
];
|
||||||
|
|
||||||
const MAC_ONLY_SHORTCUTS = [
|
export const MAC_ONLY_SHORTCUTS = [
|
||||||
KeyBindingAction.OpenUserSettings,
|
KeyBindingAction.OpenUserSettings,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -710,99 +700,6 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* This function gets the keyboard shortcuts that should be presented in the UI
|
|
||||||
* but they shouldn't be consumed by KeyBindingDefaults. That means that these
|
|
||||||
* have to be manually mirrored in KeyBindingDefaults.
|
|
||||||
*/
|
|
||||||
const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
|
|
||||||
const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');
|
|
||||||
|
|
||||||
const keyboardShortcuts: IKeyboardShortcuts = {
|
|
||||||
[KeyBindingAction.SendMessage]: {
|
|
||||||
default: {
|
|
||||||
key: Key.ENTER,
|
|
||||||
ctrlOrCmdKey: ctrlEnterToSend,
|
|
||||||
},
|
|
||||||
displayName: _td("Send message"),
|
|
||||||
},
|
|
||||||
[KeyBindingAction.NewLine]: {
|
|
||||||
default: {
|
|
||||||
key: Key.ENTER,
|
|
||||||
shiftKey: !ctrlEnterToSend,
|
|
||||||
},
|
|
||||||
displayName: _td("New line"),
|
|
||||||
},
|
|
||||||
[KeyBindingAction.CompleteAutocomplete]: {
|
|
||||||
default: {
|
|
||||||
key: Key.ENTER,
|
|
||||||
},
|
|
||||||
displayName: _td("Complete"),
|
|
||||||
},
|
|
||||||
[KeyBindingAction.ForceCompleteAutocomplete]: {
|
|
||||||
default: {
|
|
||||||
key: Key.TAB,
|
|
||||||
},
|
|
||||||
displayName: _td("Force complete"),
|
|
||||||
},
|
|
||||||
[KeyBindingAction.SearchInRoom]: {
|
|
||||||
default: {
|
|
||||||
ctrlOrCmdKey: true,
|
|
||||||
key: Key.F,
|
|
||||||
},
|
|
||||||
displayName: _td("Search (must be enabled)"),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (PlatformPeg.get().overrideBrowserShortcuts()) {
|
|
||||||
// XXX: This keyboard shortcut isn't manually added to
|
|
||||||
// KeyBindingDefaults as it can't be easily handled by the
|
|
||||||
// KeyBindingManager
|
|
||||||
keyboardShortcuts[KeyBindingAction.SwitchToSpaceByNumber] = {
|
|
||||||
default: {
|
|
||||||
ctrlOrCmdKey: true,
|
|
||||||
key: DIGITS,
|
|
||||||
},
|
|
||||||
displayName: _td("Switch to space by number"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyboardShortcuts;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults.
|
|
||||||
*/
|
|
||||||
export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
|
||||||
const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts();
|
|
||||||
|
|
||||||
return Object.keys(KEYBOARD_SHORTCUTS).filter((k: KeyBindingAction) => {
|
|
||||||
if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
|
|
||||||
if (MAC_ONLY_SHORTCUTS.includes(k) && !isMac) return false;
|
|
||||||
if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}).reduce((o, key) => {
|
|
||||||
o[key] = KEYBOARD_SHORTCUTS[key];
|
|
||||||
return o;
|
|
||||||
}, {} as IKeyboardShortcuts);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
|
||||||
*/
|
|
||||||
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
|
||||||
const entries = [
|
|
||||||
...Object.entries(getUIOnlyShortcuts()),
|
|
||||||
...Object.entries(getKeyboardShortcuts()),
|
|
||||||
];
|
|
||||||
|
|
||||||
return entries.reduce((acc, [key, value]) => {
|
|
||||||
acc[key] = value;
|
|
||||||
return acc;
|
|
||||||
}, {} as IKeyboardShortcuts);
|
|
||||||
};
|
|
||||||
|
|
||||||
// For tests
|
// For tests
|
||||||
export function mock({ keyboardShortcuts, macOnlyShortcuts, desktopShortcuts }): void {
|
export function mock({ keyboardShortcuts, macOnlyShortcuts, desktopShortcuts }): void {
|
||||||
Object.keys(KEYBOARD_SHORTCUTS).forEach((k) => delete KEYBOARD_SHORTCUTS[k]);
|
Object.keys(KEYBOARD_SHORTCUTS).forEach((k) => delete KEYBOARD_SHORTCUTS[k]);
|
||||||
|
|
67
src/components/views/settings/KeyboardShortcut.tsx
Normal file
67
src/components/views/settings/KeyboardShortcut.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
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 React from "react";
|
||||||
|
|
||||||
|
import { ALTERNATE_KEY_NAME, KEY_ICON } from "../../../accessibility/KeyboardShortcuts";
|
||||||
|
import { KeyCombo } from "../../../KeyBindingsManager";
|
||||||
|
import { isMac, Key } from "../../../Keyboard";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
|
interface IKeyboardKeyProps {
|
||||||
|
name: string;
|
||||||
|
last?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 IKeyboardShortcutProps {
|
||||||
|
value: KeyCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value }) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
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>
|
||||||
|
{ modifiersElement }
|
||||||
|
<KeyboardKey name={value.key} last />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeyboardShortcut;
|
|
@ -18,71 +18,16 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getKeyboardShortcutsForUI,
|
|
||||||
ALTERNATE_KEY_NAME,
|
|
||||||
KEY_ICON,
|
|
||||||
ICategory,
|
ICategory,
|
||||||
CATEGORIES,
|
CATEGORIES,
|
||||||
CategoryName,
|
CategoryName,
|
||||||
KeyBindingConfig,
|
|
||||||
} from "../../../../../accessibility/KeyboardShortcuts";
|
} from "../../../../../accessibility/KeyboardShortcuts";
|
||||||
import SdkConfig from "../../../../../SdkConfig";
|
import SdkConfig from "../../../../../SdkConfig";
|
||||||
import { isMac, Key } from "../../../../../Keyboard";
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
|
import {
|
||||||
// TODO: This should return KeyCombo but it has ctrlOrCmd instead of ctrlOrCmdKey
|
getKeyboardShortcutDisplayName, getKeyboardShortcutValue,
|
||||||
const getKeyboardShortcutValue = (name: string): KeyBindingConfig => {
|
} from "../../../../../accessibility/KeyboardShortcutUtils";
|
||||||
return getKeyboardShortcutsForUI()[name]?.default;
|
import { KeyboardShortcut } from "../../KeyboardShortcut";
|
||||||
};
|
|
||||||
|
|
||||||
const getKeyboardShortcutDisplayName = (name: string): string | null => {
|
|
||||||
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
|
|
||||||
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IKeyboardKeyProps {
|
|
||||||
name: string;
|
|
||||||
last?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 IKeyboardShortcutProps {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ name }) => {
|
|
||||||
const value = getKeyboardShortcutValue(name);
|
|
||||||
if (!value) return null;
|
|
||||||
|
|
||||||
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>
|
|
||||||
{ modifiersElement }
|
|
||||||
<KeyboardKey name={value.key} last />
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IKeyboardShortcutRowProps {
|
interface IKeyboardShortcutRowProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -94,11 +39,12 @@ const visibleCategories = Object.entries(CATEGORIES).filter(([categoryName]) =>
|
||||||
|
|
||||||
const KeyboardShortcutRow: React.FC<IKeyboardShortcutRowProps> = ({ name }) => {
|
const KeyboardShortcutRow: React.FC<IKeyboardShortcutRowProps> = ({ name }) => {
|
||||||
const displayName = getKeyboardShortcutDisplayName(name);
|
const displayName = getKeyboardShortcutDisplayName(name);
|
||||||
if (!displayName) return null;
|
const value = getKeyboardShortcutValue(name);
|
||||||
|
if (!displayName || !value) return null;
|
||||||
|
|
||||||
return <div className="mx_KeyboardShortcut_shortcutRow">
|
return <div className="mx_KeyboardShortcut_shortcutRow">
|
||||||
{ displayName }
|
{ displayName }
|
||||||
<KeyboardShortcut name={name} />
|
<KeyboardShortcut value={value} />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getKeyboardShortcutsForUI,
|
|
||||||
getKeyboardShortcuts,
|
|
||||||
KEYBOARD_SHORTCUTS,
|
KEYBOARD_SHORTCUTS,
|
||||||
mock,
|
mock,
|
||||||
} from "../../src/accessibility/KeyboardShortcuts";
|
} from "../../src/accessibility/KeyboardShortcuts";
|
||||||
|
import { getKeyboardShortcuts, getKeyboardShortcutsForUI } from "../../src/accessibility/KeyboardShortcutUtils";
|
||||||
import PlatformPeg from "../../src/PlatformPeg";
|
import PlatformPeg from "../../src/PlatformPeg";
|
||||||
|
|
||||||
describe("KeyboardShortcuts", () => {
|
describe("KeyboardShortcutUtils", () => {
|
||||||
it("doesn't change KEYBOARD_SHORTCUTS when getting shortcuts", async () => {
|
it("doesn't change KEYBOARD_SHORTCUTS when getting shortcuts", async () => {
|
||||||
mock({
|
mock({
|
||||||
keyboardShortcuts: {
|
keyboardShortcuts: {
|
72
test/components/views/settings/KeyboardShortcut-test.tsx
Normal file
72
test/components/views/settings/KeyboardShortcut-test.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
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 React from "react";
|
||||||
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
|
|
||||||
|
import { Key } from "../../../../src/Keyboard";
|
||||||
|
import PlatformPeg from "../../../../src/PlatformPeg";
|
||||||
|
|
||||||
|
const PATH_TO_COMPONENT = "../../../../src/components/views/settings/KeyboardShortcut.tsx";
|
||||||
|
|
||||||
|
const renderKeyboardShortcut = async (component, props?): Promise<ReactWrapper> => {
|
||||||
|
const Component = (await import(PATH_TO_COMPONENT))[component];
|
||||||
|
return mount(<Component {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("KeyboardShortcut", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders key icon", async () => {
|
||||||
|
const body = await renderKeyboardShortcut("KeyboardKey", { name: Key.ARROW_DOWN });
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders alternative key name", async () => {
|
||||||
|
const body = await renderKeyboardShortcut("KeyboardKey", { name: Key.PAGE_DOWN });
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render + if last", async () => {
|
||||||
|
const body = await renderKeyboardShortcut("KeyboardKey", { name: Key.A, last: true });
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render same modifier twice", async () => {
|
||||||
|
PlatformPeg.get = () => ({ overrideBrowserShortcuts: () => false });
|
||||||
|
const body1 = await renderKeyboardShortcut("KeyboardShortcut", {
|
||||||
|
value: {
|
||||||
|
key: Key.A,
|
||||||
|
ctrlOrCmdKey: true,
|
||||||
|
metaKey: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(body1).toMatchSnapshot();
|
||||||
|
|
||||||
|
const body2 = await renderKeyboardShortcut("KeyboardShortcut", {
|
||||||
|
value: {
|
||||||
|
key: Key.A,
|
||||||
|
ctrlOrCmdKey: true,
|
||||||
|
ctrlKey: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(body2).toMatchSnapshot();
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`KeyboardShortcut doesn't render + if last 1`] = `
|
||||||
|
<KeyboardKey
|
||||||
|
last={true}
|
||||||
|
name="a"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
</KeyboardKey>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`KeyboardShortcut doesn't render same modifier twice 1`] = `
|
||||||
|
<KeyboardShortcut
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"ctrlOrCmdKey": true,
|
||||||
|
"key": "a",
|
||||||
|
"metaKey": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<KeyboardKey
|
||||||
|
key="ctrlOrCmdKey"
|
||||||
|
name="Control"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
missing translation: en|Ctrl
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
+
|
||||||
|
</KeyboardKey>
|
||||||
|
<KeyboardKey
|
||||||
|
last={true}
|
||||||
|
name="a"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
</KeyboardKey>
|
||||||
|
</div>
|
||||||
|
</KeyboardShortcut>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`KeyboardShortcut doesn't render same modifier twice 2`] = `
|
||||||
|
<KeyboardShortcut
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"ctrlKey": true,
|
||||||
|
"ctrlOrCmdKey": true,
|
||||||
|
"key": "a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<KeyboardKey
|
||||||
|
key="ctrlOrCmdKey"
|
||||||
|
name="Control"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
missing translation: en|Ctrl
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
+
|
||||||
|
</KeyboardKey>
|
||||||
|
<KeyboardKey
|
||||||
|
last={true}
|
||||||
|
name="a"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
</KeyboardKey>
|
||||||
|
</div>
|
||||||
|
</KeyboardShortcut>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`KeyboardShortcut renders alternative key name 1`] = `
|
||||||
|
<KeyboardKey
|
||||||
|
name="PageDown"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
missing translation: en|Page Down
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
+
|
||||||
|
</KeyboardKey>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`KeyboardShortcut renders key icon 1`] = `
|
||||||
|
<KeyboardKey
|
||||||
|
name="ArrowDown"
|
||||||
|
>
|
||||||
|
<kbd>
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
</kbd>
|
||||||
|
+
|
||||||
|
</KeyboardKey>
|
||||||
|
`;
|
|
@ -21,6 +21,7 @@ import { mount, ReactWrapper } from "enzyme";
|
||||||
import { Key } from "../../../../../../src/Keyboard";
|
import { Key } from "../../../../../../src/Keyboard";
|
||||||
|
|
||||||
const PATH_TO_KEYBOARD_SHORTCUTS = "../../../../../../src/accessibility/KeyboardShortcuts";
|
const PATH_TO_KEYBOARD_SHORTCUTS = "../../../../../../src/accessibility/KeyboardShortcuts";
|
||||||
|
const PATH_TO_KEYBOARD_SHORTCUT_UTILS = "../../../../../../src/accessibility/KeyboardShortcutUtils";
|
||||||
const PATH_TO_COMPONENT = "../../../../../../src/components/views/settings/tabs/user/KeyboardUserSettingsTab";
|
const PATH_TO_COMPONENT = "../../../../../../src/components/views/settings/tabs/user/KeyboardUserSettingsTab";
|
||||||
|
|
||||||
const mockKeyboardShortcuts = (override) => {
|
const mockKeyboardShortcuts = (override) => {
|
||||||
|
@ -33,9 +34,19 @@ const mockKeyboardShortcuts = (override) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderKeyboardUserSettingsTab = async (component, props?): Promise<ReactWrapper> => {
|
const mockKeyboardShortcutUtils = (override) => {
|
||||||
|
jest.doMock(PATH_TO_KEYBOARD_SHORTCUT_UTILS, () => {
|
||||||
|
const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUT_UTILS);
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
...override,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderKeyboardUserSettingsTab = async (component): Promise<ReactWrapper> => {
|
||||||
const Component = (await import(PATH_TO_COMPONENT))[component];
|
const Component = (await import(PATH_TO_COMPONENT))[component];
|
||||||
return mount(<Component {...props} />);
|
return mount(<Component />);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("KeyboardUserSettingsTab", () => {
|
describe("KeyboardUserSettingsTab", () => {
|
||||||
|
@ -43,79 +54,8 @@ describe("KeyboardUserSettingsTab", () => {
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders key icon", async () => {
|
|
||||||
const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.ARROW_DOWN });
|
|
||||||
expect(body).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders alternative key name", async () => {
|
|
||||||
const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.PAGE_DOWN });
|
|
||||||
expect(body).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't render + if last", async () => {
|
|
||||||
const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.A, last: true });
|
|
||||||
expect(body).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't render same modifier twice", async () => {
|
|
||||||
mockKeyboardShortcuts({
|
|
||||||
"getKeyboardShortcutsForUI": () => ({
|
|
||||||
"keybind1": {
|
|
||||||
default: {
|
|
||||||
key: Key.A,
|
|
||||||
ctrlOrCmdKey: true,
|
|
||||||
metaKey: true,
|
|
||||||
},
|
|
||||||
displayName: "Cancel replying to a message",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const body1 = await renderKeyboardUserSettingsTab("KeyboardShortcut", { name: "keybind1" });
|
|
||||||
expect(body1).toMatchSnapshot();
|
|
||||||
jest.resetModules();
|
|
||||||
|
|
||||||
mockKeyboardShortcuts({
|
|
||||||
"getKeyboardShortcutsForUI": () => ({
|
|
||||||
"keybind1": {
|
|
||||||
default: {
|
|
||||||
key: Key.A,
|
|
||||||
ctrlOrCmdKey: true,
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
displayName: "Cancel replying to a message",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const body2 = await renderKeyboardUserSettingsTab("KeyboardShortcut", { name: "keybind1" });
|
|
||||||
expect(body2).toMatchSnapshot();
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders list of keyboard shortcuts", async () => {
|
it("renders list of keyboard shortcuts", async () => {
|
||||||
mockKeyboardShortcuts({
|
mockKeyboardShortcuts({
|
||||||
"getKeyboardShortcutsForUI": () => ({
|
|
||||||
"keybind1": {
|
|
||||||
default: {
|
|
||||||
key: Key.A,
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
displayName: "Cancel replying to a message",
|
|
||||||
},
|
|
||||||
"keybind2": {
|
|
||||||
default: {
|
|
||||||
key: Key.B,
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
displayName: "Toggle Bold",
|
|
||||||
},
|
|
||||||
"keybind3": {
|
|
||||||
default: {
|
|
||||||
key: Key.ENTER,
|
|
||||||
},
|
|
||||||
displayName: "Select room from the room list",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
"CATEGORIES": {
|
"CATEGORIES": {
|
||||||
"Composer": {
|
"Composer": {
|
||||||
settingNames: ["keybind1", "keybind2"],
|
settingNames: ["keybind1", "keybind2"],
|
||||||
|
@ -127,6 +67,38 @@ describe("KeyboardUserSettingsTab", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
mockKeyboardShortcutUtils({
|
||||||
|
"getKeyboardShortcutValue": (name) => {
|
||||||
|
switch (name) {
|
||||||
|
case "keybind1":
|
||||||
|
return {
|
||||||
|
key: Key.A,
|
||||||
|
ctrlKey: true,
|
||||||
|
};
|
||||||
|
case "keybind2": {
|
||||||
|
return {
|
||||||
|
key: Key.B,
|
||||||
|
ctrlKey: true };
|
||||||
|
}
|
||||||
|
case "keybind3": {
|
||||||
|
return {
|
||||||
|
key: Key.ENTER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"getKeyboardShortcutDisplayName": (name) => {
|
||||||
|
switch (name) {
|
||||||
|
case "keybind1":
|
||||||
|
return "Cancel replying to a message";
|
||||||
|
case "keybind2":
|
||||||
|
return "Toggle Bold";
|
||||||
|
|
||||||
|
case "keybind3":
|
||||||
|
return "Select room from the room list";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const body = await renderKeyboardUserSettingsTab("default");
|
const body = await renderKeyboardUserSettingsTab("default");
|
||||||
expect(body).toMatchSnapshot();
|
expect(body).toMatchSnapshot();
|
||||||
|
|
|
@ -1,104 +1,5 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab doesn't render + if last 1`] = `
|
|
||||||
<KeyboardKey
|
|
||||||
last={true}
|
|
||||||
name="a"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
a
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
</KeyboardKey>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab doesn't render same modifier twice 1`] = `
|
|
||||||
<KeyboardShortcut
|
|
||||||
name="keybind1"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<KeyboardKey
|
|
||||||
key="ctrlOrCmdKey"
|
|
||||||
name="Control"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
missing translation: en|Ctrl
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
+
|
|
||||||
</KeyboardKey>
|
|
||||||
<KeyboardKey
|
|
||||||
last={true}
|
|
||||||
name="a"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
a
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
</KeyboardKey>
|
|
||||||
</div>
|
|
||||||
</KeyboardShortcut>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab doesn't render same modifier twice 2`] = `
|
|
||||||
<KeyboardShortcut
|
|
||||||
name="keybind1"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<KeyboardKey
|
|
||||||
key="ctrlOrCmdKey"
|
|
||||||
name="Control"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
missing translation: en|Ctrl
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
+
|
|
||||||
</KeyboardKey>
|
|
||||||
<KeyboardKey
|
|
||||||
last={true}
|
|
||||||
name="a"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
a
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
</KeyboardKey>
|
|
||||||
</div>
|
|
||||||
</KeyboardShortcut>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab renders alternative key name 1`] = `
|
|
||||||
<KeyboardKey
|
|
||||||
name="PageDown"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
missing translation: en|Page Down
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
+
|
|
||||||
</KeyboardKey>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab renders key icon 1`] = `
|
|
||||||
<KeyboardKey
|
|
||||||
name="ArrowDown"
|
|
||||||
>
|
|
||||||
<kbd>
|
|
||||||
|
|
||||||
↓
|
|
||||||
|
|
||||||
</kbd>
|
|
||||||
+
|
|
||||||
</KeyboardKey>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
|
exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
|
||||||
<KeyboardUserSettingsTab>
|
<KeyboardUserSettingsTab>
|
||||||
<div
|
<div
|
||||||
|
@ -140,9 +41,14 @@ exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
|
||||||
<div
|
<div
|
||||||
className="mx_KeyboardShortcut_shortcutRow"
|
className="mx_KeyboardShortcut_shortcutRow"
|
||||||
>
|
>
|
||||||
missing translation: en|Cancel replying to a message
|
Cancel replying to a message
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
name="keybind1"
|
value={
|
||||||
|
Object {
|
||||||
|
"ctrlKey": true,
|
||||||
|
"key": "a",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
|
@ -177,9 +83,14 @@ exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
|
||||||
<div
|
<div
|
||||||
className="mx_KeyboardShortcut_shortcutRow"
|
className="mx_KeyboardShortcut_shortcutRow"
|
||||||
>
|
>
|
||||||
missing translation: en|Toggle Bold
|
Toggle Bold
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
name="keybind2"
|
value={
|
||||||
|
Object {
|
||||||
|
"ctrlKey": true,
|
||||||
|
"key": "b",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
|
@ -241,9 +152,13 @@ exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
|
||||||
<div
|
<div
|
||||||
className="mx_KeyboardShortcut_shortcutRow"
|
className="mx_KeyboardShortcut_shortcutRow"
|
||||||
>
|
>
|
||||||
missing translation: en|Select room from the room list
|
Select room from the room list
|
||||||
<KeyboardShortcut
|
<KeyboardShortcut
|
||||||
name="keybind3"
|
value={
|
||||||
|
Object {
|
||||||
|
"key": "Enter",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
|
|
Loading…
Reference in a new issue