選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

hooks.web.ts 11KB

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