UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

470 lines 26.8 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import React, { useCallback } from 'react'; import { useState } from 'react'; import { useMemo } from 'react'; import { _DrawerMenu as DrawerMenu, CaptionsSettingsModal } from "../../../../../react-components/src"; import { CaptionsBanner } from "../../../../../react-components/src"; import { _ReactionDrawerMenuItem } from "../../../../../react-components/src"; import { StartCaptionsButton } from "../../../../../react-components/src"; import { HoldButton } from "../../../../../react-components/src"; import { RaiseHandButton } from "../../../../../react-components/src"; import { CUSTOM_BUTTON_OPTIONS, generateCustomCallDrawerButtons, onFetchCustomButtonPropsTrampoline } from '../ControlBar/CustomButton'; import { usePropsFor } from '../../CallComposite/hooks/usePropsFor'; import { useLocale } from '../../localization'; import { isDisabled } from '../../CallComposite/utils'; import { Stack, Toggle, useTheme } from '@fluentui/react'; import { CaptionLanguageSettingsDrawer } from './CaptionLanguageSettingsDrawer'; import { themedToggleButtonStyle } from './MoreDrawer.styles'; import { _spokenLanguageToCaptionLanguage } from "../../../../../react-components/src"; import { useAdapter } from '../../CallComposite/adapter/CallAdapterProvider'; import { useSelector } from '../../CallComposite/hooks/useSelector'; import { getTargetCallees } from '../../CallComposite/selectors/baseSelectors'; import { getTeamsMeetingCoordinates, getIsTeamsMeeting } from '../../CallComposite/selectors/baseSelectors'; import { showDtmfDialer } from '../../CallComposite/utils/MediaGalleryUtils'; import { SpokenLanguageSettingsDrawer } from './SpokenLanguageSettingsDrawer'; import { getRemoteParticipantsConnectedSelector } from '../../CallComposite/selectors/mediaGallerySelector'; import { getCapabilites, getIsTogetherModeActive, getLocalUserId, getIsTeamsCall } from '../../CallComposite/selectors/baseSelectors'; import { CallingRealTimeTextModal } from '../CallingRealTimeTextModal'; const inferCallWithChatControlOptions = (callWithChatControls) => { if (callWithChatControls === false) { return false; } const options = callWithChatControls === true || callWithChatControls === undefined ? {} : callWithChatControls; return options; }; /** @private */ export const MoreDrawer = (props) => { var _a, _b, _c, _d, _e, _f, _g, _h; const theme = useTheme(); const callAdapter = useAdapter(); const drawerMenuItems = []; const { speakers, onSelectSpeaker, onLightDismiss } = props; const localeStrings = useLocale(); const holdButtonProps = usePropsFor(HoldButton); const realTimeTextProps = usePropsFor(CaptionsBanner); const callees = useSelector(getTargetCallees); const participants = useSelector(getRemoteParticipantsConnectedSelector); const allowDtmfDialer = showDtmfDialer(callees, participants, props.dtmfDialerOptions); const [dtmfDialerChecked, setDtmfDialerChecked] = useState((_a = props.dtmfDialerPresent) !== null && _a !== void 0 ? _a : false); const [showRealTimeTextModal, setShowRealTimeTextModal] = useState(false); const openRealTimeTextModal = useCallback(() => { setShowRealTimeTextModal(true); }, []); const onDismissRealTimeTextModal = useCallback(() => { setShowRealTimeTextModal(false); }, []); const raiseHandButtonProps = usePropsFor(RaiseHandButton); const participantCapability = useSelector(getCapabilites); const participantId = useSelector(getLocalUserId); const isTogetherModeActive = useSelector(getIsTogetherModeActive); const isTeamsCall = useSelector(getIsTeamsCall); const isTeamsMeeting = getIsTeamsMeeting(callAdapter.getState()); const onSpeakerItemClick = useCallback((_ev, itemKey) => { const selected = speakers === null || speakers === void 0 ? void 0 : speakers.find((speaker) => speaker.id === itemKey); if (selected) { // This is unsafe - we're only passing in part of the argument to the handler. // But this is a known issue in our state. onSelectSpeaker(selected); } onLightDismiss(); }, [speakers, onSelectSpeaker, onLightDismiss]); const drawerSelectionOptions = inferCallWithChatControlOptions(props.callControls); const showCaptionsButton = props.isCaptionsSupported && drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions.captionsButton); const showRealTimeTextButton = props.isRealTimeTextSupported && drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions.realTimeTextButton); if (props.reactionResources !== undefined) { drawerMenuItems.push({ itemKey: 'reactions', onRendererContent: () => (React.createElement(_ReactionDrawerMenuItem, { onReactionClick: (reaction) => __awaiter(void 0, void 0, void 0, function* () { var _j; (_j = props.onReactionClick) === null || _j === void 0 ? void 0 : _j.call(props, reaction); onLightDismiss(); }), reactionResources: props.reactionResources })) }); } if (props.speakers && props.speakers.length > 0) { drawerMenuItems.push({ itemKey: 'speakers', disabled: props.disableButtonsForHoldScreen, text: props.strings.speakerMenuTitle, iconProps: { iconName: 'MoreDrawerSpeakers' }, subMenuProps: props.speakers.map((speaker) => ({ itemKey: speaker.id, iconProps: { iconName: isDeviceSelected(speaker, props.selectedSpeaker) ? 'MoreDrawerSelectedSpeaker' : 'MoreDrawerSpeakers' }, text: speaker.name, onItemClick: onSpeakerItemClick, secondaryIconProps: isDeviceSelected(speaker, props.selectedSpeaker) ? { iconName: 'Accept' } : undefined })), secondaryText: (_b = props.selectedSpeaker) === null || _b === void 0 ? void 0 : _b.name }); } const { microphones, onSelectMicrophone } = props; const onMicrophoneItemClick = useCallback((_ev, itemKey) => { const selected = microphones === null || microphones === void 0 ? void 0 : microphones.find((mic) => mic.id === itemKey); if (selected) { // This is unsafe - we're only passing in part of the argument to the handler. // But this is a known issue in our state. onSelectMicrophone(selected); } onLightDismiss(); }, [microphones, onSelectMicrophone, onLightDismiss]); if (props.microphones && props.microphones.length > 0) { // Set props as Microphone if speakers can be enumerated else set as Audio Device const speakersAvailable = props.speakers && props.speakers.length > 0; const itemKey = speakersAvailable ? 'microphones' : 'audioDevices'; const text = speakersAvailable ? props.strings.microphoneMenuTitle : props.strings.audioDeviceMenuTitle; const iconName = speakersAvailable ? 'MoreDrawerMicrophones' : 'MoreDrawerSpeakers'; const selectedIconName = speakersAvailable ? 'MoreDrawerSelectedMicrophone' : 'MoreDrawerSelectedSpeaker'; drawerMenuItems.push({ itemKey: itemKey, disabled: props.disableButtonsForHoldScreen, text: text, iconProps: { iconName: iconName }, subMenuProps: props.microphones.map((mic) => ({ itemKey: mic.id, iconProps: { iconName: isDeviceSelected(mic, props.selectedMicrophone) ? selectedIconName : iconName }, text: mic.name, onItemClick: onMicrophoneItemClick, secondaryIconProps: isDeviceSelected(mic, props.selectedMicrophone) ? { iconName: 'Accept' } : undefined, disabled: drawerSelectionOptions !== false ? isDisabled(drawerSelectionOptions.microphoneButton) : undefined })), secondaryText: (_c = props.selectedMicrophone) === null || _c === void 0 ? void 0 : _c.name }); } const dtmfDialerScreenOption = { itemKey: 'dtmfDialerScreenKey', text: !dtmfDialerChecked ? localeStrings.strings.call.dtmfDialerMoreButtonLabelOn : localeStrings.strings.call.dtmfDialerMoreButtonLabelOff, onItemClick: () => { if (props.onSetDialpadPage) { props.onSetDialpadPage(); } setDtmfDialerChecked(!dtmfDialerChecked); onLightDismiss(); }, iconProps: { iconName: 'DtmfDialpadButton', styles: { root: { lineHeight: 0 } } } }; /** * Only render the dtmf dialer if the dialpad for PSTN calls is not present */ if (props.onSetDialpadPage && allowDtmfDialer && drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions.dtmfDialerButton)) { drawerMenuItems.push(dtmfDialerScreenOption); } const galleryLayoutOptions = { itemKey: 'galleryPositionKey', iconProps: { iconName: 'GalleryOptions', styles: { root: { lineHeight: 0 } } }, disabled: props.disableButtonsForHoldScreen, text: localeStrings.strings.call.moreButtonGalleryControlLabel, subMenuProps: [ { itemKey: 'dynamicSelectionKey', text: localeStrings.strings.call.moreButtonGalleryFloatingLocalLayoutLabel, onItemClick: () => { var _a; (_a = props.onUserSetGalleryLayout) === null || _a === void 0 ? void 0 : _a.call(props, 'floatingLocalVideo'); onLightDismiss(); }, iconProps: { iconName: 'FloatingLocalVideoGalleryLayout', styles: { root: { lineHeight: 0 } } }, secondaryIconProps: props.userSetGalleryLayout === 'floatingLocalVideo' ? { iconName: 'Accept' } : undefined }, { itemKey: 'focusedContentSelectionKey', text: localeStrings.strings.call.moreButtonGalleryFocusedContentLayoutLabel, onItemClick: () => { var _a; (_a = props.onUserSetGalleryLayout) === null || _a === void 0 ? void 0 : _a.call(props, 'focusedContent'); onLightDismiss(); }, iconProps: { iconName: 'FocusedContentGalleryLayout', styles: { root: { lineHeight: 0 } } }, secondaryIconProps: props.userSetGalleryLayout === 'focusedContent' ? { iconName: 'Accept' } : undefined } ] }; /* @conditional-compile-remove(gallery-layout-composite) */ const galleryOption = { itemKey: 'defaultSelectionKey', text: localeStrings.strings.call.moreButtonGalleryDefaultLayoutLabel, onItemClick: () => { var _a; (_a = props.onUserSetGalleryLayout) === null || _a === void 0 ? void 0 : _a.call(props, 'default'); onLightDismiss(); }, iconProps: { iconName: 'DefaultGalleryLayout', styles: { root: { lineHeight: 0 } } }, secondaryIconProps: props.userSetGalleryLayout === 'default' ? { iconName: 'Accept' } : undefined }; const togetherModeOption = { itemKey: 'togetherModeSelectionKey', text: localeStrings.strings.call.moreButtonTogetherModeLayoutLabel, onItemClick: () => { var _a; (_a = props.onUserSetGalleryLayout) === null || _a === void 0 ? void 0 : _a.call(props, 'togetherMode'); onLightDismiss(); }, iconProps: { iconName: 'TogetherModeLayout', styles: { root: { lineHeight: 0 } } }, disabled: !(((participantId === null || participantId === void 0 ? void 0 : participantId.kind) === 'microsoftTeamsUser' && ((_d = participantCapability === null || participantCapability === void 0 ? void 0 : participantCapability.startTogetherMode) === null || _d === void 0 ? void 0 : _d.isPresent)) || isTogetherModeActive), secondaryIconProps: props.userSetGalleryLayout === 'default' ? { iconName: 'Accept' } : undefined }; /* @conditional-compile-remove(gallery-layout-composite) */ (_e = galleryLayoutOptions.subMenuProps) === null || _e === void 0 ? void 0 : _e.push(galleryOption); if (isTeamsCall || isTeamsMeeting) { (_f = galleryLayoutOptions.subMenuProps) === null || _f === void 0 ? void 0 : _f.push(togetherModeOption); } if (drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.galleryControlsButton)) { drawerMenuItems.push(galleryLayoutOptions); } if (drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.peopleButton)) { drawerMenuItems.push({ itemKey: 'people', id: 'call-composite-drawer-people-button', text: props.strings.peopleButtonLabel, iconProps: { iconName: 'MoreDrawerPeople' }, onItemClick: props.onPeopleButtonClicked, disabled: isDisabled(drawerSelectionOptions.peopleButton) || props.disableButtonsForHoldScreen }); } if (drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.holdButton)) { drawerMenuItems.push({ itemKey: 'holdButtonKey', disabled: props.disableButtonsForHoldScreen || isDisabled(drawerSelectionOptions.holdButton), text: localeStrings.component.strings.holdButton.tooltipOffContent, onItemClick: () => { holdButtonProps.onToggleHold(); onLightDismiss(); }, iconProps: { iconName: 'HoldCallContextualMenuItem', styles: { root: { lineHeight: 0 } } } }); } const role = (_g = callAdapter.getState().call) === null || _g === void 0 ? void 0 : _g.role; const hideRaiseHandButtonInRoomsCall = callAdapter.getState().isRoomsCall && role && ['Consumer', 'Unknown'].includes(role); if (drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.raiseHandButton) && !hideRaiseHandButtonInRoomsCall) { const raiseHandIcon = raiseHandButtonProps.checked ? 'LowerHandContextualMenuItem' : 'RaiseHandContextualMenuItem'; drawerMenuItems.push({ itemKey: 'raiseHandButtonKey', disabled: props.disableButtonsForHoldScreen || isDisabled(drawerSelectionOptions.raiseHandButton), text: raiseHandButtonProps.checked ? localeStrings.component.strings.raiseHandButton.onLabel : localeStrings.component.strings.raiseHandButton.offLabel, onItemClick: () => { if (raiseHandButtonProps.onToggleRaiseHand) { raiseHandButtonProps.onToggleRaiseHand(); } onLightDismiss(); }, iconProps: { iconName: raiseHandIcon, styles: { root: { lineHeight: 0 } } } }); } const teamsMeetingCoordinates = getTeamsMeetingCoordinates(callAdapter.getState()); if (drawerSelectionOptions !== false && isEnabled(drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.teamsMeetingPhoneCallButton) && isTeamsMeeting && teamsMeetingCoordinates) { drawerMenuItems.push({ itemKey: 'phoneCallInfoKey', disabled: isDisabled(drawerSelectionOptions.teamsMeetingPhoneCallButton), text: localeStrings.strings.call.phoneCallMoreButtonLabel, onItemClick: () => { var _a; (_a = props.onClickMeetingPhoneInfo) === null || _a === void 0 ? void 0 : _a.call(props); onLightDismiss(); }, iconProps: { iconName: 'PhoneNumberButton', styles: { root: { lineHeight: 0 } } } }); } //Captions drawer menu const supportedSpokenLanguageStrings = useLocale().strings.call.spokenLanguageStrings; //Captions drawer menu const supportedCaptionLanguageStrings = useLocale().strings.call.captionLanguageStrings; const captionSettingsProp = usePropsFor(CaptionsSettingsModal); const startCaptionsButtonProps = usePropsFor(StartCaptionsButton); const [isSpokenLanguageDrawerOpen, setIsSpokenLanguageDrawerOpen] = useState(false); const [isCaptionLanguageDrawerOpen, setIsCaptionLanguageDrawerOpen] = useState(false); // we don't display the setting modal to set the spoken language on mobile // so when spoken language is empty (not set), we default to 'en-us' const [currentSpokenLanguage, setCurrentSpokenLanguage] = useState(captionSettingsProp.currentSpokenLanguage && captionSettingsProp.currentSpokenLanguage !== '' ? captionSettingsProp.currentSpokenLanguage : 'en-us'); const [currentCaptionLanguage, setCurrentCaptionLanguage] = useState((_h = captionSettingsProp.currentCaptionLanguage) !== null && _h !== void 0 ? _h : _spokenLanguageToCaptionLanguage[currentSpokenLanguage]); const onToggleChange = useCallback(() => __awaiter(void 0, void 0, void 0, function* () { if (!captionSettingsProp.isCaptionsFeatureActive) { yield startCaptionsButtonProps.onStartCaptions({ spokenLanguage: currentSpokenLanguage }); } else { startCaptionsButtonProps.onStopCaptions(); } }), [captionSettingsProp.isCaptionsFeatureActive, startCaptionsButtonProps, currentSpokenLanguage]); if (showCaptionsButton) { const captionsDrawerItems = []; const spokenLanguageString = supportedSpokenLanguageStrings ? supportedSpokenLanguageStrings[currentSpokenLanguage] : currentSpokenLanguage; drawerMenuItems.push({ itemKey: 'captions', id: 'common-call-composite-captions-button', disabled: props.disableButtonsForHoldScreen, text: props.strings.captionsMenuTitle, iconProps: { iconName: 'CaptionsIcon' }, subMenuProps: captionsDrawerItems }); captionsDrawerItems.push({ itemKey: 'ToggleCaptionsKey', text: captionSettingsProp.isCaptionsFeatureActive ? localeStrings.strings.call.startCaptionsButtonTooltipOnContent : localeStrings.strings.call.startCaptionsButtonTooltipOffContent, iconProps: { iconName: captionSettingsProp.isCaptionsFeatureActive ? 'CaptionsOffIcon' : 'CaptionsIcon', styles: { root: { lineHeight: 0 } } }, onItemClick: onToggleChange, disabled: props.disableButtonsForHoldScreen, secondaryComponent: (React.createElement(Stack, { verticalFill: true, verticalAlign: "center" }, React.createElement(Toggle, { id: "common-call-composite-captions-toggle-button", checked: captionSettingsProp.isCaptionsFeatureActive, styles: themedToggleButtonStyle(theme, captionSettingsProp.isCaptionsFeatureActive), onChange: onToggleChange }))) }); captionsDrawerItems.push({ itemKey: 'ChangeSpokenLanguage', text: props.strings.spokenLanguageMenuTitle, id: 'common-call-composite-captions-spoken-settings-button', secondaryText: spokenLanguageString, iconProps: { iconName: 'ChangeSpokenLanguageIcon', styles: { root: { lineHeight: 0 } } }, disabled: props.disableButtonsForHoldScreen || !captionSettingsProp.isCaptionsFeatureActive, onItemClick: () => { setIsSpokenLanguageDrawerOpen(true); }, secondaryIconProps: { iconName: 'ChevronRight', styles: { root: { lineHeight: 0 } } } }); if (props.useTeamsCaptions) { const captionLanguageString = supportedCaptionLanguageStrings ? supportedCaptionLanguageStrings[currentCaptionLanguage] : currentCaptionLanguage; captionsDrawerItems.push({ itemKey: 'ChangeCaptionLanguage', text: props.strings.captionLanguageMenuTitle, id: 'common-call-composite-captions-subtitle-settings-button', secondaryText: captionLanguageString, iconProps: { iconName: 'ChangeCaptionLanguageIcon', styles: { root: { lineHeight: 0 } } }, disabled: props.disableButtonsForHoldScreen || !captionSettingsProp.isCaptionsFeatureActive, onItemClick: () => { setIsCaptionLanguageDrawerOpen(true); }, secondaryIconProps: { iconName: 'ChevronRight', styles: { root: { lineHeight: 0 } } } }); } } const rttDisabled = props.disableButtonsForHoldScreen || realTimeTextProps.isRealTimeTextOn || props.startRealTimeTextButtonChecked; // rtt if (showRealTimeTextButton) { const realTimeTextDrawerItems = []; drawerMenuItems.push({ itemKey: 'realTimeText', id: 'common-call-composite-rtt-button', disabled: props.disableButtonsForHoldScreen, text: localeStrings.strings.call.realTimeTextLabel, iconProps: { iconName: 'RealTimeTextIcon' }, subMenuProps: realTimeTextDrawerItems }); realTimeTextDrawerItems.push({ itemKey: 'ToggleRTTKey', id: 'common-call-composite-rtt-start-button', text: localeStrings.strings.call.startRealTimeTextLabel, ariaLabel: rttDisabled ? localeStrings.strings.call.disabledStartRealTimeTextLabel : localeStrings.strings.call.startRealTimeTextLabel, iconProps: { iconName: 'RealTimeTextIcon', styles: { root: { lineHeight: 0 } } }, onItemClick: openRealTimeTextModal, disabled: rttDisabled, secondaryComponent: (React.createElement(Stack, { verticalFill: true, verticalAlign: "center" }, React.createElement(Toggle, { id: "common-call-composite-rtt-toggle-button", checked: realTimeTextProps.isRealTimeTextOn || props.startRealTimeTextButtonChecked, styles: themedToggleButtonStyle(theme, realTimeTextProps.isRealTimeTextOn || props.startRealTimeTextButtonChecked), onChange: openRealTimeTextModal }))) }); } const customDrawerButtons = useMemo(() => generateCustomCallDrawerButtons(onFetchCustomButtonPropsTrampoline(drawerSelectionOptions !== false ? drawerSelectionOptions : undefined), drawerSelectionOptions !== false ? drawerSelectionOptions === null || drawerSelectionOptions === void 0 ? void 0 : drawerSelectionOptions.displayType : undefined), [drawerSelectionOptions]); customDrawerButtons['primary'].slice(CUSTOM_BUTTON_OPTIONS.MAX_PRIMARY_MOBILE_CUSTOM_BUTTONS).forEach((element) => { drawerMenuItems.push(element); }); customDrawerButtons['secondary'].forEach((element) => { drawerMenuItems.push(element); }); customDrawerButtons['overflow'].forEach((element) => { const customButtonProps = Object.assign({}, element); // Default Auto dismiss drawer on click unless specified if (customButtonProps.dismissDrawer !== false) { element = Object.assign(Object.assign({}, customButtonProps), { onItemClick: () => { var _a; (_a = customButtonProps.onItemClick) === null || _a === void 0 ? void 0 : _a.call(customButtonProps); onLightDismiss(); } }); } drawerMenuItems.push(element); }); return (React.createElement(React.Fragment, null, showRealTimeTextModal && (React.createElement(CallingRealTimeTextModal, { showModal: showRealTimeTextModal, onDismissModal: onDismissRealTimeTextModal, onStartRealTimeText: props.onStartRealTimeText })), isSpokenLanguageDrawerOpen && showCaptionsButton && (React.createElement(SpokenLanguageSettingsDrawer, { onLightDismiss: props.onLightDismiss, selectLanguage: setCurrentSpokenLanguage, setCurrentLanguage: captionSettingsProp.onSetSpokenLanguage, currentLanguage: currentSpokenLanguage, strings: { menuTitle: props.strings.spokenLanguageMenuTitle }, supportedLanguageStrings: supportedSpokenLanguageStrings })), isCaptionLanguageDrawerOpen && showCaptionsButton && (React.createElement(CaptionLanguageSettingsDrawer, { onLightDismiss: props.onLightDismiss, selectLanguage: setCurrentCaptionLanguage, setCurrentLanguage: captionSettingsProp.onSetCaptionLanguage, currentLanguage: currentCaptionLanguage, strings: { menuTitle: props.strings.captionLanguageMenuTitle }, supportedLanguageStrings: supportedCaptionLanguageStrings })), !isSpokenLanguageDrawerOpen && !isCaptionLanguageDrawerOpen && (React.createElement(DrawerMenu, { items: drawerMenuItems, onLightDismiss: props.onLightDismiss })))); }; const isDeviceSelected = (speaker, selectedSpeaker) => !!selectedSpeaker && speaker.id === selectedSpeaker.id; const isEnabled = (option) => option !== false; //# sourceMappingURL=MoreDrawer.js.map