Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Include cameras in CameraButton selector",
"packageName": "@internal/calling-component-bindings",
"email": "82062616+prprabhu-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add camera selection menu to CameraButton",
"packageName": "@internal/react-components",
"email": "82062616+prprabhu-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Use split CameraButton in MeetingComposite control bar",
"packageName": "@internal/react-composites",
"email": "82062616+prprabhu-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Update CameraButton story to show device flyout",
"packageName": "@internal/storybook",
"email": "82062616+prprabhu-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export interface CallProviderProps {
export type CameraButtonSelector = (state: CallClientState, props: CallingBaseSelectorProps) => {
disabled: boolean;
checked: boolean;
cameras: VideoDeviceInfo[];
selectedCamera?: VideoDeviceInfo;
};

// @public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export type CameraButtonSelector = (
) => {
disabled: boolean;
checked: boolean;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
cameras: VideoDeviceInfo[];
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
selectedCamera?: VideoDeviceInfo;
};

/**
Expand All @@ -86,7 +90,11 @@ export const cameraButtonSelector: CameraButtonSelector = reselect.createSelecto

return {
disabled: !deviceManager.selectedCamera || !permission,
checked: localVideoStreams !== undefined && localVideoStreams.length > 0 ? !!localVideoFromCall : previewOn
checked: localVideoStreams !== undefined && localVideoStreams.length > 0 ? !!localVideoFromCall : previewOn,
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
cameras: deviceManager.cameras,
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
selectedCamera: deviceManager.selectedCamera
};
}
);
Expand Down
19 changes: 19 additions & 0 deletions packages/communication-react/review/communication-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,21 +488,35 @@ export interface CallState {
// @public
export const CameraButton: (props: CameraButtonProps) => JSX.Element;

// @public
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
menuItemStyles?: IContextualMenuItemStyles;
}

// @public
export interface CameraButtonProps extends ControlBarButtonProps {
cameras?: OptionsDevice[];
enableDeviceSelectionMenu?: boolean;
localVideoViewOptions?: VideoStreamOptions;
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
onToggleCamera?: (options?: VideoStreamOptions) => Promise<void>;
selectedCamera?: OptionsDevice;
strings?: Partial<CameraButtonStrings>;
styles?: Partial<CameraButtonStyles>;
}

// @public
export type CameraButtonSelector = (state: CallClientState, props: CallingBaseSelectorProps) => {
disabled: boolean;
checked: boolean;
cameras: VideoDeviceInfo[];
selectedCamera?: VideoDeviceInfo;
};

// @public
export interface CameraButtonStrings {
cameraMenuTitle: string;
cameraMenuTooltip: string;
offLabel: string;
onLabel: string;
tooltipDisabledContent?: string;
Expand All @@ -511,6 +525,11 @@ export interface CameraButtonStrings {
tooltipVideoLoadingContent?: string;
}

// @public
export interface CameraButtonStyles extends ControlBarButtonStyles {
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
}

// Warning: (ae-incompatible-release-tags) The symbol "ChatAdapter" is marked as @public, but its signature references "FileUploadAdapter" which is marked as @beta
//
// @public
Expand Down
7 changes: 6 additions & 1 deletion packages/communication-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,12 @@ export type { LocalVideoCameraCycleButtonProps } from '../../react-components/sr
export * from '../../react-components/src/localization/locales';
export * from '../../react-components/src/theming';
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
export type { MicrophoneButtonContextualMenuStyles, MicrophoneButtonStyles } from '../../react-components/src';
export type {
CameraButtonContextualMenuStyles,
CameraButtonStyles,
MicrophoneButtonContextualMenuStyles,
MicrophoneButtonStyles
} from '../../react-components/src';

export * from '../../calling-stateful-client/src';
export * from '../../chat-stateful-client/src';
Expand Down
17 changes: 17 additions & 0 deletions packages/react-components/review/react-components.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,27 @@ export type CallParticipantListParticipant = ParticipantListParticipant & {
// @public
export const CameraButton: (props: CameraButtonProps) => JSX.Element;

// @public
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
menuItemStyles?: IContextualMenuItemStyles;
}

// @public
export interface CameraButtonProps extends ControlBarButtonProps {
cameras?: OptionsDevice[];
enableDeviceSelectionMenu?: boolean;
localVideoViewOptions?: VideoStreamOptions;
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
onToggleCamera?: (options?: VideoStreamOptions) => Promise<void>;
selectedCamera?: OptionsDevice;
strings?: Partial<CameraButtonStrings>;
styles?: Partial<CameraButtonStyles>;
}

// @public
export interface CameraButtonStrings {
cameraMenuTitle: string;
cameraMenuTooltip: string;
offLabel: string;
onLabel: string;
tooltipDisabledContent?: string;
Expand All @@ -75,6 +87,11 @@ export interface CameraButtonStrings {
tooltipVideoLoadingContent?: string;
}

// @public
export interface CameraButtonStyles extends ControlBarButtonStyles {
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
}

// @public
export interface ChatMessage extends MessageCommon {
// (undocumented)
Expand Down
93 changes: 92 additions & 1 deletion packages/react-components/src/components/CameraButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { IContextualMenuProps } from '@fluentui/react';
import React, { useCallback, useState } from 'react';
import { useLocale } from '../localization';
import { VideoStreamOptions } from '../types';
import { ControlBarButton, ControlBarButtonProps } from './ControlBarButton';
import { HighContrastAwareIcon } from './HighContrastAwareIcon';

/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
import { IContextualMenuItemStyles, IContextualMenuStyles } from '@fluentui/react';
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
import { ControlBarButtonStyles } from './ControlBarButton';
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
import { OptionsDevice, generateDefaultDeviceMenuProps } from './DevicesButton';

const defaultLocalVideoViewOptions = {
scalingMode: 'Crop',
isMirrored: true
Expand All @@ -30,8 +38,43 @@ export interface CameraButtonStrings {
tooltipOffContent?: string;
/** Tooltip content when the button is disabled due to video loading. */
tooltipVideoLoadingContent?: string;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Title of camera menu
*/
cameraMenuTitle: string;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Tooltip of camera menu
*/
cameraMenuTooltip: string;
}

