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.3KB

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