123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- import { useEffect } from 'react';
- import { batch, useDispatch, useSelector } from 'react-redux';
-
- import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent } from '../analytics/AnalyticsEvents';
- import { sendAnalytics } from '../analytics/functions';
- import { IReduxState } from '../app/types';
- import { toggleDialog } from '../base/dialog/actions';
- import { isIosMobileBrowser, isIpadMobileBrowser } from '../base/environment/utils';
- import { HELP_BUTTON_ENABLED } from '../base/flags/constants';
- import { getFeatureFlag } from '../base/flags/functions';
- import JitsiMeetJS from '../base/lib-jitsi-meet';
- import { raiseHand } from '../base/participants/actions';
- import { getLocalParticipant, hasRaisedHand } from '../base/participants/functions';
- import { isToggleCameraEnabled } from '../base/tracks/functions.web';
- import { toggleChat } from '../chat/actions.web';
- import ChatButton from '../chat/components/web/ChatButton';
- import { useEmbedButton } from '../embed-meeting/hooks';
- import { useEtherpadButton } from '../etherpad/hooks';
- import { useFeedbackButton } from '../feedback/hooks.web';
- import { setGifMenuVisibility } from '../gifs/actions';
- import { isGifEnabled } from '../gifs/function.any';
- import InviteButton from '../invite/components/add-people-dialog/web/InviteButton';
- import { registerShortcut, unregisterShortcut } from '../keyboard-shortcuts/actions';
- import { useKeyboardShortcutsButton } from '../keyboard-shortcuts/hooks';
- import NoiseSuppressionButton from '../noise-suppression/components/NoiseSuppressionButton';
- import {
- close as closeParticipantsPane,
- open as openParticipantsPane
- } from '../participants-pane/actions.web';
- import {
- getParticipantsPaneOpen,
- isParticipantsPaneEnabled
- } from '../participants-pane/functions';
- import { useParticipantPaneButton } from '../participants-pane/hooks.web';
- import { addReactionToBuffer } from '../reactions/actions.any';
- import { toggleReactionsMenuVisibility } from '../reactions/actions.web';
- import RaiseHandContainerButton from '../reactions/components/web/RaiseHandContainerButtons';
- import { REACTIONS } from '../reactions/constants';
- import { shouldDisplayReactionsButtons } from '../reactions/functions.any';
- import { useReactionsButton } from '../reactions/hooks.web';
- import { useLiveStreamingButton, useRecordingButton } from '../recording/hooks.web';
- import { isSalesforceEnabled } from '../salesforce/functions';
- import { startScreenShareFlow } from '../screen-share/actions.web';
- import ShareAudioButton from '../screen-share/components/web/ShareAudioButton';
- import { isScreenAudioSupported, isScreenVideoShared } from '../screen-share/functions';
- import { useSecurityDialogButton } from '../security/hooks.web';
- import SettingsButton from '../settings/components/web/SettingsButton';
- import { useSharedVideoButton } from '../shared-video/hooks';
- import SpeakerStats from '../speaker-stats/components/web/SpeakerStats';
- import { isSpeakerStatsDisabled } from '../speaker-stats/functions';
- import { useSpeakerStatsButton } from '../speaker-stats/hooks.web';
- import { useClosedCaptionButton } from '../subtitles/hooks.web';
- import { toggleTileView } from '../video-layout/actions.any';
- import { shouldDisplayTileView } from '../video-layout/functions.web';
- import { useTileViewButton } from '../video-layout/hooks';
- import VideoQualityButton from '../video-quality/components/VideoQualityButton.web';
- import VideoQualityDialog from '../video-quality/components/VideoQualityDialog.web';
- import { useVirtualBackgroundButton } from '../virtual-background/hooks';
- import { useWhiteboardButton } from '../whiteboard/hooks';
-
- import { setFullScreen } from './actions.web';
- import DownloadButton from './components/DownloadButton';
- import HelpButton from './components/HelpButton';
- import AudioSettingsButton from './components/web/AudioSettingsButton';
- import CustomOptionButton from './components/web/CustomOptionButton';
- import FullscreenButton from './components/web/FullscreenButton';
- import LinkToSalesforceButton from './components/web/LinkToSalesforceButton';
- import ProfileButton from './components/web/ProfileButton';
- import ShareDesktopButton from './components/web/ShareDesktopButton';
- import ToggleCameraButton from './components/web/ToggleCameraButton';
- import VideoSettingsButton from './components/web/VideoSettingsButton';
- import { isButtonEnabled, isDesktopShareButtonDisabled } from './functions.web';
- import { ICustomToolbarButton, IToolboxButton, ToolbarButton } from './types';
-
-
- const microphone = {
- key: 'microphone',
- Content: AudioSettingsButton,
- group: 0
- };
-
- const camera = {
- key: 'camera',
- Content: VideoSettingsButton,
- group: 0
- };
-
- const profile = {
- key: 'profile',
- Content: ProfileButton,
- group: 1
- };
-
- const chat = {
- key: 'chat',
- Content: ChatButton,
- group: 2
- };
-
- const desktop = {
- key: 'desktop',
- Content: ShareDesktopButton,
- group: 2
- };
-
- // In Narrow layout and mobile web we are using drawer for popups and that is why it is better to include
- // all forms of reactions in the overflow menu. Otherwise the toolbox will be hidden and the reactions popup
- // misaligned.
- const raisehand = {
- key: 'raisehand',
- Content: RaiseHandContainerButton,
- group: 2
- };
-
- const invite = {
- key: 'invite',
- Content: InviteButton,
- group: 2
- };
-
- const toggleCamera = {
- key: 'toggle-camera',
- Content: ToggleCameraButton,
- group: 2
- };
-
- const videoQuality = {
- key: 'videoquality',
- Content: VideoQualityButton,
- group: 2
- };
-
- const fullscreen = {
- key: 'fullscreen',
- Content: FullscreenButton,
- group: 2
- };
-
- const linkToSalesforce = {
- key: 'linktosalesforce',
- Content: LinkToSalesforceButton,
- group: 2
- };
-
- const shareAudio = {
- key: 'shareaudio',
- Content: ShareAudioButton,
- group: 3
- };
-
- const noiseSuppression = {
- key: 'noisesuppression',
- Content: NoiseSuppressionButton,
- group: 3
- };
-
- const settings = {
- key: 'settings',
- Content: SettingsButton,
- group: 4
- };
-
- const download = {
- key: 'download',
- Content: DownloadButton,
- group: 4
- };
-
- const help = {
- key: 'help',
- Content: HelpButton,
- group: 4
- };
-
- /**
- * A hook that returns the toggle camera button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function useToggleCameraButton() {
- const toggleCameraEnabled = useSelector(isToggleCameraEnabled);
-
- if (toggleCameraEnabled) {
- return toggleCamera;
- }
- }
-
- /**
- * A hook that returns the desktop sharing button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function getDesktopSharingButton() {
- if (JitsiMeetJS.isDesktopSharingEnabled()) {
- return desktop;
- }
- }
-
- /**
- * A hook that returns the fullscreen button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function getFullscreenButton() {
- if (!isIosMobileBrowser() || isIpadMobileBrowser()) {
- return fullscreen;
- }
- }
-
- /**
- * A hook that returns the "link to salesforce" button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function useLinkToSalesforceButton() {
- const _isSalesforceEnabled = useSelector(isSalesforceEnabled);
-
- if (_isSalesforceEnabled) {
- return linkToSalesforce;
- }
- }
-
-
- /**
- * A hook that returns the share audio button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function getShareAudioButton() {
- if (JitsiMeetJS.isDesktopSharingEnabled() && isScreenAudioSupported()) {
- return shareAudio;
- }
- }
-
- /**
- * A hook that returns the download button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function useDownloadButton() {
- const visible = useSelector(
- (state: IReduxState) => typeof state['features/base/config'].deploymentUrls?.downloadAppsUrl === 'string');
-
- if (visible) {
- return download;
- }
- }
-
- /**
- * A hook that returns the help button if it is enabled and undefined otherwise.
- *
- * @returns {Object | undefined}
- */
- function useHelpButton() {
- const visible = useSelector(
- (state: IReduxState) =>
- typeof state['features/base/config'].deploymentUrls?.userDocumentationURL === 'string'
- && getFeatureFlag(state, HELP_BUTTON_ENABLED, true));
-
- if (visible) {
- return help;
- }
- }
-
- /**
- * Returns all buttons that could be rendered.
- *
- * @param {Object} _customToolbarButtons - An array containing custom buttons objects.
- * @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
- */
- export function useToolboxButtons(
- _customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } {
- const dekstopSharing = getDesktopSharingButton();
- const toggleCameraButton = useToggleCameraButton();
- const _fullscreen = getFullscreenButton();
- const security = useSecurityDialogButton();
- const reactions = useReactionsButton();
- const participants = useParticipantPaneButton();
- const tileview = useTileViewButton();
- const cc = useClosedCaptionButton();
- const recording = useRecordingButton();
- const liveStreaming = useLiveStreamingButton();
- const linktosalesforce = useLinkToSalesforceButton();
- const shareaudio = getShareAudioButton();
- const shareVideo = useSharedVideoButton();
- const whiteboard = useWhiteboardButton();
- const etherpad = useEtherpadButton();
- const virtualBackground = useVirtualBackgroundButton();
- const speakerStats = useSpeakerStatsButton();
- const shortcuts = useKeyboardShortcutsButton();
- const embed = useEmbedButton();
- const feedback = useFeedbackButton();
- const _download = useDownloadButton();
- const _help = useHelpButton();
-
- const buttons: { [key in ToolbarButton]?: IToolboxButton; } = {
- microphone,
- camera,
- profile,
- desktop: dekstopSharing,
- chat,
- raisehand,
- reactions,
- 'participants-pane': participants,
- invite,
- tileview,
- 'toggle-camera': toggleCameraButton,
- videoquality: videoQuality,
- fullscreen: _fullscreen,
- security,
- closedcaptions: cc,
- recording,
- livestreaming: liveStreaming,
- linktosalesforce,
- sharedvideo: shareVideo,
- shareaudio,
- noisesuppression: noiseSuppression,
- whiteboard,
- etherpad,
- 'select-background': virtualBackground,
- stats: speakerStats,
- settings,
- shortcuts,
- embedmeeting: embed,
- feedback,
- download: _download,
- help: _help
- };
- const buttonKeys = Object.keys(buttons) as ToolbarButton[];
-
- buttonKeys.forEach(
- key => typeof buttons[key] === 'undefined' && delete buttons[key]);
-
- const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
- prev[id] = {
- backgroundColor,
- key: id,
- id,
- Content: CustomOptionButton,
- group: 4,
- icon,
- text
- };
-
- return prev;
- }, {} as { [key: string]: ICustomToolbarButton; });
-
- return {
- ...buttons,
- ...customButtons
- };
- }
-
-
- export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
- const dispatch = useDispatch();
- const _isSpeakerStatsDisabled = useSelector(isSpeakerStatsDisabled);
- const _isParticipantsPaneEnabled = useSelector(isParticipantsPaneEnabled);
- const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);
- const _toolbarButtons = useSelector(
- (state: IReduxState) => toolbarButtons || state['features/toolbox'].toolbarButtons);
- const chatOpen = useSelector((state: IReduxState) => state['features/chat'].isOpen);
- const desktopSharingButtonDisabled = useSelector(isDesktopShareButtonDisabled);
- const desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
- const fullScreen = useSelector((state: IReduxState) => state['features/toolbox'].fullScreen);
- const gifsEnabled = useSelector(isGifEnabled);
- const participantsPaneOpen = useSelector(getParticipantsPaneOpen);
- const raisedHand = useSelector((state: IReduxState) => hasRaisedHand(getLocalParticipant(state)));
- const screenSharing = useSelector(isScreenVideoShared);
- const tileViewEnabled = useSelector(shouldDisplayTileView);
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling the display of chat.
- *
- * @private
- * @returns {void}
- */
- function onToggleChat() {
- sendAnalytics(createShortcutEvent(
- 'toggle.chat',
- ACTION_SHORTCUT_TRIGGERED,
- {
- enable: !chatOpen
- }));
-
- // Checks if there was any text selected by the user.
- // Used for when we press simultaneously keys for copying
- // text messages from the chat board
- if (window.getSelection()?.toString() !== '') {
- return false;
- }
-
- dispatch(toggleChat());
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling the display of the participants pane.
- *
- * @private
- * @returns {void}
- */
- function onToggleParticipantsPane() {
- sendAnalytics(createShortcutEvent(
- 'toggle.participants-pane',
- ACTION_SHORTCUT_TRIGGERED,
- {
- enable: !participantsPaneOpen
- }));
-
- if (participantsPaneOpen) {
- dispatch(closeParticipantsPane());
- } else {
- dispatch(openParticipantsPane());
- }
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling the display of Video Quality.
- *
- * @private
- * @returns {void}
- */
- function onToggleVideoQuality() {
- sendAnalytics(createShortcutEvent('video.quality'));
-
- dispatch(toggleDialog(VideoQualityDialog));
- }
-
- /**
- * Dispatches an action for toggling the tile view.
- *
- * @private
- * @returns {void}
- */
- function onToggleTileView() {
- sendAnalytics(createShortcutEvent(
- 'toggle.tileview',
- ACTION_SHORTCUT_TRIGGERED,
- {
- enable: !tileViewEnabled
- }));
-
- dispatch(toggleTileView());
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling full screen mode.
- *
- * @private
- * @returns {void}
- */
- function onToggleFullScreen() {
- sendAnalytics(createShortcutEvent(
- 'toggle.fullscreen',
- ACTION_SHORTCUT_TRIGGERED,
- {
- enable: !fullScreen
- }));
- dispatch(setFullScreen(!fullScreen));
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling raise hand.
- *
- * @private
- * @returns {void}
- */
- function onToggleRaiseHand() {
- sendAnalytics(createShortcutEvent(
- 'toggle.raise.hand',
- ACTION_SHORTCUT_TRIGGERED,
- { enable: !raisedHand }));
-
- dispatch(raiseHand(!raisedHand));
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling screensharing.
- *
- * @private
- * @returns {void}
- */
- function onToggleScreenshare() {
- // Ignore the shortcut if the button is disabled.
- if (desktopSharingButtonDisabled) {
- return;
- }
- sendAnalytics(createShortcutEvent(
- 'toggle.screen.sharing',
- ACTION_SHORTCUT_TRIGGERED,
- {
- enable: !screenSharing
- }));
-
- if (desktopSharingEnabled && !desktopSharingButtonDisabled) {
- dispatch(startScreenShareFlow(!screenSharing));
- }
- }
-
- /**
- * Creates an analytics keyboard shortcut event and dispatches an action for
- * toggling speaker stats.
- *
- * @private
- * @returns {void}
- */
- function onSpeakerStats() {
- sendAnalytics(createShortcutEvent(
- 'speaker.stats'
- ));
-
- dispatch(toggleDialog(SpeakerStats, {
- conference: APP.conference
- }));
- }
-
- useEffect(() => {
- const KEYBOARD_SHORTCUTS = [
- isButtonEnabled('videoquality', _toolbarButtons) && {
- character: 'A',
- exec: onToggleVideoQuality,
- helpDescription: 'toolbar.callQuality'
- },
- isButtonEnabled('chat', _toolbarButtons) && {
- character: 'C',
- exec: onToggleChat,
- helpDescription: 'keyboardShortcuts.toggleChat'
- },
- isButtonEnabled('desktop', _toolbarButtons) && {
- character: 'D',
- exec: onToggleScreenshare,
- helpDescription: 'keyboardShortcuts.toggleScreensharing'
- },
- _isParticipantsPaneEnabled && isButtonEnabled('participants-pane', _toolbarButtons) && {
- character: 'P',
- exec: onToggleParticipantsPane,
- helpDescription: 'keyboardShortcuts.toggleParticipantsPane'
- },
- isButtonEnabled('raisehand', _toolbarButtons) && {
- character: 'R',
- exec: onToggleRaiseHand,
- helpDescription: 'keyboardShortcuts.raiseHand'
- },
- isButtonEnabled('fullscreen', _toolbarButtons) && {
- character: 'S',
- exec: onToggleFullScreen,
- helpDescription: 'keyboardShortcuts.fullScreen'
- },
- isButtonEnabled('tileview', _toolbarButtons) && {
- character: 'W',
- exec: onToggleTileView,
- helpDescription: 'toolbar.tileViewToggle'
- },
- !_isSpeakerStatsDisabled && isButtonEnabled('stats', _toolbarButtons) && {
- character: 'T',
- exec: onSpeakerStats,
- helpDescription: 'keyboardShortcuts.showSpeakerStats'
- }
- ];
-
- KEYBOARD_SHORTCUTS.forEach(shortcut => {
- if (typeof shortcut === 'object') {
- dispatch(registerShortcut({
- character: shortcut.character,
- handler: shortcut.exec,
- helpDescription: shortcut.helpDescription
- }));
- }
- });
-
- // If the buttons for sending reactions are not displayed we should disable the shortcuts too.
- if (_shouldDisplayReactionsButtons) {
- const REACTION_SHORTCUTS = Object.keys(REACTIONS).map(key => {
- const onShortcutSendReaction = () => {
- dispatch(addReactionToBuffer(key));
- sendAnalytics(createShortcutEvent(
- `reaction.${key}`
- ));
- };
-
- return {
- character: REACTIONS[key].shortcutChar,
- exec: onShortcutSendReaction,
- helpDescription: `toolbar.reaction${key.charAt(0).toUpperCase()}${key.slice(1)}`,
- altKey: true
- };
- });
-
- REACTION_SHORTCUTS.forEach(shortcut => {
- dispatch(registerShortcut({
- alt: shortcut.altKey,
- character: shortcut.character,
- handler: shortcut.exec,
- helpDescription: shortcut.helpDescription
- }));
- });
-
- if (gifsEnabled) {
- const onGifShortcut = () => {
- batch(() => {
- dispatch(toggleReactionsMenuVisibility());
- dispatch(setGifMenuVisibility(true));
- });
- };
-
- dispatch(registerShortcut({
- character: 'G',
- handler: onGifShortcut,
- helpDescription: 'keyboardShortcuts.giphyMenu'
- }));
- }
- }
-
- return () => {
- [ 'A', 'C', 'D', 'P', 'R', 'S', 'W', 'T', 'G' ].forEach(letter =>
- dispatch(unregisterShortcut(letter)));
-
- if (_shouldDisplayReactionsButtons) {
- Object.keys(REACTIONS).map(key => REACTIONS[key].shortcutChar)
- .forEach(letter =>
- dispatch(unregisterShortcut(letter, true)));
- }
- };
- }, [
- _shouldDisplayReactionsButtons,
- chatOpen,
- desktopSharingButtonDisabled,
- desktopSharingEnabled,
- fullScreen,
- gifsEnabled,
- participantsPaneOpen,
- raisedHand,
- screenSharing,
- tileViewEnabled
- ]);
- };
|