/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Styles for {@link CameraButton}
*
* @public
*/
export interface CameraButtonStyles extends ControlBarButtonStyles {
/**
* Styles for the {@link CameraButton} menu.
*/
menuStyles?: Partial<CameraButtonContextualMenuStyles>;
}

/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Styles for the {@link CameraButton} menu.
*
* @public
*/
export interface CameraButtonContextualMenuStyles extends IContextualMenuStyles {
/**
* Styles for the items inside the {@link CameraButton} button menu.
*/
menuItemStyles?: IContextualMenuItemStyles;
}
/**
* Props for {@link CameraButton} component.
*
Expand All @@ -48,11 +91,37 @@ export interface CameraButtonProps extends ControlBarButtonProps {
* Options for rendering local video view.
*/
localVideoViewOptions?: VideoStreamOptions;

/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Available cameras for selection
*/
cameras?: OptionsDevice[];
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Camera that is shown as currently selected
*/
selectedCamera?: OptionsDevice;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Callback when a camera is selected
*/
onSelectCamera?: (device: OptionsDevice) => Promise<void>;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Whether to use a {@link SplitButton} with a {@link IContextualMenu} for device selection.
*
* default: false
*/
enableDeviceSelectionMenu?: boolean;
/**
* Optional strings to override in component
*/
strings?: Partial<CameraButtonStrings>;
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
/**
* Styles for {@link CameraButton} and the device selection flyout.
*/
styles?: Partial<CameraButtonStyles>;
}

