You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keyboardshortcut.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /* global APP, $, JitsiMeetJS */
  2. import {
  3. toggleDialog
  4. } from '../../react/features/base/dialog';
  5. import { SpeakerStats } from '../../react/features/speaker-stats';
  6. /**
  7. * The reference to the shortcut dialogs when opened.
  8. */
  9. let keyboardShortcutDialog = null;
  10. /**
  11. * Initialise global shortcuts.
  12. * Global shortcuts are shortcuts for features that don't have a button or
  13. * link associated with the action. In other words they represent actions
  14. * triggered _only_ with a shortcut.
  15. */
  16. function initGlobalShortcuts() {
  17. KeyboardShortcut.registerShortcut("ESCAPE", null, function() {
  18. showKeyboardShortcutsPanel(false);
  19. });
  20. KeyboardShortcut.registerShortcut("?", null, function() {
  21. JitsiMeetJS.analytics.sendEvent("shortcut.shortcut.help");
  22. showKeyboardShortcutsPanel(true);
  23. }, "keyboardShortcuts.toggleShortcuts");
  24. // register SPACE shortcut in two steps to insure visibility of help message
  25. KeyboardShortcut.registerShortcut(" ", null, function() {
  26. JitsiMeetJS.analytics.sendEvent("shortcut.talk.clicked");
  27. APP.conference.muteAudio(true);
  28. });
  29. KeyboardShortcut._addShortcutToHelp("SPACE","keyboardShortcuts.pushToTalk");
  30. KeyboardShortcut.registerShortcut("T", null, () => {
  31. JitsiMeetJS.analytics.sendEvent("shortcut.speakerStats.clicked");
  32. APP.store.dispatch(toggleDialog(SpeakerStats, {
  33. conference: APP.conference
  34. }));
  35. }, "keyboardShortcuts.showSpeakerStats");
  36. /**
  37. * FIXME: Currently focus keys are directly implemented below in onkeyup.
  38. * They should be moved to the SmallVideo instead.
  39. */
  40. KeyboardShortcut._addShortcutToHelp("0", "keyboardShortcuts.focusLocal");
  41. KeyboardShortcut._addShortcutToHelp("1-9", "keyboardShortcuts.focusRemote");
  42. }
  43. /**
  44. * Shows or hides the keyboard shortcuts dialog.
  45. * @param {boolean} show whether to show or hide the dialog
  46. */
  47. function showKeyboardShortcutsPanel(show) {
  48. if (show
  49. && !APP.UI.messageHandler.isDialogOpened()
  50. && keyboardShortcutDialog === null) {
  51. let msg = $('#keyboard-shortcuts').html();
  52. let buttons = { Close: true };
  53. keyboardShortcutDialog = APP.UI.messageHandler.openDialog(
  54. 'keyboardShortcuts.keyboardShortcuts', msg, true, buttons);
  55. } else {
  56. if (keyboardShortcutDialog !== null) {
  57. keyboardShortcutDialog.close();
  58. keyboardShortcutDialog = null;
  59. }
  60. }
  61. }
  62. /**
  63. * Map of shortcuts. When a shortcut is registered it enters the mapping.
  64. * @type {{}}
  65. */
  66. let _shortcuts = {};
  67. /**
  68. * True if the keyboard shortcuts are enabled and false if not.
  69. * @type {boolean}
  70. */
  71. let enabled = true;
  72. /**
  73. * Maps keycode to character, id of popover for given function and function.
  74. */
  75. var KeyboardShortcut = {
  76. init: function () {
  77. initGlobalShortcuts();
  78. var self = this;
  79. window.onkeyup = function(e) {
  80. if(!enabled) {
  81. return;
  82. }
  83. var key = self._getKeyboardKey(e).toUpperCase();
  84. var num = parseInt(key, 10);
  85. if(!($(":focus").is("input[type=text]") ||
  86. $(":focus").is("input[type=password]") ||
  87. $(":focus").is("textarea"))) {
  88. if (_shortcuts.hasOwnProperty(key)) {
  89. _shortcuts[key].function(e);
  90. }
  91. else if (!isNaN(num) && num >= 0 && num <= 9) {
  92. APP.UI.clickOnVideo(num);
  93. }
  94. //esc while the smileys are visible hides them
  95. } else if (key === "ESCAPE" &&
  96. $('#smileysContainer').is(':visible')) {
  97. APP.UI.toggleSmileys();
  98. }
  99. };
  100. window.onkeydown = function(e) {
  101. if(!enabled) {
  102. return;
  103. }
  104. if(!($(":focus").is("input[type=text]") ||
  105. $(":focus").is("input[type=password]") ||
  106. $(":focus").is("textarea"))) {
  107. var key = self._getKeyboardKey(e).toUpperCase();
  108. if(key === " ") {
  109. if(APP.conference.isLocalAudioMuted())
  110. APP.conference.muteAudio(false);
  111. }
  112. }
  113. };
  114. },
  115. /**
  116. * Enables/Disables the keyboard shortcuts.
  117. * @param {boolean} value - the new value.
  118. */
  119. enable: function (value) {
  120. enabled = value;
  121. },
  122. /**
  123. * Registers a new shortcut.
  124. *
  125. * @param shortcutChar the shortcut character triggering the action
  126. * @param shortcutAttr the "shortcut" html element attribute mappring an
  127. * element to this shortcut and used to show the shortcut character on the
  128. * element tooltip
  129. * @param exec the function to be executed when the shortcut is pressed
  130. * @param helpDescription the description of the shortcut that would appear
  131. * in the help menu
  132. */
  133. registerShortcut: function( shortcutChar,
  134. shortcutAttr,
  135. exec,
  136. helpDescription) {
  137. _shortcuts[shortcutChar] = {
  138. character: shortcutChar,
  139. shortcutAttr: shortcutAttr,
  140. function: exec
  141. };
  142. if (helpDescription)
  143. this._addShortcutToHelp(shortcutChar, helpDescription);
  144. },
  145. /**
  146. * Unregisters a shortcut.
  147. *
  148. * @param shortcutChar unregisters the given shortcut, which means it will
  149. * no longer be usable
  150. */
  151. unregisterShortcut: function(shortcutChar) {
  152. _shortcuts.remove(shortcutChar);
  153. this._removeShortcutFromHelp(shortcutChar);
  154. },
  155. /**
  156. * Returns the tooltip string for the given shortcut attribute.
  157. *
  158. * @param shortcutAttr indicates the popover associated with the shortcut
  159. * @returns {string} the tooltip string to add to the given shortcut popover
  160. * or an empty string if the shortcutAttr is null, an empty string or not
  161. * found in the shortcut mapping
  162. */
  163. getShortcutTooltip: function (shortcutAttr) {
  164. if (typeof shortcutAttr === "string" && shortcutAttr.length > 0) {
  165. for (var key in _shortcuts) {
  166. if (_shortcuts.hasOwnProperty(key)
  167. && _shortcuts[key].shortcutAttr
  168. && _shortcuts[key].shortcutAttr === shortcutAttr) {
  169. return " (" + _shortcuts[key].character + ")";
  170. }
  171. }
  172. }
  173. return "";
  174. },
  175. /**
  176. * @param e a KeyboardEvent
  177. * @returns {string} e.key or something close if not supported
  178. */
  179. _getKeyboardKey: function (e) {
  180. if (typeof e.key === "string") {
  181. return e.key;
  182. }
  183. if (e.type === "keypress" && (
  184. (e.which >= 32 && e.which <= 126) ||
  185. (e.which >= 160 && e.which <= 255) )) {
  186. return String.fromCharCode(e.which);
  187. }
  188. // try to fallback (0-9A-Za-z and QWERTY keyboard)
  189. switch (e.which) {
  190. case 27:
  191. return "Escape";
  192. case 191:
  193. return e.shiftKey ? "?" : "/";
  194. }
  195. if (e.shiftKey || e.type === "keypress") {
  196. return String.fromCharCode(e.which);
  197. } else {
  198. return String.fromCharCode(e.which).toLowerCase();
  199. }
  200. },
  201. /**
  202. * Adds the given shortcut to the help dialog.
  203. *
  204. * @param shortcutChar the shortcut character
  205. * @param shortcutDescriptionKey the description of the shortcut
  206. * @private
  207. */
  208. _addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) {
  209. let listElement = document.createElement("li");
  210. let itemClass = 'shortcuts-list__item';
  211. listElement.className = itemClass;
  212. listElement.id = shortcutChar;
  213. let spanElement = document.createElement("span");
  214. spanElement.className = "item-action";
  215. let kbdElement = document.createElement("kbd");
  216. let classes = 'aui-label regular-key';
  217. kbdElement.className = classes;
  218. kbdElement.innerHTML = shortcutChar;
  219. spanElement.appendChild(kbdElement);
  220. let descriptionElement = document.createElement("span");
  221. let descriptionClass = "shortcuts-list__description";
  222. descriptionElement.className = descriptionClass;
  223. descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey);
  224. APP.translation.translateElement($(descriptionElement));
  225. listElement.appendChild(spanElement);
  226. listElement.appendChild(descriptionElement);
  227. let parentListElement
  228. = document.getElementById("keyboard-shortcuts-list");
  229. if (parentListElement)
  230. parentListElement.appendChild(listElement);
  231. },
  232. /**
  233. * Removes the list element corresponding to the given shortcut from the
  234. * help dialog
  235. * @private
  236. */
  237. _removeShortcutFromHelp: function (shortcutChar) {
  238. var parentListElement
  239. = document.getElementById("keyboard-shortcuts-list");
  240. var shortcutElement = document.getElementById(shortcutChar);
  241. if (shortcutElement)
  242. parentListElement.removeChild(shortcutElement);
  243. }
  244. };
  245. module.exports = KeyboardShortcut;