295 lines
9.3 KiB
JavaScript
295 lines
9.3 KiB
JavaScript
/* global APP, $, interfaceConfig */
|
|
|
|
import { toggleDialog } from '../../react/features/base/dialog';
|
|
import { sendAnalyticsEvent } from '../../react/features/analytics';
|
|
import { SpeakerStats } from '../../react/features/speaker-stats';
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
/**
|
|
* The reference to the shortcut dialogs when opened.
|
|
*/
|
|
let keyboardShortcutDialog = null;
|
|
|
|
/**
|
|
* Shows or hides the keyboard shortcuts dialog.
|
|
* @param {boolean} show whether to show or hide the dialog
|
|
*/
|
|
function showKeyboardShortcutsPanel(show) {
|
|
if (show
|
|
&& !APP.UI.messageHandler.isDialogOpened()
|
|
&& keyboardShortcutDialog === null) {
|
|
const msg = $('#keyboard-shortcuts').html();
|
|
const buttons = { Close: true };
|
|
|
|
keyboardShortcutDialog = APP.UI.messageHandler.openDialog(
|
|
'keyboardShortcuts.keyboardShortcuts', msg, true, buttons);
|
|
} else if (keyboardShortcutDialog !== null) {
|
|
keyboardShortcutDialog.close();
|
|
keyboardShortcutDialog = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map of shortcuts. When a shortcut is registered it enters the mapping.
|
|
* @type {{}}
|
|
*/
|
|
const _shortcuts = {};
|
|
|
|
/**
|
|
* True if the keyboard shortcuts are enabled and false if not.
|
|
* @type {boolean}
|
|
*/
|
|
let enabled = true;
|
|
|
|
/**
|
|
* Maps keycode to character, id of popover for given function and function.
|
|
*/
|
|
const KeyboardShortcut = {
|
|
init() {
|
|
this._initGlobalShortcuts();
|
|
|
|
window.onkeyup = e => {
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
const key = this._getKeyboardKey(e).toUpperCase();
|
|
const num = parseInt(key, 10);
|
|
|
|
if (!($(':focus').is('input[type=text]')
|
|
|| $(':focus').is('input[type=password]')
|
|
|| $(':focus').is('textarea'))) {
|
|
if (_shortcuts.hasOwnProperty(key)) {
|
|
_shortcuts[key].function(e);
|
|
} else if (!isNaN(num) && num >= 0 && num <= 9) {
|
|
APP.UI.clickOnVideo(num);
|
|
}
|
|
|
|
// esc while the smileys are visible hides them
|
|
} else if (key === 'ESCAPE'
|
|
&& $('#smileysContainer').is(':visible')) {
|
|
APP.UI.toggleSmileys();
|
|
}
|
|
};
|
|
|
|
window.onkeydown = e => {
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
if (!($(':focus').is('input[type=text]')
|
|
|| $(':focus').is('input[type=password]')
|
|
|| $(':focus').is('textarea'))) {
|
|
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
|
|
if (APP.conference.isLocalAudioMuted()) {
|
|
sendAnalyticsEvent('shortcut.talk.released');
|
|
logger.log('Talk shortcut released');
|
|
APP.conference.muteAudio(false);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Enables/Disables the keyboard shortcuts.
|
|
* @param {boolean} value - the new value.
|
|
*/
|
|
enable(value) {
|
|
enabled = value;
|
|
},
|
|
|
|
/**
|
|
* Registers a new shortcut.
|
|
*
|
|
* @param shortcutChar the shortcut character triggering the action
|
|
* @param shortcutAttr the "shortcut" html element attribute mappring an
|
|
* element to this shortcut and used to show the shortcut character on the
|
|
* element tooltip
|
|
* @param exec the function to be executed when the shortcut is pressed
|
|
* @param helpDescription the description of the shortcut that would appear
|
|
* in the help menu
|
|
*/
|
|
registerShortcut(// eslint-disable-line max-params
|
|
shortcutChar,
|
|
shortcutAttr,
|
|
exec,
|
|
helpDescription) {
|
|
_shortcuts[shortcutChar] = {
|
|
character: shortcutChar,
|
|
shortcutAttr,
|
|
function: exec
|
|
};
|
|
|
|
if (helpDescription) {
|
|
this._addShortcutToHelp(shortcutChar, helpDescription);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Unregisters a shortcut.
|
|
*
|
|
* @param shortcutChar unregisters the given shortcut, which means it will
|
|
* no longer be usable
|
|
*/
|
|
unregisterShortcut(shortcutChar) {
|
|
_shortcuts.remove(shortcutChar);
|
|
|
|
this._removeShortcutFromHelp(shortcutChar);
|
|
},
|
|
|
|
/**
|
|
* Returns the tooltip string for the given shortcut attribute.
|
|
*
|
|
* @param shortcutAttr indicates the popover associated with the shortcut
|
|
* @returns {string} the tooltip string to add to the given shortcut popover
|
|
* or an empty string if the shortcutAttr is null, an empty string or not
|
|
* found in the shortcut mapping
|
|
*/
|
|
getShortcutTooltip(shortcutAttr) {
|
|
if (typeof shortcutAttr === 'string' && shortcutAttr.length > 0) {
|
|
for (const key in _shortcuts) {
|
|
if (_shortcuts.hasOwnProperty(key)
|
|
&& _shortcuts[key].shortcutAttr
|
|
&& _shortcuts[key].shortcutAttr === shortcutAttr) {
|
|
return ` (${_shortcuts[key].character})`;
|
|
}
|
|
}
|
|
}
|
|
|
|
return '';
|
|
},
|
|
|
|
/**
|
|
* @param e a KeyboardEvent
|
|
* @returns {string} e.key or something close if not supported
|
|
*/
|
|
_getKeyboardKey(e) {
|
|
if (typeof e.key === 'string') {
|
|
return e.key;
|
|
}
|
|
if (e.type === 'keypress'
|
|
&& ((e.which >= 32 && e.which <= 126)
|
|
|| (e.which >= 160 && e.which <= 255))) {
|
|
return String.fromCharCode(e.which);
|
|
}
|
|
|
|
// try to fallback (0-9A-Za-z and QWERTY keyboard)
|
|
switch (e.which) {
|
|
case 27:
|
|
return 'Escape';
|
|
case 191:
|
|
return e.shiftKey ? '?' : '/';
|
|
}
|
|
if (e.shiftKey || e.type === 'keypress') {
|
|
return String.fromCharCode(e.which);
|
|
}
|
|
|
|
return String.fromCharCode(e.which).toLowerCase();
|
|
|
|
},
|
|
|
|
/**
|
|
* Adds the given shortcut to the help dialog.
|
|
*
|
|
* @param shortcutChar the shortcut character
|
|
* @param shortcutDescriptionKey the description of the shortcut
|
|
* @private
|
|
*/
|
|
_addShortcutToHelp(shortcutChar, shortcutDescriptionKey) {
|
|
|
|
const listElement = document.createElement('li');
|
|
const itemClass = 'shortcuts-list__item';
|
|
|
|
listElement.className = itemClass;
|
|
listElement.id = shortcutChar;
|
|
|
|
const spanElement = document.createElement('span');
|
|
|
|
spanElement.className = 'item-action';
|
|
|
|
const kbdElement = document.createElement('kbd');
|
|
const classes = 'aui-label regular-key';
|
|
|
|
kbdElement.className = classes;
|
|
kbdElement.innerHTML = shortcutChar;
|
|
spanElement.appendChild(kbdElement);
|
|
|
|
const descriptionElement = document.createElement('span');
|
|
const descriptionClass = 'shortcuts-list__description';
|
|
|
|
descriptionElement.className = descriptionClass;
|
|
descriptionElement.setAttribute('data-i18n', shortcutDescriptionKey);
|
|
APP.translation.translateElement($(descriptionElement));
|
|
|
|
listElement.appendChild(spanElement);
|
|
listElement.appendChild(descriptionElement);
|
|
|
|
const parentListElement
|
|
= document.getElementById('keyboard-shortcuts-list');
|
|
|
|
if (parentListElement) {
|
|
parentListElement.appendChild(listElement);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes the list element corresponding to the given shortcut from the
|
|
* help dialog
|
|
* @private
|
|
*/
|
|
_removeShortcutFromHelp(shortcutChar) {
|
|
const parentListElement
|
|
= document.getElementById('keyboard-shortcuts-list');
|
|
|
|
const shortcutElement = document.getElementById(shortcutChar);
|
|
|
|
if (shortcutElement) {
|
|
parentListElement.removeChild(shortcutElement);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
_initGlobalShortcuts() {
|
|
this.registerShortcut('ESCAPE', null, () => {
|
|
showKeyboardShortcutsPanel(false);
|
|
});
|
|
|
|
this.registerShortcut('?', null, () => {
|
|
sendAnalyticsEvent('shortcut.shortcut.help');
|
|
showKeyboardShortcutsPanel(true);
|
|
}, 'keyboardShortcuts.toggleShortcuts');
|
|
|
|
// register SPACE shortcut in two steps to insure visibility of help
|
|
// message
|
|
this.registerShortcut(' ', null, () => {
|
|
sendAnalyticsEvent('shortcut.talk.clicked');
|
|
logger.log('Talk shortcut pressed');
|
|
APP.conference.muteAudio(true);
|
|
});
|
|
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
|
|
|
|
if (!interfaceConfig.filmStripOnly) {
|
|
this.registerShortcut('T', null, () => {
|
|
sendAnalyticsEvent('shortcut.speakerStats.clicked');
|
|
APP.store.dispatch(toggleDialog(SpeakerStats, {
|
|
conference: APP.conference
|
|
}));
|
|
}, 'keyboardShortcuts.showSpeakerStats');
|
|
}
|
|
|
|
/**
|
|
* FIXME: Currently focus keys are directly implemented below in
|
|
* onkeyup. They should be moved to the SmallVideo instead.
|
|
*/
|
|
this._addShortcutToHelp('0', 'keyboardShortcuts.focusLocal');
|
|
this._addShortcutToHelp('1-9', 'keyboardShortcuts.focusRemote');
|
|
}
|
|
};
|
|
|
|
export default KeyboardShortcut;
|