/**
Expand Down Expand Up @@ -98,6 +167,28 @@ export const CameraButton = (props: CameraButtonProps): JSX.Element => {
onRenderOffIcon={props.onRenderOffIcon ?? onRenderCameraOffIcon}
strings={strings}
labelKey={props.labelKey ?? 'cameraButtonLabel'}
menuProps={props.menuProps ?? generateDefaultDeviceMenuPropsTrampoline(props, strings)}
menuIconProps={props.menuIconProps ?? !enableDeviceSelectionMenuTrampoline(props) ? { hidden: true } : undefined}
split={props.split ?? enableDeviceSelectionMenuTrampoline(props)}
/>
);
};

const generateDefaultDeviceMenuPropsTrampoline = (
props: CameraButtonProps,
strings: CameraButtonStrings
): IContextualMenuProps | undefined => {
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
if (props.enableDeviceSelectionMenu) {
return generateDefaultDeviceMenuProps({ ...props, styles: props.styles?.menuStyles }, strings);
}
return undefined;
};

const enableDeviceSelectionMenuTrampoline = (props: CameraButtonProps): boolean => {
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
if (props.enableDeviceSelectionMenu) {
return true;
}
return false;
};
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface MicrophoneButtonStrings {
*/
export interface MicrophoneButtonStyles extends ControlBarButtonStyles {
/**
* Styles for the {@link DevicesButton} menu.
* Styles for the {@link MicrophoneButton} menu.
*/
menuStyles?: Partial<MicrophoneButtonContextualMenuStyles>;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type { LocalVideoCameraCycleButtonProps } from './LocalVideoCameraButton'

export { CameraButton } from './CameraButton';
export type { CameraButtonProps, CameraButtonStrings } from './CameraButton';
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
export type { CameraButtonContextualMenuStyles, CameraButtonStyles } from './CameraButton';

export { ControlBar } from './ControlBar';
export type { ControlBarProps, ControlBarLayout } from './ControlBar';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"tooltipDisabledContent": "Camera is disabled",
"tooltipOnContent": "Turn off camera",
"tooltipOffContent": "Turn on camera",
"tooltipVideoLoadingContent": "Video is loading"
"tooltipVideoLoadingContent": "Video is loading",
"cameraMenuTitle": "Camera",
"cameraMenuTooltip": "Choose Camera"
},
"microphoneButton": {
"onLabel": "Mute",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ export const CallControls = (props: CallControlsProps): JSX.Element => {
{...cameraButtonProps}
showLabel={!compactMode}
styles={commonButtonStyles}
/* @conditional-compile-remove-from(stable) meeting-composite control-bar-split-buttons */
enableDeviceSelectionMenu={props.splitButtonsForDeviceSelection}
/>
);

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { CameraButton } from '@azure/communication-react';
import { CameraButton, CameraButtonProps } from '@azure/communication-react';
import { Canvas, Description, Heading, Props, Source, Title } from '@storybook/addon-docs';
import { Meta } from '@storybook/react/types-6-0';
import React from 'react';
import React, { useState } from 'react';

import { COMPONENT_FOLDER_PREFIX } from '../../../constants';
import { controlsToAdd, hiddenControl } from '../../../controlsUtils';
import { controlsToAdd, defaultControlsCameras, hiddenControl } from '../../../controlsUtils';
import { CustomCameraButtonExample } from './snippets/Custom.snippet';
import { CameraButtonExample } from './snippets/Default.snippet';
import { CameraButtonWithLabelExample } from './snippets/WithLabel.snippet';
Expand Down Expand Up @@ -78,7 +78,21 @@ const getDocs: () => JSX.Element = () => {
};

const CameraStory = (args): JSX.Element => {
return <CameraButton {...args} />;
return <CameraButtonWithDevices {...args} />;
};

const CameraButtonWithDevices = (props: CameraButtonProps): JSX.Element => {
const [selectedCamera, setSelectedCamera] = useState<{ id: string; name: string }>(defaultControlsCameras[0]);
Comment thread
prprabhu-ms marked this conversation as resolved.
const deviceProps: CameraButtonProps = {
selectedCamera,
onSelectCamera: async (device: { id: string; name: string }) => {
setSelectedCamera(device);
},
onToggleCamera: async (): Promise<void> => {
/* Need a defined callback to show split button */
}
};
return <CameraButton {...props} {...deviceProps} />;
};

// This must be the only named export from this module, and must be named to match the storybook path suffix.
Expand All @@ -92,10 +106,14 @@ export default {
argTypes: {
checked: controlsToAdd.checked,
showLabel: controlsToAdd.showLabel,
cameras: controlsToAdd.cameras,
// Hiding auto-generated controls
onToggleCamera: hiddenControl,
localVideoViewOptions: hiddenControl,
strings: hiddenControl
selectedCamera: hiddenControl,
onSelectCamera: hiddenControl,
strings: hiddenControl,
styles: hiddenControl
},
parameters: {
docs: {
Expand Down
Loading