您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

hooks.web.ts 11KB

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