adding a local preview example (#212)
* adding a local preview example * prettier and lint fixes * PR comments and visual upgrade * Removing control bar from video tile examples * using new local preview in composite * some small nit fixes * storybook fixes * Change files * updating snapshots
This commit is contained in:
Родитель
d7b62e0ea8
Коммит
20cd0cc3c3
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "prerelease",
|
||||
"comment": "Removing references to static media.svg and styles",
|
||||
"packageName": "react-components",
|
||||
"email": "alkwa@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "prerelease",
|
||||
"comment": "Using new local preview in calling composite",
|
||||
"packageName": "react-composites",
|
||||
"email": "alkwa@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -22,9 +22,3 @@ export const videoHint = mergeStyles({
|
|||
maxWidth: '40%',
|
||||
borderRadius: 4
|
||||
});
|
||||
|
||||
export const staticMediaContainer = mergeStyles({
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
});
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { CallVideoIcon, MicIcon } from '@fluentui/react-icons-northstar';
|
||||
import { Stack, Toggle, Image, ImageFit, IImageStyles } from '@fluentui/react';
|
||||
import { CallVideoOffIcon } from '@fluentui/react-icons-northstar';
|
||||
import { Stack, Text } from '@fluentui/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
localPreviewContainerStyle,
|
||||
toggleButtonsBarStyle,
|
||||
toggleButtonsBarToken,
|
||||
toggleStyle
|
||||
} from './styles/LocalPreview.styles';
|
||||
import { localPreviewContainerStyle, cameraOffLabelStyle, localPreviewTileStyle } from './styles/LocalPreview.styles';
|
||||
import { MapToMediaControlsProps, MediaControlsContainerProps } from './consumers/MapToMediaControlsProps';
|
||||
import {
|
||||
connectFuncsToContext,
|
||||
|
@ -18,24 +13,19 @@ import {
|
|||
MapToErrorBarProps,
|
||||
MapToLocalVideoProps
|
||||
} from '../../consumers';
|
||||
import { StreamMedia, VideoTile, ErrorBar as ErrorBarComponent } from 'react-components';
|
||||
import staticMediaSVG from './assets/staticmedia.svg';
|
||||
import {
|
||||
StreamMedia,
|
||||
VideoTile,
|
||||
ErrorBar as ErrorBarComponent,
|
||||
MicrophoneButton,
|
||||
ControlBar,
|
||||
CameraButton
|
||||
} from 'react-components';
|
||||
import { useCallContext } from '../../providers';
|
||||
import { ErrorHandlingProps } from '../../providers/ErrorProvider';
|
||||
import { WithErrorHandling } from '../../utils/WithErrorHandling';
|
||||
import { CommunicationUiErrorFromError } from '../../types/CommunicationUiError';
|
||||
|
||||
const staticAvatarStyle: Partial<IImageStyles> = {
|
||||
image: { maxWidth: '10rem', maxHeight: '10rem', width: '100%', height: '100%' },
|
||||
root: { flexGrow: 1 }
|
||||
};
|
||||
|
||||
const imageProps = {
|
||||
src: staticMediaSVG.toString(),
|
||||
imageFit: ImageFit.contain,
|
||||
maximizeFrame: true
|
||||
};
|
||||
|
||||
const LocalPreviewComponentBase = (
|
||||
props: MediaControlsContainerProps & LocalDeviceSettingsContainerProps & ErrorHandlingProps
|
||||
): JSX.Element => {
|
||||
|
@ -52,53 +42,56 @@ const LocalPreviewComponentBase = (
|
|||
});
|
||||
const ErrorBar = connectFuncsToContext(ErrorBarComponent, MapToErrorBarProps);
|
||||
|
||||
const { localVideoEnabled, isMicrophoneActive } = props;
|
||||
|
||||
return (
|
||||
<Stack className={localPreviewContainerStyle}>
|
||||
<VideoTile
|
||||
styles={localPreviewTileStyle}
|
||||
isVideoReady={isVideoReady}
|
||||
videoProvider={<StreamMedia videoStreamElement={videoStreamElement} />}
|
||||
placeholderProvider={
|
||||
<Image styles={staticAvatarStyle} aria-label="Local video preview image" {...imageProps} />
|
||||
<Stack style={{ width: '100%', height: '100%' }} verticalAlign="center">
|
||||
<Stack.Item align="center">
|
||||
<CallVideoOffIcon />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<Text className={cameraOffLabelStyle}>Your camera is turned off</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="center"
|
||||
verticalAlign="center"
|
||||
tokens={toggleButtonsBarToken}
|
||||
className={toggleButtonsBarStyle}
|
||||
>
|
||||
<CallVideoIcon size="medium" />
|
||||
<Toggle
|
||||
styles={toggleStyle}
|
||||
disabled={isVideoDisabled}
|
||||
onChange={() => {
|
||||
props.toggleLocalVideo().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
ariaLabel="Video Icon"
|
||||
/>
|
||||
<MicIcon size="medium" />
|
||||
<Toggle
|
||||
styles={toggleStyle}
|
||||
disabled={isAudioDisabled}
|
||||
onChange={() => {
|
||||
props.toggleMicrophone().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
ariaLabel="Microphone Icon"
|
||||
/>
|
||||
</Stack>
|
||||
<ControlBar layout="floatingBottom">
|
||||
<CameraButton
|
||||
checked={localVideoEnabled}
|
||||
ariaLabel="Video Icon"
|
||||
disabled={isVideoDisabled}
|
||||
onClick={() => {
|
||||
props.toggleLocalVideo().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MicrophoneButton
|
||||
ariaLabel="Microphone Icon"
|
||||
disabled={isAudioDisabled}
|
||||
checked={isMicrophoneActive}
|
||||
onClick={() => {
|
||||
props.toggleMicrophone().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ControlBar>
|
||||
</VideoTile>
|
||||
<ErrorBar />
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -39,3 +39,15 @@ export const toggleButtonsBarStyle = mergeStyles({
|
|||
width: '100%',
|
||||
backgroundColor: palette.neutralQuaternaryAlt
|
||||
});
|
||||
|
||||
export const cameraOffLabelStyle = mergeStyles({
|
||||
fontFamily: 'Segoe UI Regular',
|
||||
fontSize: '0.625rem', // 10px
|
||||
color: palette.neutralTertiary
|
||||
});
|
||||
|
||||
export const localPreviewTileStyle = {
|
||||
root: {
|
||||
minHeight: '14rem'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -29,13 +29,13 @@ module.exports = {
|
|||
// Note: This triggers babel to retranspile all package dependency files during webpack's compilation step.
|
||||
config.resolve.alias = {
|
||||
...(config.resolve.alias || {}),
|
||||
"@azure/communication-react": path.resolve(__dirname, "../../communication-react/src"),
|
||||
"react-composites": path.resolve(__dirname, "../../react-composites/src"),
|
||||
"@azure/acs-chat-declarative": path.resolve(__dirname, "../../acs-chat-declarative/src"),
|
||||
"@azure/acs-chat-selector": path.resolve(__dirname, "../../acs-chat-selector/src"),
|
||||
"@azure/acs-calling-declarative": path.resolve(__dirname, "../../acs-calling-declarative/src"),
|
||||
"@azure/acs-calling-selector": path.resolve(__dirname, "../../acs-calling-selector/src")
|
||||
}
|
||||
'@azure/communication-react': path.resolve(__dirname, '../../communication-react/src'),
|
||||
'react-composites': path.resolve(__dirname, '../../react-composites/src'),
|
||||
'@azure/acs-chat-declarative': path.resolve(__dirname, '../../acs-chat-declarative/src'),
|
||||
'@azure/acs-chat-selector': path.resolve(__dirname, '../../acs-chat-selector/src'),
|
||||
'@azure/acs-calling-declarative': path.resolve(__dirname, '../../acs-calling-declarative/src'),
|
||||
'@azure/acs-calling-selector': path.resolve(__dirname, '../../acs-calling-selector/src')
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { analyticsCookieConsentObtained } from "./telemetry";
|
||||
import { analyticsCookieConsentObtained } from './telemetry';
|
||||
|
||||
describe('storybook telemetry util tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// reset cookie consent window variables
|
||||
(window as any).siteConsent = undefined;
|
||||
})
|
||||
|
||||
describe ('test analyticsCookieConsentObtained', () => {
|
||||
});
|
||||
|
||||
describe('test analyticsCookieConsentObtained', () => {
|
||||
test('analyticsCookieConsentObtained returns false if telemetry library is not initialized', () => {
|
||||
// set cookie consent library to uninitialized
|
||||
(window as any).siteConsent = undefined;
|
||||
|
||||
|
||||
const result = analyticsCookieConsentObtained();
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
@ -27,7 +25,7 @@ describe('storybook telemetry util tests', () => {
|
|||
Analytics: false
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
const result = analyticsCookieConsentObtained();
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
@ -42,7 +40,7 @@ describe('storybook telemetry util tests', () => {
|
|||
Analytics: true
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
const result = analyticsCookieConsentObtained();
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
@ -57,11 +55,10 @@ describe('storybook telemetry util tests', () => {
|
|||
Analytics: false
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
const result = analyticsCookieConsentObtained();
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
|
||||
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
|
||||
|
||||
/**
|
||||
* Check if we have the necessary cookie consent to allow the app insights library to make use of cookies
|
||||
*/
|
||||
export const analyticsCookieConsentObtained = (): boolean => {
|
||||
return !!(window as any).siteConsent && // has telemetry library been initialized
|
||||
return (
|
||||
!!(window as any).siteConsent && // has telemetry library been initialized
|
||||
(!(window as any).siteConsent.isConsentRequired || // check if we need collect consent in this region
|
||||
(window as any).siteConsent.getConsent().Analytics); // check if we have consent to collect analytics telemetry
|
||||
}
|
||||
(window as any).siteConsent.getConsent().Analytics)
|
||||
); // check if we have consent to collect analytics telemetry
|
||||
};
|
||||
|
||||
/**
|
||||
* Start telemetry collection and watch for cookie consent changes.
|
||||
|
@ -18,7 +20,7 @@ export const initTelemetry = () => {
|
|||
if (appInsightsInstance) {
|
||||
createCookieChangedCallback(appInsightsInstance);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the window.cookieConsentChanged that is called when the cookie banner's onConsentChanged is called.
|
||||
|
@ -28,8 +30,8 @@ const createCookieChangedCallback = (applicationInsightsInstance: ApplicationIns
|
|||
(window as any).cookieConsentChanged = () => {
|
||||
const analyticsCookieConsent = analyticsCookieConsentObtained();
|
||||
applicationInsightsInstance.getCookieMgr().setEnabled(analyticsCookieConsent);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Start app insights tracking telemetry
|
||||
|
@ -39,11 +41,10 @@ const createCookieChangedCallback = (applicationInsightsInstance: ApplicationIns
|
|||
const startTelemetry = (cookieConsent: boolean): ApplicationInsights | undefined => {
|
||||
const instrumentationKey = process.env.TELEMETRY_INSTRUMENTATION_KEY;
|
||||
if (!instrumentationKey) {
|
||||
console.warn('No telemetry instrumentationKey provided. Telemetry collection is disabled.')
|
||||
console.warn('No telemetry instrumentationKey provided. Telemetry collection is disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Initialize and start collecting telemetry
|
||||
const appInsights = new ApplicationInsights({
|
||||
config: {
|
||||
|
@ -55,4 +56,4 @@ const startTelemetry = (cookieConsent: boolean): ApplicationInsights | undefined
|
|||
appInsights.loadAppInsights();
|
||||
|
||||
return appInsights;
|
||||
}
|
||||
};
|
||||
|
|
До Ширина: | Высота: | Размер: 1.2 KiB После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -1,5 +1,3 @@
|
|||
// LobbyControlBar.example.tsx
|
||||
|
||||
import { CameraButton, ControlBar, EndCallButton, MicrophoneButton, OptionsButton } from '@azure/communication-react';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import React from 'react';
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { boolean } from '@storybook/addon-knobs';
|
||||
import { Meta } from '@storybook/react/types-6-0';
|
||||
import React from 'react';
|
||||
import { EXAMPLES_FOLDER_PREFIX } from '../../constants';
|
||||
import { getDocs } from './LocalPreviewDocs';
|
||||
import { LocalPreviewExample } from './snippets/LocalPreviewExample.snippet';
|
||||
|
||||
export const LocalPreview: () => JSX.Element = () => {
|
||||
const isVideoAvailable = boolean('Is video available', true);
|
||||
const isCameraEnabled = boolean('Is camera available', true);
|
||||
const isMicrophoneEnabled = boolean('Is microphone available', true);
|
||||
|
||||
return (
|
||||
<LocalPreviewExample
|
||||
isVideoAvailable={isVideoAvailable}
|
||||
isCameraEnabled={isCameraEnabled}
|
||||
isMicrophoneEnabled={isMicrophoneEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: `${EXAMPLES_FOLDER_PREFIX}/Local Preview`,
|
||||
component: LocalPreview,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => getDocs()
|
||||
}
|
||||
}
|
||||
} as Meta;
|
|
@ -0,0 +1,29 @@
|
|||
import { Title, Heading, Description, Canvas, Source } from '@storybook/addon-docs/blocks';
|
||||
import React from 'react';
|
||||
import { LocalPreviewExample } from './snippets/LocalPreviewExample.snippet';
|
||||
|
||||
const LocalPreviewExampleText = require('!!raw-loader!./snippets/LocalPreviewExample.snippet.tsx').default;
|
||||
|
||||
export const getDocs: () => JSX.Element = () => {
|
||||
return (
|
||||
<>
|
||||
<Title>Local Preview</Title>
|
||||
|
||||
<Heading>Basic example</Heading>
|
||||
<Description>
|
||||
To build a local preview, we recommend using the Fluent UI
|
||||
[Stack](https://developer.microsoft.com/en-us/fluentui#/controls/web/stack) as a container as shown in the code
|
||||
below. For enabling and disabling the camera or microphone we suggest using the
|
||||
[Toggle](https://developer.microsoft.com/en-us/fluentui#/controls/web/toggle) component. The area for showing
|
||||
your local preview is a [VideoTile](./?path=/docs/ui-components-videotile--video-tile-component) which can also
|
||||
be used in our video grid layouts.
|
||||
</Description>
|
||||
<Source code={LocalPreviewExampleText} />
|
||||
<Canvas withSource="none">
|
||||
<div style={{ height: '17.188rem' }}>
|
||||
<LocalPreviewExample isVideoAvailable={true} isCameraEnabled={true} isMicrophoneEnabled={true} />
|
||||
</div>
|
||||
</Canvas>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
import { Stack, mergeStyles, Text } from '@fluentui/react';
|
||||
import { CallVideoOffIcon } from '@fluentui/react-northstar';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
StreamMedia,
|
||||
VideoTile,
|
||||
ControlBar,
|
||||
CameraButton,
|
||||
MicrophoneButton,
|
||||
FluentThemeProvider
|
||||
} from 'react-components';
|
||||
import { renderVideoStream } from '../../../utils';
|
||||
|
||||
export interface LocalPreviewProps {
|
||||
isVideoAvailable: boolean;
|
||||
isCameraEnabled: boolean;
|
||||
isMicrophoneEnabled: boolean;
|
||||
}
|
||||
|
||||
export const LocalPreviewExample = ({
|
||||
isVideoAvailable,
|
||||
isCameraEnabled,
|
||||
isMicrophoneEnabled
|
||||
}: LocalPreviewProps): JSX.Element => {
|
||||
const [microphone, setMicrophone] = useState(false);
|
||||
const [camera, setCamera] = useState(false);
|
||||
const theme = useTheme();
|
||||
const palette = theme.palette;
|
||||
|
||||
const localPreviewContainerStyle = mergeStyles({
|
||||
minWidth: '25rem',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxHeight: '18.75rem',
|
||||
minHeight: '16.875rem',
|
||||
margin: '0 auto',
|
||||
background: palette.neutralLighter,
|
||||
color: palette.neutralTertiary
|
||||
});
|
||||
|
||||
const videoTileStyle = {
|
||||
root: {
|
||||
minHeight: '14rem'
|
||||
}
|
||||
};
|
||||
|
||||
const cameraOffLabelStyle = mergeStyles({
|
||||
fontFamily: 'Segoe UI Regular',
|
||||
fontSize: '0.625rem', // 10px
|
||||
color: palette.neutralTertiary
|
||||
});
|
||||
|
||||
return (
|
||||
<FluentThemeProvider fluentTheme={theme}>
|
||||
<Stack style={{ width: '100%', height: '100%' }} verticalAlign="center">
|
||||
<Stack.Item align="center">
|
||||
<Stack className={localPreviewContainerStyle}>
|
||||
<VideoTile
|
||||
styles={videoTileStyle}
|
||||
isVideoReady={isVideoAvailable}
|
||||
// Here this storybook example isn't connected with Azure Communication Services
|
||||
// We would suggest you replace this videoStreamElement below with a rendered video stream from the calling SDK
|
||||
videoProvider={<StreamMedia videoStreamElement={renderVideoStream()} />}
|
||||
placeholderProvider={
|
||||
<Stack style={{ width: '100%', height: '100%' }} verticalAlign="center">
|
||||
<Stack.Item align="center">
|
||||
<CallVideoOffIcon />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<Text className={cameraOffLabelStyle}>Your camera is turned off</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<ControlBar layout="floatingBottom">
|
||||
<CameraButton disabled={!isCameraEnabled} checked={camera} onClick={() => setCamera(!camera)} />
|
||||
<MicrophoneButton
|
||||
disabled={!isMicrophoneEnabled}
|
||||
checked={microphone}
|
||||
onClick={() => setMicrophone(!microphone)}
|
||||
/>
|
||||
</ControlBar>
|
||||
</VideoTile>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</FluentThemeProvider>
|
||||
);
|
||||
};
|
|
@ -1,13 +1,5 @@
|
|||
import {
|
||||
CameraButton,
|
||||
ControlBar,
|
||||
EndCallButton,
|
||||
FluentThemeProvider,
|
||||
MicrophoneButton,
|
||||
StreamMedia,
|
||||
VideoTile
|
||||
} from '@azure/communication-react';
|
||||
import React from 'react';
|
||||
import { FluentThemeProvider, StreamMedia, VideoTile } from 'react-components';
|
||||
import { renderVideoStream } from '../../utils';
|
||||
|
||||
export const VideoTileExample: () => JSX.Element = () => {
|
||||
|
@ -16,7 +8,7 @@ export const VideoTileExample: () => JSX.Element = () => {
|
|||
videoContainer: { border: '5px solid firebrick' },
|
||||
overlayContainer: { background: 'rgba(165, 13, 13, 0.5)' }
|
||||
};
|
||||
const controlBarStyles = { root: { background: 'white' } };
|
||||
|
||||
return (
|
||||
<FluentThemeProvider>
|
||||
<VideoTile
|
||||
|
@ -28,13 +20,7 @@ export const VideoTileExample: () => JSX.Element = () => {
|
|||
avatarName={'Jack Reacher'}
|
||||
invertVideo={true}
|
||||
styles={customStyles}
|
||||
>
|
||||
<ControlBar layout="floatingBottom" styles={controlBarStyles}>
|
||||
<CameraButton />
|
||||
<MicrophoneButton />
|
||||
<EndCallButton />
|
||||
</ControlBar>
|
||||
</VideoTile>
|
||||
></VideoTile>
|
||||
</FluentThemeProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
|
||||
import {
|
||||
CameraButton,
|
||||
ControlBar,
|
||||
EndCallButton,
|
||||
MicrophoneButton,
|
||||
OptionsButton,
|
||||
StreamMedia,
|
||||
VideoTile as VideoTileComponent
|
||||
} from '@azure/communication-react';
|
||||
import { Stack } from '@fluentui/react';
|
||||
import { StreamMedia, VideoTile as VideoTileComponent } from '@azure/communication-react';
|
||||
import { text, boolean, number } from '@storybook/addon-knobs';
|
||||
import { Meta } from '@storybook/react/types-6-0';
|
||||
import React from 'react';
|
||||
|
@ -22,7 +13,6 @@ import { getDocs } from './VideoTileDocs';
|
|||
export const VideoTile: () => JSX.Element = () => {
|
||||
const avatarName = text('Avatar Name', 'John Krasinski');
|
||||
const isVideoReady = boolean('Is Video Ready', false);
|
||||
const showControlBarComponent = boolean('Show Control Bar (Not a part of this component)', false);
|
||||
const invertVideo = boolean('Invert Video', false);
|
||||
const width = number('Width', 400, {
|
||||
range: true,
|
||||
|
@ -46,18 +36,7 @@ export const VideoTile: () => JSX.Element = () => {
|
|||
styles={{
|
||||
root: { height: height, width: width }
|
||||
}}
|
||||
>
|
||||
{showControlBarComponent && (
|
||||
<Stack style={{ position: 'absolute', left: '50%', bottom: '1rem' }}>
|
||||
<ControlBar styles={{ root: { position: 'relative', left: '-50%' } }}>
|
||||
<CameraButton />
|
||||
<MicrophoneButton />
|
||||
<OptionsButton />
|
||||
<EndCallButton />
|
||||
</ControlBar>
|
||||
</Stack>
|
||||
)}
|
||||
</VideoTileComponent>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -381,7 +381,7 @@ exports[`storybook snapshot tests Storyshots Examples/IncomingCallAlerts Incomin
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ms-Stack css-118 css-48"
|
||||
className="ms-Stack css-119 css-48"
|
||||
>
|
||||
<div
|
||||
className="ms-Stack css-51"
|
||||
|
@ -580,10 +580,10 @@ exports[`storybook snapshot tests Storyshots Examples/IncomingCallAlerts Incomin
|
|||
className="ms-Stack css-2"
|
||||
>
|
||||
<div
|
||||
className="ms-Stack css-114 css-3"
|
||||
className="ms-Stack css-115 css-3"
|
||||
>
|
||||
<div
|
||||
className="ms-Stack css-115 css-4"
|
||||
className="ms-Stack css-116 css-4"
|
||||
>
|
||||
<div
|
||||
aria-label="John Doe"
|
||||
|
@ -647,7 +647,7 @@ exports[`storybook snapshot tests Storyshots Examples/IncomingCallAlerts Incomin
|
|||
className="ms-Stack css-21"
|
||||
>
|
||||
<button
|
||||
className="ms-Button ms-Button--default css-117 root-22"
|
||||
className="ms-Button ms-Button--default css-118 root-22"
|
||||
data-is-focusable={true}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
@ -685,7 +685,7 @@ exports[`storybook snapshot tests Storyshots Examples/IncomingCallAlerts Incomin
|
|||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="ms-Button ms-Button--default css-116 root-22"
|
||||
className="ms-Button ms-Button--default css-117 root-22"
|
||||
data-is-focusable={true}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { CallVideoIcon, MicIcon } from '@fluentui/react-icons-northstar';
|
||||
import { Stack, Toggle, Image, ImageFit, IImageStyles, mergeStyles } from '@fluentui/react';
|
||||
import { CallVideoOffIcon } from '@fluentui/react-icons-northstar';
|
||||
import { Stack, Text } from '@fluentui/react';
|
||||
import { localPreviewContainerStyle, cameraOffLabelStyle, localPreviewTileStyle } from './styles/LocalPreview.styles';
|
||||
import React from 'react';
|
||||
import {
|
||||
localPreviewContainerStyle,
|
||||
toggleButtonsBarStyle,
|
||||
toggleButtonsBarToken,
|
||||
toggleStyle
|
||||
} from './styles/LocalPreview.styles';
|
||||
import { MapToMediaControlsProps, MediaControlsContainerProps } from './consumers/MapToMediaControlsProps';
|
||||
import { ErrorBar as ErrorBarComponent, StreamMedia, VideoTile } from 'react-components';
|
||||
import {
|
||||
CameraButton,
|
||||
ControlBar,
|
||||
ErrorBar as ErrorBarComponent,
|
||||
MicrophoneButton,
|
||||
StreamMedia,
|
||||
VideoTile
|
||||
} from 'react-components';
|
||||
import {
|
||||
connectFuncsToContext,
|
||||
MapToLocalVideoProps,
|
||||
|
@ -24,20 +26,6 @@ import {
|
|||
LocalDeviceSettingsContainerProps
|
||||
} from 'react-composites';
|
||||
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import staticMediaSVG from '../assets/staticmedia.svg';
|
||||
|
||||
const staticAvatarStyle: Partial<IImageStyles> = {
|
||||
image: { maxWidth: '10rem', maxHeight: '10rem', width: '100%', height: '100%' },
|
||||
root: { flexGrow: 1 }
|
||||
};
|
||||
|
||||
const imageProps = {
|
||||
src: staticMediaSVG.toString(),
|
||||
imageFit: ImageFit.contain,
|
||||
maximizeFrame: true
|
||||
};
|
||||
|
||||
const LocalPreviewComponentBase = (
|
||||
props: MediaControlsContainerProps & LocalDeviceSettingsContainerProps & ErrorHandlingProps
|
||||
): JSX.Element => {
|
||||
|
@ -47,7 +35,6 @@ const LocalPreviewComponentBase = (
|
|||
// we haven't properly properly exported this component to make it re-usable
|
||||
// we should create a MapToLocalPreviewProps, instead of using MapToMediaControlsProps and MapToLocalDeviceSettingsProps
|
||||
const { localVideoStream } = useCallContext();
|
||||
const theme = useTheme();
|
||||
|
||||
const { isVideoReady, videoStreamElement } = MapToLocalVideoProps({
|
||||
stream: localVideoStream,
|
||||
|
@ -55,53 +42,56 @@ const LocalPreviewComponentBase = (
|
|||
});
|
||||
const ErrorBar = connectFuncsToContext(ErrorBarComponent, MapToErrorBarProps);
|
||||
|
||||
const { localVideoEnabled, isMicrophoneActive } = props;
|
||||
|
||||
return (
|
||||
<Stack className={localPreviewContainerStyle}>
|
||||
<VideoTile
|
||||
styles={localPreviewTileStyle}
|
||||
isVideoReady={isVideoReady}
|
||||
videoProvider={<StreamMedia videoStreamElement={videoStreamElement} />}
|
||||
placeholderProvider={
|
||||
<Image styles={staticAvatarStyle} aria-label="Local video preview image" {...imageProps} />
|
||||
<Stack style={{ width: '100%', height: '100%' }} verticalAlign="center">
|
||||
<Stack.Item align="center">
|
||||
<CallVideoOffIcon />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<Text className={cameraOffLabelStyle}>Your camera is turned off</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="center"
|
||||
verticalAlign="center"
|
||||
tokens={toggleButtonsBarToken}
|
||||
className={mergeStyles(toggleButtonsBarStyle, { background: theme.palette.neutralLight })}
|
||||
>
|
||||
<CallVideoIcon size="medium" style={{ color: theme.palette.black }} />
|
||||
<Toggle
|
||||
styles={toggleStyle}
|
||||
disabled={isVideoDisabled}
|
||||
onChange={() => {
|
||||
props.toggleLocalVideo().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
ariaLabel="Video Icon"
|
||||
/>
|
||||
<MicIcon size="medium" style={{ color: theme.palette.black }} />
|
||||
<Toggle
|
||||
styles={toggleStyle}
|
||||
disabled={isAudioDisabled}
|
||||
onChange={() => {
|
||||
props.toggleMicrophone().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
ariaLabel="Microphone Icon"
|
||||
/>
|
||||
</Stack>
|
||||
<ControlBar layout="floatingBottom">
|
||||
<CameraButton
|
||||
checked={localVideoEnabled}
|
||||
ariaLabel="Video Icon"
|
||||
disabled={isVideoDisabled}
|
||||
onClick={() => {
|
||||
props.toggleLocalVideo().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MicrophoneButton
|
||||
ariaLabel="Microphone Icon"
|
||||
disabled={isAudioDisabled}
|
||||
checked={isMicrophoneActive}
|
||||
onClick={() => {
|
||||
props.toggleMicrophone().catch((error) => {
|
||||
if (props.onErrorCallback) {
|
||||
props.onErrorCallback(CommunicationUiErrorFromError(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ControlBar>
|
||||
</VideoTile>
|
||||
<ErrorBar />
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -31,10 +31,22 @@ export const localPreviewContainerStyle = mergeStyles({
|
|||
maxHeight: '18.75rem',
|
||||
minHeight: '16.875rem',
|
||||
background: palette.neutralLighter,
|
||||
color: palette.neutralPrimaryAlt
|
||||
color: palette.neutralTertiary
|
||||
});
|
||||
|
||||
export const cameraOffLabelStyle = mergeStyles({
|
||||
fontFamily: 'Segoe UI Regular',
|
||||
fontSize: '0.625rem', // 10px
|
||||
color: palette.neutralTertiary
|
||||
});
|
||||
|
||||
export const toggleButtonsBarStyle = mergeStyles({
|
||||
height: '2.8125rem',
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
export const localPreviewTileStyle = {
|
||||
root: {
|
||||
minHeight: '14rem'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24">
|
||||
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
|
||||
<title>ic_fluent_person_24_filled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="🔍-Product-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_fluent_person_24_filled" fill="#D7D7D7" fill-rule="nonzero">
|
||||
<path d="M17.7541747,13.999921 C18.9961948,13.999921 20.0030511,15.0067773 20.0030511,16.2487975 L20.0030511,17.1672553 C20.0030511,17.7406209 19.8238304,18.2996465 19.4904678,18.7661395 C17.9445793,20.9293884 15.4202806,22.0010712 12,22.0010712 C8.57903185,22.0010712 6.05606966,20.9289147 4.51390935,18.7645697 C4.18194679,18.2986691 4.00354153,17.7408416 4.00354153,17.1687745 L4.00354153,16.2487975 C4.00354153,15.0067773 5.0103978,13.999921 6.25241795,13.999921 L17.7541747,13.999921 Z M12,2.0046246 C14.7614237,2.0046246 17,4.24320085 17,7.0046246 C17,9.76604835 14.7614237,12.0046246 12,12.0046246 C9.23857625,12.0046246 7,9.76604835 7,7.0046246 C7,4.24320085 9.23857625,2.0046246 12,2.0046246 Z" id="🎨-Color"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.2 KiB |
Загрузка…
Ссылка в новой задаче