From 7076ada6f422e2638ce1426a6163f149c030e9f2 Mon Sep 17 00:00:00 2001 From: yanas Date: Sun, 28 Aug 2016 22:59:09 -0500 Subject: [PATCH] Attach keyboard shortcuts to features --- css/keyboard-shortcuts.css | 5 + index.html | 62 +----- modules/UI/toolbars/BottomToolbar.js | 12 +- modules/UI/toolbars/Toolbar.js | 88 +++++++- modules/UI/util/UIUtil.js | 2 +- modules/keyboardshortcut/keyboardshortcut.js | 212 +++++++++++-------- 6 files changed, 221 insertions(+), 160 deletions(-) diff --git a/css/keyboard-shortcuts.css b/css/keyboard-shortcuts.css index 7a4de15c7..10a668abc 100644 --- a/css/keyboard-shortcuts.css +++ b/css/keyboard-shortcuts.css @@ -16,4 +16,9 @@ #keyboard-shortcuts .item-action { color: #209EFF; font-size: 14pt; + padding-right: 5px; +} + +#keyboard-shortcuts-list { + list-style-type: none; } \ No newline at end of file diff --git a/index.html b/index.html index 394ed2620..bb80c7060 100644 --- a/index.html +++ b/index.html @@ -279,67 +279,7 @@ diff --git a/modules/UI/toolbars/BottomToolbar.js b/modules/UI/toolbars/BottomToolbar.js index 6dea1539c..ee8edc137 100644 --- a/modules/UI/toolbars/BottomToolbar.js +++ b/modules/UI/toolbars/BottomToolbar.js @@ -3,9 +3,15 @@ import UIUtil from '../util/UIUtil'; import UIEvents from '../../../service/UI/UIEvents'; const defaultBottomToolbarButtons = { - 'chat': '#bottom_toolbar_chat', - 'contacts': '#bottom_toolbar_contact_list', - 'filmstrip': '#bottom_toolbar_film_strip' + 'chat': { + id: '#bottom_toolbar_chat' + }, + 'contacts': { + id: '#bottom_toolbar_contact_list' + }, + 'filmstrip': { + id: '#bottom_toolbar_film_strip' + } }; const BottomToolbar = { diff --git a/modules/UI/toolbars/Toolbar.js b/modules/UI/toolbars/Toolbar.js index e83cddd13..00d95d2fc 100644 --- a/modules/UI/toolbars/Toolbar.js +++ b/modules/UI/toolbars/Toolbar.js @@ -2,6 +2,7 @@ /* jshint -W101 */ import UIUtil from '../util/UIUtil'; import UIEvents from '../../../service/UI/UIEvents'; +import KeyboardShortcut from '../../keyboardshortcut/KeyboardShortcut'; let roomUrl = null; let emitter = null; @@ -110,7 +111,8 @@ const buttonHandlers = { }, "toolbar_button_fullScreen": function() { JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled'); - UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen"); + UIUtil.buttonClick("#toolbar_button_fullScreen", + "icon-full-screen icon-exit-full-screen"); emitter.emit(UIEvents.FULLSCREEN_TOGGLE); }, "toolbar_button_sip": function () { @@ -152,16 +154,64 @@ const buttonHandlers = { } }; const defaultToolbarButtons = { - 'microphone': '#toolbar_button_mute', - 'camera': '#toolbar_button_camera', - 'desktop': '#toolbar_button_desktopsharing', - 'security': '#toolbar_button_security', - 'invite': '#toolbar_button_link', - 'chat': '#toolbar_button_chat', - 'etherpad': '#toolbar_button_etherpad', - 'fullscreen': '#toolbar_button_fullScreen', - 'settings': '#toolbar_button_settings', - 'hangup': '#toolbar_button_hangup' + 'microphone': { + id: '#toolbar_button_mute', + shortcut: 'M', + shortcutAttr: 'mutePopover', + shortcutFunc: function() { + JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled'); + APP.conference.toggleAudioMuted(); + }, + shortcutDescription: "keyboardShortcuts.mute" + }, + 'camera': { + id: '#toolbar_button_camera', + shortcut: 'V', + shortcutAttr: 'toggleVideoPopover', + shortcutFunc: function() { + JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled'); + APP.conference.toggleVideoMuted(); + }, + shortcutDescription: "keyboardShortcuts.videoMute" + }, + 'desktop': { + id: '#toolbar_button_desktopsharing', + shortcut: 'D', + shortcutAttr: 'toggleDesktopSharingPopover', + shortcutFunc: function() { + JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled'); + APP.conference.toggleScreenSharing(); + }, + shortcutDescription: "keyboardShortcuts.toggleScreensharing" + }, + 'security': { + id: '#toolbar_button_security' + }, + 'invite': { + id: '#toolbar_button_link' + }, + 'chat': { + id: '#toolbar_button_chat', + shortcut: 'C', + shortcutAttr: 'toggleChatPopover', + shortcutFunc: function() { + JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled'); + APP.UI.toggleChat(); + }, + shortcutDescription: "keyboardShortcuts.toggleChat" + }, + 'etherpad': { + id: '#toolbar_button_etherpad' + }, + 'fullscreen': { + id: '#toolbar_button_fullScreen' + }, + 'settings': { + id: '#toolbar_button_settings' + }, + 'hangup': { + id: '#toolbar_button_hangup' + } }; function dialpadButtonClicked() { @@ -197,6 +247,22 @@ const Toolbar = { UIUtil.hideDisabledButtons(defaultToolbarButtons); + Object.keys(defaultToolbarButtons).forEach( + id => { + if (UIUtil.isButtonEnabled(id)) { + var button = defaultToolbarButtons[id]; + + if (button.shortcut) + KeyboardShortcut.registerShortcut( + button.shortcut, + button.shortcutAttr, + button.shortcutFunc, + button.shortcutDescription + ); + } + } + ); + Object.keys(buttonHandlers).forEach( buttonId => $(`#${buttonId}`).click(function(event) { !$(this).prop('disabled') && buttonHandlers[buttonId](event); diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index 18c78e717..1c621349f 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -130,7 +130,7 @@ var selector = Object.keys(mappings) .map(function (buttonName) { return UIUtil.isButtonEnabled(buttonName) - ? null : mappings[buttonName]; }) + ? null : mappings[buttonName].id; }) .filter(function (item) { return item; }) .join(','); $(selector).hide(); diff --git a/modules/keyboardshortcut/keyboardshortcut.js b/modules/keyboardshortcut/keyboardshortcut.js index 0a2f7881b..0c51acf30 100644 --- a/modules/keyboardshortcut/keyboardshortcut.js +++ b/modules/keyboardshortcut/keyboardshortcut.js @@ -1,91 +1,64 @@ /* global APP, $, JitsiMeetJS */ -//maps keycode to character, id of popover for given function and function -var shortcuts = {}; -function initShortcutHandlers() { - shortcuts = { - "ESCAPE": { - character: "Esc", - function: function() { - APP.UI.showKeyboardShortcutsPanel(false); - } - }, - "C": { - character: "C", - id: "toggleChatPopover", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled'); - APP.UI.toggleChat(); - } - }, - "D": { - character: "D", - id: "toggleDesktopSharingPopover", - function: function () { - JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled'); - APP.conference.toggleScreenSharing(); - } - }, - "F": { - character: "F", - id: "filmstripPopover", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.film.toggled'); - APP.UI.toggleFilmStrip(); - } - }, - "M": { - character: "M", - id: "mutePopover", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled'); - APP.conference.toggleAudioMuted(); - } - }, - "R": { - character: "R", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.raisedhand.toggled'); - APP.conference.maybeToggleRaisedHand(); - } - }, - "T": { - character: "T", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.talk.clicked'); - APP.conference.muteAudio(true); - } - }, - "V": { - character: "V", - id: "toggleVideoPopover", - function: function() { - JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled'); - APP.conference.toggleVideoMuted(); - } - }, - "?": { - character: "?", - function: function(e) { - JitsiMeetJS.analytics.sendEvent('shortcut.shortcut.help'); - APP.UI.toggleKeyboardShortcutsPanel(); - } - } - }; +/** + * 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. + */ +function initGlobalShortcuts() { + + KeyboardShortcut.registerShortcut("ESCAPE", null, function() { + APP.UI.showKeyboardShortcutsPanel(false); + }); + + KeyboardShortcut.registerShortcut("?", null, function() { + JitsiMeetJS.analytics.sendEvent("shortcut.shortcut.help"); + APP.UI.toggleKeyboardShortcutsPanel(); + }, "keyboardShortcuts.toggleShortcuts"); + + KeyboardShortcut.registerShortcut("R", null, function() { + JitsiMeetJS.analytics.sendEvent("shortcut.raisedhand.toggled"); + APP.conference.maybeToggleRaisedHand(); + }, "keyboardShortcuts.raiseHand"); + + KeyboardShortcut.registerShortcut("T", null, function() { + JitsiMeetJS.analytics.sendEvent("shortcut.talk.clicked"); + APP.conference.muteAudio(true); + }, "keyboardShortcuts.pushToTalk"); + + KeyboardShortcut.registerShortcut("F", 'filmstripPopover', function() { + JitsiMeetJS.analytics.sendEvent("shortcut.film.toggled"); + APP.UI.toggleFilmStrip(); + }, "keyboardShortcuts.toggleFilmstrip"); + + // Focus keys are directly implemented below. + KeyboardShortcut._addShortcutToHelp("0", "keyboardShortcuts.focusLocal"); + KeyboardShortcut._addShortcutToHelp("1-9", "keyboardShortcuts.focusRemote"); } +/** + * Map of shortcuts. When a shortcut is registered it enters the mapping. + * @type {{}} + */ +let _shortcuts = {}; + +/** + * Maps keycode to character, id of popover for given function and function. + */ var KeyboardShortcut = { init: function () { - initShortcutHandlers(); + initGlobalShortcuts(); + var self = this; window.onkeyup = function(e) { - var key = self.getKeyboardKey(e).toUpperCase(); + var key = self._getKeyboardKey(e).toUpperCase(); var 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); + if (_shortcuts.hasOwnProperty(key)) { + _shortcuts[key].function(e); } else if (!isNaN(num) && num >= 0 && num <= 9) { APP.UI.clickOnVideo(num + 1); @@ -101,7 +74,7 @@ var KeyboardShortcut = { if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) { - var key = self.getKeyboardKey(e).toUpperCase(); + var key = self._getKeyboardKey(e).toUpperCase(); if(key === "T") { if(APP.conference.isLocalAudioMuted()) APP.conference.muteAudio(false); @@ -112,20 +85,56 @@ var KeyboardShortcut = { trigger: 'click hover', content: function() { return this.getAttribute("content") + - self.getShortcut(this.getAttribute("shortcut")); + self._getShortcut(this.getAttribute("shortcut")); } }); }, + + /** + * 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: function( shortcutChar, + shortcutAttr, + exec, + helpDescription) { + _shortcuts[shortcutChar] = { + character: shortcutChar, + shortcutAttr: 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: function(shortcutChar) { + _shortcuts.remove(shortcutChar); + }, + /** * * @param id indicates the popover associated with the shortcut * @returns {string} the keyboard shortcut used for the id given */ - getShortcut: function (id) { - for (var key in shortcuts) { - if (shortcuts.hasOwnProperty(key)) { - if (shortcuts[key].id === id) { - return " (" + shortcuts[key].character + ")"; + _getShortcut: function (id) { + for (var key in _shortcuts) { + if (_shortcuts.hasOwnProperty(key)) { + if (_shortcuts[key].shortcutAttr === id) { + return " (" + _shortcuts[key].character + ")"; } } } @@ -135,7 +144,7 @@ var KeyboardShortcut = { * @param e a KeyboardEvent * @returns {string} e.key or something close if not supported */ - getKeyboardKey: function (e) { + _getKeyboardKey: function (e) { if (typeof e.key === "string") { return e.key; } @@ -156,6 +165,41 @@ var KeyboardShortcut = { } else { 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: function (shortcutChar, shortcutDescriptionKey) { + + var listElement = document.createElement("li"); + + var spanElement = document.createElement("span"); + spanElement.className = "item-action"; + + var kbdElement = document.createElement("kbd"); + kbdElement.className = "regular-key"; + kbdElement.innerHTML = shortcutChar; + spanElement.appendChild(kbdElement); + + var descriptionElement = document.createElement("span"); + descriptionElement.className = "item-description"; + descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey); + descriptionElement.innerHTML + = APP.translation.translateString(shortcutDescriptionKey); + + listElement.appendChild(spanElement); + listElement.appendChild(descriptionElement); + + var parentListElement + = document.getElementById("keyboard-shortcuts-list"); + + if (parentListElement) + parentListElement.appendChild(listElement); } };