snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
29
react/features/keyboard-shortcuts/actionTypes.ts
Normal file
29
react/features/keyboard-shortcuts/actionTypes.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* The type of the action which signals that the keyboard shortcuts should be initialized.
|
||||
*/
|
||||
export const INIT_KEYBOARD_SHORTCUTS = 'INIT_KEYBOARD_SHORTCUTS';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be registered.
|
||||
*/
|
||||
export const REGISTER_KEYBOARD_SHORTCUT = 'REGISTER_KEYBOARD_SHORTCUT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be unregistered.
|
||||
*/
|
||||
export const UNREGISTER_KEYBOARD_SHORTCUT = 'UNREGISTER_KEYBOARD_SHORTCUT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be enabled.
|
||||
*/
|
||||
export const ENABLE_KEYBOARD_SHORTCUTS = 'ENABLE_KEYBOARD_SHORTCUTS';
|
||||
|
||||
/**
|
||||
* The type of the action which signals that a keyboard shortcut should be disabled.
|
||||
*/
|
||||
export const DISABLE_KEYBOARD_SHORTCUTS = 'DISABLE_KEYBOARD_SHORTCUTS';
|
||||
|
||||
/**
|
||||
* The type of the action which sets whether Ctrl+Alt reaction shortcut aliases are enabled.
|
||||
*/
|
||||
export const SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED = 'SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED';
|
||||
256
react/features/keyboard-shortcuts/actions.ts
Normal file
256
react/features/keyboard-shortcuts/actions.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { ACTION_SHORTCUT_PRESSED, ACTION_SHORTCUT_RELEASED, createShortcutEvent } from '../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { IStore } from '../app/types';
|
||||
import { clickOnVideo } from '../filmstrip/actions.web';
|
||||
import { openSettingsDialog } from '../settings/actions.web';
|
||||
import { SETTINGS_TABS } from '../settings/constants';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import {
|
||||
DISABLE_KEYBOARD_SHORTCUTS,
|
||||
ENABLE_KEYBOARD_SHORTCUTS,
|
||||
REGISTER_KEYBOARD_SHORTCUT,
|
||||
SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED,
|
||||
UNREGISTER_KEYBOARD_SHORTCUT
|
||||
} from './actionTypes';
|
||||
import { areKeyboardShortcutsEnabled, getKeyboardShortcuts } from './functions';
|
||||
import logger from './logger';
|
||||
import { IKeyboardShortcut } from './types';
|
||||
import { getKeyboardKey, getPriorityFocusedElement } from './utils';
|
||||
|
||||
/**
|
||||
* Action to register a new shortcut.
|
||||
*
|
||||
* @param {IKeyboardShortcut} shortcut - The shortcut to register.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const registerShortcut = (shortcut: IKeyboardShortcut): AnyAction => {
|
||||
return {
|
||||
type: REGISTER_KEYBOARD_SHORTCUT,
|
||||
shortcut
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to unregister a shortcut.
|
||||
*
|
||||
* @param {string} character - The character of the shortcut to unregister.
|
||||
* @param {boolean} altKey - Whether the shortcut used altKey.
|
||||
* @param {boolean} ctrlKey - Whether the shortcut used ctrlKey.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const unregisterShortcut = (character: string, altKey = false, ctrlKey = false): AnyAction => {
|
||||
return {
|
||||
alt: altKey,
|
||||
ctrl: ctrlKey,
|
||||
type: UNREGISTER_KEYBOARD_SHORTCUT,
|
||||
character
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets whether Ctrl+Alt aliases for reaction shortcuts are enabled.
|
||||
*
|
||||
* @param {boolean} enabled - Whether the aliases should be enabled.
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const setCtrlAltReactionShortcutsEnabled = (enabled: boolean): AnyAction => {
|
||||
return {
|
||||
type: SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED,
|
||||
enabled
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action to enable keyboard shortcuts.
|
||||
*
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const enableKeyboardShortcuts = (): AnyAction => {
|
||||
return {
|
||||
type: ENABLE_KEYBOARD_SHORTCUTS
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Action to enable keyboard shortcuts.
|
||||
*
|
||||
* @returns {AnyAction}
|
||||
*/
|
||||
export const disableKeyboardShortcuts = (): AnyAction => {
|
||||
return {
|
||||
type: DISABLE_KEYBOARD_SHORTCUTS
|
||||
};
|
||||
};
|
||||
|
||||
type KeyHandler = ((e: KeyboardEvent) => void) | undefined;
|
||||
|
||||
let keyDownHandler: KeyHandler;
|
||||
let keyUpHandler: KeyHandler;
|
||||
|
||||
/**
|
||||
* Initialise global shortcuts.
|
||||
* Global shortcuts are shortcuts for features that don't have a button or
|
||||
* link associated with the action. In other words they represent actions
|
||||
* triggered _only_ with a shortcut.
|
||||
*
|
||||
* @param {Function} dispatch - The redux dispatch function.
|
||||
* @returns {void}
|
||||
*/
|
||||
function initGlobalKeyboardShortcuts(dispatch: IStore['dispatch']) {
|
||||
batch(() => {
|
||||
dispatch(registerShortcut({
|
||||
character: '?',
|
||||
helpDescription: 'keyboardShortcuts.toggleShortcuts',
|
||||
handler: () => {
|
||||
sendAnalytics(createShortcutEvent('help'));
|
||||
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
|
||||
}
|
||||
}));
|
||||
|
||||
// register SPACE shortcut in two steps to insure visibility of help message
|
||||
dispatch(registerShortcut({
|
||||
character: ' ',
|
||||
helpCharacter: 'SPACE',
|
||||
helpDescription: 'keyboardShortcuts.pushToTalk',
|
||||
handler: () => {
|
||||
// Handled directly on the global handler.
|
||||
}
|
||||
}));
|
||||
|
||||
dispatch(registerShortcut({
|
||||
character: '0',
|
||||
helpDescription: 'keyboardShortcuts.focusLocal',
|
||||
handler: () => {
|
||||
dispatch(clickOnVideo(0));
|
||||
}
|
||||
}));
|
||||
|
||||
for (let num = 1; num < 10; num++) {
|
||||
dispatch(registerShortcut({
|
||||
character: `${num}`,
|
||||
|
||||
// only show help hint for the first shortcut
|
||||
helpCharacter: num === 1 ? '1-9' : undefined,
|
||||
helpDescription: num === 1 ? 'keyboardShortcuts.focusRemote' : undefined,
|
||||
handler: () => {
|
||||
dispatch(clickOnVideo(num));
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters global shortcuts.
|
||||
*
|
||||
* @param {Function} dispatch - The redux dispatch function.
|
||||
* @returns {void}
|
||||
*/
|
||||
function unregisterGlobalKeyboardShortcuts(dispatch: IStore['dispatch']) {
|
||||
batch(() => {
|
||||
dispatch(unregisterShortcut('?'));
|
||||
|
||||
// register SPACE shortcut in two steps to insure visibility of help message
|
||||
dispatch(unregisterShortcut(' '));
|
||||
|
||||
dispatch(unregisterShortcut('0'));
|
||||
|
||||
for (let num = 1; num < 10; num++) {
|
||||
dispatch(unregisterShortcut(`${num}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes keyboard shortcuts.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function initKeyboardShortcuts() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
initGlobalKeyboardShortcuts(dispatch);
|
||||
|
||||
const pttDelay = 50;
|
||||
let pttTimeout: number | undefined;
|
||||
|
||||
// Used to chain the push to talk operations in order to fix an issue when on press we actually need to create
|
||||
// a new track and the release happens before the track is created. In this scenario the release is ignored.
|
||||
// The chaining would also prevent creating multiple new tracks if the space bar is pressed and released
|
||||
// multiple times before the new track creation finish.
|
||||
// TODO: Revisit the fix once we have better track management in LJM. It is possible that we would not need the
|
||||
// chaining at all.
|
||||
let mutePromise = Promise.resolve();
|
||||
|
||||
keyUpHandler = (e: KeyboardEvent) => {
|
||||
const state = getState();
|
||||
const enabled = areKeyboardShortcutsEnabled(state);
|
||||
const shortcuts = getKeyboardShortcuts(state);
|
||||
|
||||
if (!enabled || getPriorityFocusedElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = getKeyboardKey(e).toUpperCase();
|
||||
|
||||
if (key === ' ') {
|
||||
clearTimeout(pttTimeout);
|
||||
pttTimeout = window.setTimeout(() => {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
mutePromise = mutePromise.then(() =>
|
||||
APP.conference.muteAudio(true).catch(() => { /* nothing to be done */ }));
|
||||
}, pttDelay);
|
||||
}
|
||||
|
||||
if (shortcuts.has(key)) {
|
||||
shortcuts.get(key)?.handler(e);
|
||||
}
|
||||
};
|
||||
|
||||
keyDownHandler = (e: KeyboardEvent) => {
|
||||
const state = getState();
|
||||
const enabled = areKeyboardShortcutsEnabled(state);
|
||||
|
||||
if (!enabled || iAmVisitor(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusedElement = getPriorityFocusedElement();
|
||||
const key = getKeyboardKey(e).toUpperCase();
|
||||
|
||||
if (key === ' ' && !focusedElement) {
|
||||
clearTimeout(pttTimeout);
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', ACTION_SHORTCUT_PRESSED));
|
||||
logger.log('Talk shortcut pressed');
|
||||
mutePromise = mutePromise.then(() =>
|
||||
APP.conference.muteAudio(false).catch(() => { /* nothing to be done */ }));
|
||||
} else if (key === 'ESCAPE') {
|
||||
focusedElement?.blur();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keyup', keyUpHandler);
|
||||
window.addEventListener('keydown', keyDownHandler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the global shortcuts and removes the global keyboard listeners.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disposeKeyboardShortcuts() {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
// The components that are registering shortcut should take care of unregistering them.
|
||||
unregisterGlobalKeyboardShortcuts(dispatch);
|
||||
|
||||
keyUpHandler && window.removeEventListener('keyup', keyUpHandler);
|
||||
keyDownHandler && window.removeEventListener('keydown', keyDownHandler);
|
||||
keyDownHandler = keyUpHandler = undefined;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { isMobileBrowser } from '../../base/environment/utils';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { IconShortcuts } from '../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../base/toolbox/components/AbstractButton';
|
||||
import { openSettingsDialog } from '../../settings/actions.web';
|
||||
import { SETTINGS_TABS } from '../../settings/constants';
|
||||
import { areKeyboardShortcutsEnabled } from '../functions';
|
||||
|
||||
/**
|
||||
* Implementation of a button for opening keyboard shortcuts dialog.
|
||||
*/
|
||||
class KeyboardShortcutsButton extends AbstractButton<AbstractButtonProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.shortcuts';
|
||||
override icon = IconShortcuts;
|
||||
override label = 'toolbar.shortcuts';
|
||||
override tooltip = 'toolbar.shortcuts';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('shortcuts'));
|
||||
dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
return {
|
||||
visible: !isMobileBrowser() && areKeyboardShortcutsEnabled(state)
|
||||
};
|
||||
};
|
||||
|
||||
export default translate(connect(mapStateToProps)(KeyboardShortcutsButton));
|
||||
46
react/features/keyboard-shortcuts/functions.ts
Normal file
46
react/features/keyboard-shortcuts/functions.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
|
||||
/**
|
||||
* Returns whether or not Ctrl+Alt aliases for reaction shortcuts are enabled.
|
||||
*
|
||||
* The setting is Firefox-only and defaults to enabled for existing users whose
|
||||
* persisted state does not contain it yet.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - Whether or not the aliases are enabled.
|
||||
*/
|
||||
export function areCtrlAltReactionShortcutsEnabled(state: IReduxState) {
|
||||
return browser.isFirefox()
|
||||
&& state['features/keyboard-shortcuts'].ctrlAltReactionShortcutsEnabled !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the keyboard shortcuts are enabled.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - Whether or not the keyboard shortcuts are enabled.
|
||||
*/
|
||||
export function areKeyboardShortcutsEnabled(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard shortcuts map.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Map} - The keyboard shortcuts map.
|
||||
*/
|
||||
export function getKeyboardShortcuts(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].shortcuts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyboard shortcuts help descriptions.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Map} - The keyboard shortcuts help descriptions.
|
||||
*/
|
||||
export function getKeyboardShortcutsHelpDescriptions(state: IReduxState) {
|
||||
return state['features/keyboard-shortcuts'].shortcutsHelp;
|
||||
}
|
||||
25
react/features/keyboard-shortcuts/hooks.ts
Normal file
25
react/features/keyboard-shortcuts/hooks.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
|
||||
import KeyboardShortcutsButton from './components/KeyboardShortcutsButton';
|
||||
import { areKeyboardShortcutsEnabled } from './functions';
|
||||
|
||||
const shortcuts = {
|
||||
key: 'shortcuts',
|
||||
Content: KeyboardShortcutsButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the keyboard shortcuts button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useKeyboardShortcutsButton() {
|
||||
const _areKeyboardShortcutsEnabled = useSelector(areKeyboardShortcutsEnabled);
|
||||
|
||||
if (!isMobileBrowser() && _areKeyboardShortcutsEnabled) {
|
||||
return shortcuts;
|
||||
}
|
||||
}
|
||||
3
react/features/keyboard-shortcuts/logger.ts
Normal file
3
react/features/keyboard-shortcuts/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('app:keyboard-shortcuts');
|
||||
52
react/features/keyboard-shortcuts/middleware.ts
Normal file
52
react/features/keyboard-shortcuts/middleware.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { CONFERENCE_JOINED, CONFERENCE_LEFT } from '../base/conference/actionTypes';
|
||||
import { SET_CONFIG } from '../base/config/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { CAPTURE_EVENTS } from '../remote-control/actionTypes';
|
||||
|
||||
import {
|
||||
disableKeyboardShortcuts,
|
||||
disposeKeyboardShortcuts,
|
||||
enableKeyboardShortcuts,
|
||||
initKeyboardShortcuts
|
||||
} from './actions';
|
||||
|
||||
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => {
|
||||
const { dispatch } = store;
|
||||
|
||||
switch (action.type) {
|
||||
case CAPTURE_EVENTS:
|
||||
if (action.isCapturingEvents) {
|
||||
dispatch(disableKeyboardShortcuts());
|
||||
} else {
|
||||
dispatch(enableKeyboardShortcuts());
|
||||
}
|
||||
|
||||
return next(action);
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
const state = store.getState();
|
||||
const { disableShortcuts } = state['features/base/config'];
|
||||
|
||||
if (disableShortcuts !== undefined) {
|
||||
if (disableShortcuts) {
|
||||
dispatch(disableKeyboardShortcuts());
|
||||
} else {
|
||||
dispatch(enableKeyboardShortcuts());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case CONFERENCE_JOINED:
|
||||
dispatch(initKeyboardShortcuts());
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
dispatch(disposeKeyboardShortcuts());
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
97
react/features/keyboard-shortcuts/reducer.ts
Normal file
97
react/features/keyboard-shortcuts/reducer.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import PersistenceRegistry from '../base/redux/PersistenceRegistry';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
DISABLE_KEYBOARD_SHORTCUTS,
|
||||
ENABLE_KEYBOARD_SHORTCUTS,
|
||||
REGISTER_KEYBOARD_SHORTCUT,
|
||||
SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED,
|
||||
UNREGISTER_KEYBOARD_SHORTCUT
|
||||
} from './actionTypes';
|
||||
import { IKeyboardShortcutsState } from './types';
|
||||
|
||||
/**
|
||||
* The redux subtree of this feature.
|
||||
*/
|
||||
const STORE_NAME = 'features/keyboard-shortcuts';
|
||||
|
||||
const defaultState = {
|
||||
ctrlAltReactionShortcutsEnabled: true,
|
||||
enabled: true,
|
||||
shortcuts: new Map(),
|
||||
shortcutsHelp: new Map()
|
||||
};
|
||||
|
||||
PersistenceRegistry.register(STORE_NAME, {
|
||||
ctrlAltReactionShortcutsEnabled: true,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
ReducerRegistry.register<IKeyboardShortcutsState>(STORE_NAME,
|
||||
(state = defaultState, action): IKeyboardShortcutsState => {
|
||||
switch (action.type) {
|
||||
case ENABLE_KEYBOARD_SHORTCUTS:
|
||||
return {
|
||||
...state,
|
||||
enabled: true
|
||||
};
|
||||
case DISABLE_KEYBOARD_SHORTCUTS:
|
||||
return {
|
||||
...state,
|
||||
enabled: false
|
||||
};
|
||||
case SET_CTRL_ALT_REACTION_SHORTCUTS_ENABLED:
|
||||
return {
|
||||
...state,
|
||||
ctrlAltReactionShortcutsEnabled: action.enabled
|
||||
};
|
||||
case REGISTER_KEYBOARD_SHORTCUT: {
|
||||
const { alt, character, ctrl } = action.shortcut;
|
||||
let shortcutKey = character;
|
||||
|
||||
if (ctrl && alt) {
|
||||
shortcutKey = `-:${character}`;
|
||||
} else if (alt) {
|
||||
shortcutKey = `:${character}`;
|
||||
} else if (ctrl) {
|
||||
shortcutKey = `-${character}`;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts: new Map(state.shortcuts)
|
||||
.set(shortcutKey, action.shortcut),
|
||||
shortcutsHelp: action.shortcut.helpDescription
|
||||
? new Map(state.shortcutsHelp)
|
||||
.set(action.shortcut.helpCharacter ?? shortcutKey, action.shortcut.helpDescription)
|
||||
: state.shortcutsHelp
|
||||
};
|
||||
}
|
||||
case UNREGISTER_KEYBOARD_SHORTCUT: {
|
||||
let shortcutKey = action.character;
|
||||
|
||||
if (action.ctrl && action.alt) {
|
||||
shortcutKey = `-:${action.character}`;
|
||||
} else if (action.alt) {
|
||||
shortcutKey = `:${action.character}`;
|
||||
} else if (action.ctrl) {
|
||||
shortcutKey = `-${action.character}`;
|
||||
}
|
||||
const shortcuts = new Map(state.shortcuts);
|
||||
|
||||
shortcuts.delete(shortcutKey);
|
||||
|
||||
const shortcutsHelp = new Map(state.shortcutsHelp);
|
||||
|
||||
shortcutsHelp.delete(shortcutKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
shortcuts,
|
||||
shortcutsHelp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
27
react/features/keyboard-shortcuts/types.ts
Normal file
27
react/features/keyboard-shortcuts/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export interface IKeyboardShortcut {
|
||||
|
||||
// whether or not the alt key must be pressed
|
||||
alt?: boolean;
|
||||
|
||||
// the character to be pressed that triggers the action
|
||||
character: string;
|
||||
|
||||
// whether or not the ctrl key must be pressed
|
||||
ctrl?: boolean;
|
||||
|
||||
// the function to be executed when the shortcut is pressed
|
||||
handler: Function;
|
||||
|
||||
// character to be displayed in the help dialog shortcuts list
|
||||
helpCharacter?: string;
|
||||
|
||||
// help description of the shortcut, to be displayed in the help dialog
|
||||
helpDescription?: string;
|
||||
}
|
||||
|
||||
export interface IKeyboardShortcutsState {
|
||||
ctrlAltReactionShortcutsEnabled: boolean;
|
||||
enabled: boolean;
|
||||
shortcuts: Map<string, IKeyboardShortcut>;
|
||||
shortcutsHelp: Map<string, string>;
|
||||
}
|
||||
88
react/features/keyboard-shortcuts/utils.ts
Normal file
88
react/features/keyboard-shortcuts/utils.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Prefer keyboard handling of these elements over global shortcuts.
|
||||
* If a button is triggered using the Spacebar it should not trigger PTT.
|
||||
* If an input element is focused and M is pressed it should not mute audio.
|
||||
*/
|
||||
const _elementsBlacklist = [
|
||||
'input',
|
||||
'textarea',
|
||||
'button',
|
||||
'[role=button]',
|
||||
'[role=menuitem]',
|
||||
'[role=radio]',
|
||||
'[role=tab]',
|
||||
'[role=option]',
|
||||
'[role=switch]',
|
||||
'[role=range]',
|
||||
'[role=log]'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the currently focused element if it is not blacklisted.
|
||||
*
|
||||
* @returns {HTMLElement|null} - The currently focused element.
|
||||
*/
|
||||
export const getPriorityFocusedElement = (): HTMLElement | null =>
|
||||
document.querySelector(`:focus:is(${_elementsBlacklist.join(',')})`);
|
||||
|
||||
/**
|
||||
* Returns the keyboard key from a KeyboardEvent.
|
||||
*
|
||||
* @param {KeyboardEvent} e - The KeyboardEvent.
|
||||
* @returns {string} - The keyboard key.
|
||||
*/
|
||||
export const getKeyboardKey = (e: KeyboardEvent): string => {
|
||||
// @ts-ignore
|
||||
const { altKey, code, key, shiftKey, type, which, ctrlKey } = e;
|
||||
|
||||
// If alt is pressed a different char can be returned so this takes
|
||||
// the char from the code. It also prefixes with a colon to differentiate
|
||||
// alt combo from simple keypress.
|
||||
|
||||
const replacedKey = code.replace('Key', '');
|
||||
|
||||
if (ctrlKey && altKey) {
|
||||
return `-:${replacedKey}`;
|
||||
}
|
||||
|
||||
if (altKey) {
|
||||
return `:${replacedKey}`;
|
||||
}
|
||||
|
||||
// If e.key is a string, then it is assumed it already plainly states
|
||||
// the key pressed. This may not be true in all cases, such as with Edge
|
||||
// and "?", when the browser cannot properly map a key press event to a
|
||||
// keyboard key. To be safe, when a key is "Unidentified" it must be
|
||||
// further analyzed by jitsi to a key using e.which.
|
||||
if (typeof key === 'string' && key !== 'Unidentified') {
|
||||
if (ctrlKey) {
|
||||
return `-${key}`;
|
||||
}
|
||||
|
||||
if (code.startsWith('Key')) {
|
||||
return replacedKey;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
if (type === 'keypress'
|
||||
&& ((which >= 32 && which <= 126)
|
||||
|| (which >= 160 && which <= 255))) {
|
||||
return String.fromCharCode(which);
|
||||
}
|
||||
|
||||
// try to fallback (0-9A-Za-z and QWERTY keyboard)
|
||||
switch (which) {
|
||||
case 27:
|
||||
return 'Escape';
|
||||
case 191:
|
||||
return shiftKey ? '?' : '/';
|
||||
}
|
||||
|
||||
if (shiftKey || type === 'keypress') {
|
||||
return String.fromCharCode(which);
|
||||
}
|
||||
|
||||
return String.fromCharCode(which).toLowerCase();
|
||||
};
|
||||
Reference in New Issue
Block a user