@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
150 lines • 12.1 kB
JavaScript
// 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, useMemo, useRef, useState } from 'react';
import { useAdaptedSelector } from '../hooks/useAdaptedSelector';
import { useHandlers } from '../hooks/useHandlers';
import { LocalDeviceSettings } from '../components/LocalDeviceSettings';
import { StartCallButton } from '../components/StartCallButton';
import { devicePermissionSelector } from '../selectors/devicePermissionSelector';
import { useSelector } from '../hooks/useSelector';
import { CameraButton, DevicesButton, ErrorBar, _useContainerWidth, useTheme } from "../../../../../react-components/src";
import { getCallingSelector } from "../../../../../calling-component-bindings/src";
import { Image, mergeStyles, Panel, PanelType, Stack } from '@fluentui/react';
import { callDetailsContainerStyles, configurationCenteredContent, configurationSectionStyle, deviceConfigurationStackTokens, fillWidth, logoStyles, panelFocusProps, panelStyles, startCallButtonStyleDesktop } from '../styles/CallConfiguration.styles';
import { LocalPreview } from '../components/LocalPreview';
import { callDetailsStyleDesktop, callDetailsStyleMobile, configurationStackTokensDesktop, configurationStackTokensMobile, configurationContainerStyle, selectionContainerStyle, startCallButtonContainerStyleDesktop, startCallButtonContainerStyleMobile, startCallButtonStyleMobile, titleContainerStyleDesktop, titleContainerStyleMobile } from '../styles/CallConfiguration.styles';
import { useLocale } from '../../localization';
import { bannerNotificationStyles } from '../styles/CallPage.styles';
import { usePropsFor } from '../hooks/usePropsFor';
import { ConfigurationPageErrorBar } from '../components/ConfigurationPageErrorBar';
import { _isSafari } from '../utils';
import { VIDEO_EFFECTS_SIDE_PANE_WIDTH_REM, useVideoEffectsPane } from '../components/SidePane/useVideoEffectsPane';
import { SidePane } from '../components/SidePane/SidePane';
import { useIsParticularSidePaneOpen } from '../components/SidePane/SidePaneProvider';
import { localVideoSelector } from '../../CallComposite/selectors/localVideoStreamSelector';
import { SvgWithWordWrapping } from '../components/SvgWithWordWrapping';
import { getMicrophones, getRole } from '../selectors/baseSelectors';
import { getEnvironmentInfo } from '../selectors/baseSelectors';
/**
* @private
*/
export const ConfigurationPage = (props) => {
var _a;
const { startCallHandler, mobileView, modalLayerHostId, joinCallOptions = {
microphoneCheck: 'requireMicrophoneAvailable'
} } = props;
const theme = useTheme();
const options = useAdaptedSelector(getCallingSelector(DevicesButton));
const localDeviceSettingsHandlers = useHandlers(LocalDeviceSettings);
const { video: cameraPermissionGranted, audio: microphonePermissionGranted } = useSelector(devicePermissionSelector);
const environmentInfo = useSelector(getEnvironmentInfo);
const configContainerRef = useRef(null);
const configWidth = _useContainerWidth(configContainerRef);
/**
* We want to stack the two sections (preview and devices) when the container is less than 450 wide.
* We lose size calculation when the container is less than 450 wide, so we stack the two sections.
*/
const stackConfig = !!configWidth && configWidth < 450;
const errorBarProps = usePropsFor(ErrorBar);
const microphones = useSelector(getMicrophones);
let disableStartCallButton = (!microphonePermissionGranted || (microphones === null || microphones === void 0 ? void 0 : microphones.length) === 0) && joinCallOptions.microphoneCheck === 'requireMicrophoneAvailable';
const role = useSelector(getRole);
const isCameraOn = useSelector(localVideoSelector).isAvailable;
const [cameraLoading, setCameraLoading] = useState(false);
const switchCamera = useCallback((device, options) => __awaiter(void 0, void 0, void 0, function* () {
// Only set camera to be loading if we are switching source while the camera is on
setCameraLoading(isCameraOn);
try {
yield localDeviceSettingsHandlers.onSelectCamera(device, options);
}
finally {
setCameraLoading(false);
}
}), [localDeviceSettingsHandlers, isCameraOn]);
const { onToggleCamera } = usePropsFor(CameraButton);
const toggleCamera = useCallback((options) => __awaiter(void 0, void 0, void 0, function* () {
// Only set camera to loading if we are turning on the camera (i.e. the camera was off)
setCameraLoading(!isCameraOn);
try {
yield onToggleCamera(options);
}
finally {
setCameraLoading(false);
}
}), [isCameraOn, onToggleCamera]);
let filteredLatestErrors = props.latestErrors;
// TODO: move this logic to the error bar selector once role is plumbed from the headless SDK
if (role !== 'Consumer') {
filteredLatestErrors = filteredLatestErrors.filter(e => e.type !== 'callCameraAccessDenied' && e.type !== 'callCameraAccessDeniedSafari');
}
if ((useIsParticularSidePaneOpen('videoeffects') || !isCameraOn) && errorBarProps) {
filteredLatestErrors = filteredLatestErrors.filter(e => e.type !== 'unableToStartVideoEffect');
}
if (role === 'Consumer') {
// If user's role permissions do not allow access to the microphone button then DO NOT disable the start call button
// because microphone device permission is not needed for the user's role
disableStartCallButton = false;
}
const locale = useLocale();
const title = locale.strings.call.configurationPageTitle.length > 0 ? React.createElement(Stack.Item, { className: mobileView ? titleContainerStyleMobile(theme) : titleContainerStyleDesktop(theme) },
React.createElement(SvgWithWordWrapping, { width: mobileView ? 325 : 445, lineHeightPx: 16 * 1.5, bufferHeightPx: 16, text: locale.strings.call.configurationPageTitle, role: "heading" })) : React.createElement(React.Fragment, null);
const callDescription = locale.strings.call.configurationPageCallDetails && React.createElement(Stack.Item, { className: mobileView ? callDetailsStyleMobile(theme) : callDetailsStyleDesktop(theme) }, locale.strings.call.configurationPageCallDetails);
const mobileWithPreview = mobileView && role !== 'Consumer'; // When permission API is not available, we want to show screen saying checking for access (disappears on its own)
// then based on permission setting, we show permission denied or nothing
const { toggleVideoEffectsPane, closeVideoEffectsPane, isVideoEffectsPaneOpen } = useVideoEffectsPane(props.updateSidePaneRenderer, mobileView, props.latestErrors, props.onDismissError);
const startCall = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
closeVideoEffectsPane();
startCallHandler();
}), [startCallHandler, closeVideoEffectsPane]);
const panelLayerProps = useMemo(() => ({
hostId: modalLayerHostId
}), [modalLayerHostId]);
const filteredErrorBarProps = useMemo(() => (Object.assign(Object.assign({}, errorBarProps), { activeErrorMessages: filteredLatestErrors })), [errorBarProps, filteredLatestErrors]);
const containerStyles = useMemo(() => { var _a; return configurationContainerStyle(!mobileView, (_a = props.backgroundImage) === null || _a === void 0 ? void 0 : _a.url); }, [mobileView, (_a = props.backgroundImage) === null || _a === void 0 ? void 0 : _a.url]);
return React.createElement("div", { ref: configContainerRef, className: mergeStyles(containerStyles) },
React.createElement(Stack, { styles: bannerNotificationStyles },
React.createElement(ConfigurationPageErrorBar, { errorBarProps: filteredErrorBarProps, onDismissError: props.onDismissError })),
React.createElement(Stack, { verticalFill: true, grow: true, horizontal: true, className: fillWidth },
React.createElement(Stack, { className: configurationCenteredContent(mobileWithPreview, !!props.logo), verticalAlign: stackConfig && !mobileView ? undefined : 'center', verticalFill: mobileWithPreview, tokens: mobileWithPreview ? configurationStackTokensMobile : configurationStackTokensDesktop },
React.createElement(Stack.Item, { styles: callDetailsContainerStyles },
React.createElement(Logo, { logo: props.logo }),
title,
callDescription),
React.createElement(Stack, { horizontal: configHorizontal(mobileWithPreview, mobileView, stackConfig), horizontalAlign: mobileWithPreview ? 'stretch' : 'center', verticalFill: mobileWithPreview, tokens: deviceConfigurationStackTokens },
role !== 'Consumer' && React.createElement(LocalPreview, { mobileView: mobileWithPreview, showDevicesButton: mobileView, onToggleCamera: toggleCamera, cameraLoading: cameraLoading && !isCameraOn }),
React.createElement(Stack, { styles: mobileView ? undefined : configurationSectionStyle },
!mobileWithPreview && React.createElement(Stack, { className: mobileView ? undefined : selectionContainerStyle(theme, _isSafari(environmentInfo)) },
React.createElement(LocalDeviceSettings, Object.assign({}, options, localDeviceSettingsHandlers, { onSelectCamera: switchCamera, cameraPermissionGranted: cameraPermissionGrantedTrampoline(cameraPermissionGranted), microphonePermissionGranted: micPermissionGrantedTrampoline(microphonePermissionGranted), onClickVideoEffects: toggleVideoEffectsPane }))),
React.createElement(Stack, { styles: mobileWithPreview ? startCallButtonContainerStyleMobile : startCallButtonContainerStyleDesktop, horizontalAlign: mobileWithPreview ? 'stretch' : 'end' },
React.createElement(StartCallButton, { className: mobileWithPreview ? startCallButtonStyleMobile : startCallButtonStyleDesktop, onClick: startCall, disabled: disableStartCallButton, hideIcon: true }))))),
React.createElement(Panel, { isOpen: isVideoEffectsPaneOpen, hasCloseButton: false, isBlocking: false, isHiddenOnDismiss: false, styles: panelStyles, focusTrapZoneProps: panelFocusProps, layerProps: panelLayerProps, type: PanelType.custom, customWidth: `${VIDEO_EFFECTS_SIDE_PANE_WIDTH_REM}rem` },
React.createElement(SidePane, { ariaLabel: isVideoEffectsPaneOpen ? locale.strings.call.videoEffectsPaneAriaLabel : undefined, mobileView: props.mobileView, updateSidePaneRenderer: props.updateSidePaneRenderer, maxWidth: `${VIDEO_EFFECTS_SIDE_PANE_WIDTH_REM}rem`, minWidth: `${VIDEO_EFFECTS_SIDE_PANE_WIDTH_REM}rem` }))));
};
const cameraPermissionGrantedTrampoline = (cameraPermissionGranted, videoState) => {
return cameraPermissionGranted;
};
const micPermissionGrantedTrampoline = (microphonePermissionGranted, audioState) => {
return microphonePermissionGranted;
};
const Logo = (props) => {
if (!props.logo) {
return React.createElement(React.Fragment, null);
}
return React.createElement(Image, { styles: logoStyles(props.logo.shape), src: props.logo.url, alt: props.logo.alt });
};
const configHorizontal = (mobileWithPreview, isMobile, configTooNarrow) => {
if (configTooNarrow && !isMobile) {
return false;
}
return !mobileWithPreview;
};
//# sourceMappingURL=ConfigurationPage.js.map