Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

hooks.web.ts 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import { useEffect } from 'react';
  2. import { batch, useDispatch, useSelector } from 'react-redux';
  3. import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent } from '../analytics/AnalyticsEvents';
  4. import { sendAnalytics } from '../analytics/functions';
  5. import { IReduxState } from '../app/types';
  6. import { getToolbarButtons, isToolbarButtonEnabled } from '../base/config/functions.web';
  7. import { toggleDialog } from '../base/dialog/actions';
  8. import JitsiMeetJS from '../base/lib-jitsi-meet';
  9. import { raiseHand } from '../base/participants/actions';
  10. import { getLocalParticipant, hasRaisedHand } from '../base/participants/functions';
  11. import { toggleChat } from '../chat/actions.web';
  12. import { setGifMenuVisibility } from '../gifs/actions';
  13. import { isGifEnabled } from '../gifs/function.any';
  14. import { registerShortcut, unregisterShortcut } from '../keyboard-shortcuts/actions.any';
  15. import {
  16. close as closeParticipantsPane,
  17. open as openParticipantsPane
  18. } from '../participants-pane/actions.web';
  19. import {
  20. getParticipantsPaneOpen,
  21. isParticipantsPaneEnabled
  22. } from '../participants-pane/functions';
  23. import { addReactionToBuffer } from '../reactions/actions.any';
  24. import { toggleReactionsMenuVisibility } from '../reactions/actions.web';
  25. import { REACTIONS } from '../reactions/constants';
  26. import { shouldDisplayReactionsButtons } from '../reactions/functions.any';
  27. import { startScreenShareFlow } from '../screen-share/actions.web';
  28. import { isScreenVideoShared } from '../screen-share/functions';
  29. import SpeakerStats from '../speaker-stats/components/web/SpeakerStats';
  30. import { isSpeakerStatsDisabled } from '../speaker-stats/functions';
  31. import { toggleTileView } from '../video-layout/actions.any';
  32. import { shouldDisplayTileView } from '../video-layout/functions.any';
  33. import VideoQualityDialog from '../video-quality/components/VideoQualityDialog.web';
  34. import { setFullScreen } from './actions.web';
  35. import { isDesktopShareButtonDisabled } from './functions.web';
  36. export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
  37. const dispatch = useDispatch();
  38. const _isSpeakerStatsDisabled = useSelector(isSpeakerStatsDisabled);
  39. const _isParticipantsPaneEnabled = useSelector(isParticipantsPaneEnabled);
  40. const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);
  41. const _toolbarButtons = useSelector((state: IReduxState) => toolbarButtons || getToolbarButtons(state));
  42. const chatOpen = useSelector((state: IReduxState) => state['features/chat'].isOpen);
  43. const desktopSharingButtonDisabled = useSelector(isDesktopShareButtonDisabled);
  44. const desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
  45. const fullScreen = useSelector((state: IReduxState) => state['features/toolbox'].fullScreen);
  46. const gifsEnabled = useSelector(isGifEnabled);
  47. const participantsPaneOpen = useSelector(getParticipantsPaneOpen);
  48. const raisedHand = useSelector((state: IReduxState) => hasRaisedHand(getLocalParticipant(state)));
  49. const screenSharing = useSelector(isScreenVideoShared);
  50. const tileViewEnabled = useSelector(shouldDisplayTileView);
  51. /**
  52. * Creates an analytics keyboard shortcut event and dispatches an action for
  53. * toggling the display of chat.
  54. *
  55. * @private
  56. * @returns {void}
  57. */
  58. function onToggleChat() {
  59. sendAnalytics(createShortcutEvent(
  60. 'toggle.chat',
  61. ACTION_SHORTCUT_TRIGGERED,
  62. {
  63. enable: !chatOpen
  64. }));
  65. // Checks if there was any text selected by the user.
  66. // Used for when we press simultaneously keys for copying
  67. // text messages from the chat board
  68. if (window.getSelection()?.toString() !== '') {
  69. return false;
  70. }
  71. dispatch(toggleChat());
  72. }
  73. /**
  74. * Creates an analytics keyboard shortcut event and dispatches an action for
  75. * toggling the display of the participants pane.
  76. *
  77. * @private
  78. * @returns {void}
  79. */
  80. function onToggleParticipantsPane() {
  81. sendAnalytics(createShortcutEvent(
  82. 'toggle.participants-pane',
  83. ACTION_SHORTCUT_TRIGGERED,
  84. {
  85. enable: !participantsPaneOpen
  86. }));
  87. if (participantsPaneOpen) {
  88. dispatch(closeParticipantsPane());
  89. } else {
  90. dispatch(openParticipantsPane());
  91. }
  92. }
  93. /**
  94. * Creates an analytics keyboard shortcut event and dispatches an action for
  95. * toggling the display of Video Quality.
  96. *
  97. * @private
  98. * @returns {void}
  99. */
  100. function onToggleVideoQuality() {
  101. sendAnalytics(createShortcutEvent('video.quality'));
  102. dispatch(toggleDialog(VideoQualityDialog));
  103. }
  104. /**
  105. * Dispatches an action for toggling the tile view.
  106. *
  107. * @private
  108. * @returns {void}
  109. */
  110. function onToggleTileView() {
  111. sendAnalytics(createShortcutEvent(
  112. 'toggle.tileview',
  113. ACTION_SHORTCUT_TRIGGERED,
  114. {
  115. enable: !tileViewEnabled
  116. }));
  117. dispatch(toggleTileView());
  118. }
  119. /**
  120. * Creates an analytics keyboard shortcut event and dispatches an action for
  121. * toggling full screen mode.
  122. *
  123. * @private
  124. * @returns {void}
  125. */
  126. function onToggleFullScreen() {
  127. sendAnalytics(createShortcutEvent(
  128. 'toggle.fullscreen',
  129. ACTION_SHORTCUT_TRIGGERED,
  130. {
  131. enable: !fullScreen
  132. }));
  133. dispatch(setFullScreen(!fullScreen));
  134. }
  135. /**
  136. * Creates an analytics keyboard shortcut event and dispatches an action for
  137. * toggling raise hand.
  138. *
  139. * @private
  140. * @returns {void}
  141. */
  142. function onToggleRaiseHand() {
  143. sendAnalytics(createShortcutEvent(
  144. 'toggle.raise.hand',
  145. ACTION_SHORTCUT_TRIGGERED,
  146. { enable: !raisedHand }));
  147. dispatch(raiseHand(!raisedHand));
  148. }
  149. /**
  150. * Creates an analytics keyboard shortcut event and dispatches an action for
  151. * toggling screensharing.
  152. *
  153. * @private
  154. * @returns {void}
  155. */
  156. function onToggleScreenshare() {
  157. // Ignore the shortcut if the button is disabled.
  158. if (desktopSharingButtonDisabled) {
  159. return;
  160. }
  161. sendAnalytics(createShortcutEvent(
  162. 'toggle.screen.sharing',
  163. ACTION_SHORTCUT_TRIGGERED,
  164. {
  165. enable: !screenSharing
  166. }));
  167. if (desktopSharingEnabled && !desktopSharingButtonDisabled) {
  168. dispatch(startScreenShareFlow(!screenSharing));
  169. }
  170. }
  171. /**
  172. * Creates an analytics keyboard shortcut event and dispatches an action for
  173. * toggling speaker stats.
  174. *
  175. * @private
  176. * @returns {void}
  177. */
  178. function onSpeakerStats() {
  179. sendAnalytics(createShortcutEvent(
  180. 'speaker.stats'
  181. ));
  182. dispatch(toggleDialog(SpeakerStats, {
  183. conference: APP.conference
  184. }));
  185. }
  186. useEffect(() => {
  187. const KEYBOARD_SHORTCUTS = [
  188. isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
  189. character: 'A',
  190. exec: onToggleVideoQuality,
  191. helpDescription: 'toolbar.callQuality'
  192. },
  193. isToolbarButtonEnabled('chat', _toolbarButtons) && {
  194. character: 'C',
  195. exec: onToggleChat,
  196. helpDescription: 'keyboardShortcuts.toggleChat'
  197. },
  198. isToolbarButtonEnabled('desktop', _toolbarButtons) && {
  199. character: 'D',
  200. exec: onToggleScreenshare,
  201. helpDescription: 'keyboardShortcuts.toggleScreensharing'
  202. },
  203. _isParticipantsPaneEnabled && isToolbarButtonEnabled('participants-pane', _toolbarButtons) && {
  204. character: 'P',
  205. exec: onToggleParticipantsPane,
  206. helpDescription: 'keyboardShortcuts.toggleParticipantsPane'
  207. },
  208. isToolbarButtonEnabled('raisehand', _toolbarButtons) && {
  209. character: 'R',
  210. exec: onToggleRaiseHand,
  211. helpDescription: 'keyboardShortcuts.raiseHand'
  212. },
  213. isToolbarButtonEnabled('fullscreen', _toolbarButtons) && {
  214. character: 'S',
  215. exec: onToggleFullScreen,
  216. helpDescription: 'keyboardShortcuts.fullScreen'
  217. },
  218. isToolbarButtonEnabled('tileview', _toolbarButtons) && {
  219. character: 'W',
  220. exec: onToggleTileView,
  221. helpDescription: 'toolbar.tileViewToggle'
  222. },
  223. !_isSpeakerStatsDisabled && isToolbarButtonEnabled('stats', _toolbarButtons) && {
  224. character: 'T',
  225. exec: onSpeakerStats,
  226. helpDescription: 'keyboardShortcuts.showSpeakerStats'
  227. }
  228. ];
  229. KEYBOARD_SHORTCUTS.forEach(shortcut => {
  230. if (typeof shortcut === 'object') {
  231. dispatch(registerShortcut({
  232. character: shortcut.character,
  233. handler: shortcut.exec,
  234. helpDescription: shortcut.helpDescription
  235. }));
  236. }
  237. });
  238. // If the buttons for sending reactions are not displayed we should disable the shortcuts too.
  239. if (_shouldDisplayReactionsButtons) {
  240. const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
  241. const onShortcutSendReaction = () => {
  242. dispatch(addReactionToBuffer(key));
  243. sendAnalytics(createShortcutEvent(
  244. `reaction.${key}`
  245. ));
  246. };
  247. return {
  248. character: REACTIONS[key].shortcutChar,
  249. exec: onShortcutSendReaction,
  250. helpDescription: `toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`,
  251. altKey: true
  252. };
  253. });
  254. REACTION_SHORTCUTS.forEach(shortcut => {
  255. dispatch(registerShortcut({
  256. alt: shortcut.altKey,
  257. character: shortcut.character,
  258. handler: shortcut.exec,
  259. helpDescription: shortcut.helpDescription
  260. }));
  261. });
  262. if (gifsEnabled) {
  263. const onGifShortcut = () => {
  264. batch(() => {
  265. dispatch(toggleReactionsMenuVisibility());
  266. dispatch(setGifMenuVisibility(true));
  267. });
  268. };
  269. dispatch(registerShortcut({
  270. character: 'G',
  271. handler: onGifShortcut,
  272. helpDescription: 'keyboardShortcuts.giphyMenu'
  273. }));
  274. }
  275. }
  276. return () => {
  277. [ 'A', 'C', 'D', 'P', 'R', 'S', 'W', 'T', 'G' ].forEach(letter =>
  278. dispatch(unregisterShortcut(letter)));
  279. if (_shouldDisplayReactionsButtons) {
  280. Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
  281. .forEach(letter =>
  282. dispatch(unregisterShortcut(letter, true)));
  283. }
  284. };
  285. }, [
  286. _shouldDisplayReactionsButtons,
  287. chatOpen,
  288. desktopSharingButtonDisabled,
  289. desktopSharingEnabled,
  290. fullScreen,
  291. gifsEnabled,
  292. participantsPaneOpen,
  293. raisedHand,
  294. screenSharing,
  295. tileViewEnabled
  296. ]);
  297. };