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

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