Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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": "Incorporate PeoplePaneContent into Call Composite",
"packageName": "@internal/react-composites",
"email": "edwardlee@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// Licensed under the MIT license.

import { mergeStyles, Stack } from '@fluentui/react';
/* @conditional-compile-remove(one-to-n-calling) */
import { LayerHost } from '@fluentui/react';
/* @conditional-compile-remove(one-to-n-calling) */
import { useId } from '@fluentui/react-hooks';
import {
_ComplianceBanner,
_ComplianceBannerProps,
Expand All @@ -12,8 +16,20 @@ import {
useTheme
} from '@internal/react-components';
import React, { useMemo, useRef } from 'react';
/* @conditional-compile-remove(one-to-n-calling) */
import { useCallback, useState } from 'react';
/* @conditional-compile-remove(one-to-n-calling) */
import { AvatarPersonaDataCallback } from '../../common/AvatarPersona';
import { containerDivStyles } from '../../common/ContainerRectProps';
/* @conditional-compile-remove(one-to-n-calling) */
import { modalLayerHostStyle } from '../../common/styles/ModalLocalAndRemotePIP.styles';
/* @conditional-compile-remove(one-to-n-calling) */
import { useAdapter } from '../adapter/CallAdapterProvider';
import { CallControls, CallControlsProps } from '../components/CallControls';
/* @conditional-compile-remove(one-to-n-calling) */
import { useSelector } from '../hooks/useSelector';
/* @conditional-compile-remove(one-to-n-calling) */
import { callStatusSelector } from '../selectors/callStatusSelector';
import {
callControlsContainerStyles,
notificationsContainerStyles,
Expand All @@ -23,6 +39,10 @@ import {
galleryParentContainerStyles,
bannerNotificationStyles
} from '../styles/CallPage.styles';
/* @conditional-compile-remove(one-to-n-calling) */
import { CallControlOptions } from '../types/CallControlOptions';
/* @conditional-compile-remove(one-to-n-calling) */
import { CallPane, CallPaneOption } from './CallPane';
import { MutedNotification, MutedNotificationProps } from './MutedNotification';

/**
Expand All @@ -36,6 +56,8 @@ export interface CallArrangementProps {
onRenderGalleryContent: () => JSX.Element;
dataUiId: string;
mobileView: boolean;
/* @conditional-compile-remove(one-to-n-calling) */
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
}

/**
Expand All @@ -56,40 +78,133 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
const containerWidth = _useContainerWidth(containerRef);
const containerHeight = _useContainerHeight(containerRef);

/* @conditional-compile-remove(one-to-n-calling) */
const adapter = useAdapter();
/* @conditional-compile-remove(one-to-n-calling) */
const [activePane, setActivePane] = useState<CallPaneOption>('none');
/* @conditional-compile-remove(one-to-n-calling) */
const { callStatus } = useSelector(callStatusSelector);

/* @conditional-compile-remove(one-to-n-calling) */
const closePane = useCallback(() => {
setActivePane('none');
}, [setActivePane]);

/* @conditional-compile-remove(one-to-n-calling) */
const modalLayerHostId = useId('modalLayerhost');
/* @conditional-compile-remove(one-to-n-calling) */
const isMobileWithActivePane = props.mobileView && activePane !== 'none';

/* @conditional-compile-remove(one-to-n-calling) */
const togglePeople = useCallback(() => {
if (activePane === 'people' || !(callStatus === 'Connected')) {
setActivePane('none');
} else {
setActivePane('people');
}
}, [activePane, setActivePane, callStatus]);

/* @conditional-compile-remove(one-to-n-calling) */
const selectPeople = useCallback(() => {
if (callStatus === 'Connected') {
setActivePane('people');
}
}, [setActivePane, callStatus]);

/* @conditional-compile-remove(one-to-n-calling) */
const callCompositeContainerCSS = useMemo(() => {
return { display: isMobileWithActivePane ? 'none' : 'flex' };
}, [isMobileWithActivePane]);

// To be removed once feature is out of beta, replace with callCompositeContainerCSS
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const callCompositeContainerFlex = () => {
/* @conditional-compile-remove(one-to-n-calling) */
return callCompositeContainerCSS;
return { display: 'flex' };
};

/* @conditional-compile-remove(one-to-n-calling) */
const callPaneContent = (): JSX.Element => {
if (adapter && callStatus === 'Connected') {
return (
<CallPane
callAdapter={adapter}
onClose={closePane}
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
onPeopleButtonClicked={
showShowPeopleTabHeaderButton(props.callControlProps.options) ? selectPeople : undefined
}
modalLayerHostId={modalLayerHostId}
activePane={activePane}
mobileView={props.mobileView}
inviteLink={props.callControlProps.callInvitationURL}
/>
);
}
return <></>;
};

return (
<div ref={containerRef} className={mergeStyles(containerDivStyles)}>
<Stack verticalFill horizontalAlign="stretch" className={containerClassName} data-ui-id={props.dataUiId}>
<Stack.Item styles={notificationsContainerStyles}>
<Stack styles={bannerNotificationStyles}>
<_ComplianceBanner {...props.complianceBannerProps} />
</Stack>
{props.errorBarProps !== false && (
<Stack horizontal grow>
<Stack.Item styles={notificationsContainerStyles}>
<Stack styles={bannerNotificationStyles}>
<ErrorBar {...props.errorBarProps} />
<_ComplianceBanner {...props.complianceBannerProps} />
</Stack>
)}
{!!props.mutedNotificationProps && <MutedNotification {...props.mutedNotificationProps} />}
</Stack.Item>

<Stack.Item styles={callGalleryStyles} grow>
{props.onRenderGalleryContent && (
<Stack verticalFill styles={mediaGalleryContainerStyles}>
{props.onRenderGalleryContent()}
</Stack>
)}
</Stack.Item>

{props.callControlProps?.options !== false && (
<Stack.Item className={callControlsContainerStyles}>
<CallControls
{...props.callControlProps}
containerWidth={containerWidth}
containerHeight={containerHeight}
isMobile={props.mobileView}
/>
{props.errorBarProps !== false && (
<Stack styles={bannerNotificationStyles}>
<ErrorBar {...props.errorBarProps} />
</Stack>
)}
{!!props.mutedNotificationProps && <MutedNotification {...props.mutedNotificationProps} />}
</Stack.Item>
)}
<Stack.Item grow style={callCompositeContainerFlex()}>
<Stack.Item styles={callGalleryStyles} grow>
{props.onRenderGalleryContent && (
<Stack verticalFill styles={mediaGalleryContainerStyles}>
{props.onRenderGalleryContent()}
</Stack>
)}
</Stack.Item>
</Stack.Item>
{/* @conditional-compile-remove(one-to-n-calling) */ callPaneContent()}
</Stack>
{props.callControlProps?.options !== false &&
/* @conditional-compile-remove(one-to-n-calling) */ !isMobileWithActivePane && (
<Stack.Item className={callControlsContainerStyles}>
<CallControls
{...props.callControlProps}
containerWidth={containerWidth}
containerHeight={containerHeight}
isMobile={props.mobileView}
/* @conditional-compile-remove(one-to-n-calling) */
peopleButtonChecked={activePane === 'people'}
/* @conditional-compile-remove(one-to-n-calling) */
onPeopleButtonClicked={togglePeople}
/>
</Stack.Item>
)}
{
// This layer host is for ModalLocalAndRemotePIP in CallPane. This LayerHost cannot be inside the CallPane
// because when the CallPane is hidden, ie. style property display is 'none', it takes up no space. This causes problems when dragging
// the Modal because the draggable bounds thinks it has no space and will always return to its initial position after dragging.
/* @conditional-compile-remove(one-to-n-calling) */
props.mobileView && <LayerHost id={modalLayerHostId} className={mergeStyles(modalLayerHostStyle)} />
}
</Stack>
</div>
);
};

/* @conditional-compile-remove(one-to-n-calling) */
const showShowPeopleTabHeaderButton = (callControls?: boolean | CallControlOptions): boolean => {
if (callControls === undefined || callControls === true) {
return true;
}
if (callControls === false) {
return false;
}
return callControls.participantsButton !== false;
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ import { Microphone } from './buttons/Microphone';
import { Participants } from './buttons/Participants';
import { ScreenShare } from './buttons/ScreenShare';
import { ContainerRectProps } from '../../common/ContainerRectProps';
/* @conditional-compile-remove(one-to-n-calling) */
import { People } from './buttons/People';
/* @conditional-compile-remove(one-to-n-calling) */
import { useLocale } from '../../localization';

/**
* @private
*/
export type CallControlsProps = {
/* @conditional-compile-remove(one-to-n-calling) */
peopleButtonChecked?: boolean;
/* @conditional-compile-remove(one-to-n-calling) */
onPeopleButtonClicked?: () => void;
callInvitationURL?: string;
onFetchParticipantMenuItems?: ParticipantMenuItemsCallback;
options?: boolean | CallControlOptions;
Expand All @@ -36,6 +44,18 @@ export type CallControlsProps = {
*/
export const CallControls = (props: CallControlsProps & ContainerRectProps): JSX.Element => {
const options = useMemo(() => (typeof props.options === 'boolean' ? {} : props.options), [props.options]);
/* @conditional-compile-remove(one-to-n-calling) */
const localeStrings = useLocale();

/* @conditional-compile-remove(one-to-n-calling) */
const peopleButtonStrings = useMemo(
() => ({
label: localeStrings.strings.callWithChat.peopleButtonLabel,
tooltipOffContent: localeStrings.strings.callWithChat.peopleButtonTooltipOpen,
tooltipOnContent: localeStrings.strings.callWithChat.peopleButtonTooltipClose
}),
[localeStrings]
);

/* @conditional-compile-remove(control-bar-button-injection) */
const customButtons = useMemo(
Expand Down Expand Up @@ -65,15 +85,25 @@ export const CallControls = (props: CallControlsProps & ContainerRectProps): JSX
<ScreenShare option={options?.screenShareButton} displayType={options?.displayType} />
)}
{isEnabled(options?.participantsButton) && (
<Participants
option={options?.participantsButton}
callInvitationURL={props.callInvitationURL}
onFetchParticipantMenuItems={props.onFetchParticipantMenuItems}
displayType={options?.displayType}
increaseFlyoutItemSize={props.increaseFlyoutItemSize}
isMobile={props.isMobile}
/>
)}
<Participants
option={options?.participantsButton}
callInvitationURL={props.callInvitationURL}
onFetchParticipantMenuItems={props.onFetchParticipantMenuItems}
displayType={options?.displayType}
increaseFlyoutItemSize={props.increaseFlyoutItemSize}
isMobile={props.isMobile}
/>
) && (
/* @conditional-compile-remove(one-to-n-calling) */ /* @conditional-compile-remove(one-to-n-calling) */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should one of these tags be for (PSTN-calls)?

<People
checked={props.peopleButtonChecked}
showLabel={options?.displayType !== 'compact'}
onClick={props.onPeopleButtonClicked}
data-ui-id="call-with-chat-composite-people-button"
disabled={isDisabled(options?.participantsButton)}
strings={peopleButtonStrings}
/>
)}
{isEnabled(options?.devicesButton) && (
<Devices displayType={options?.displayType} increaseFlyoutItemSize={props.increaseFlyoutItemSize} />
)}
Expand All @@ -86,3 +116,10 @@ export const CallControls = (props: CallControlsProps & ContainerRectProps): JSX
};

const isEnabled = (option: unknown): boolean => option !== false;
/* @conditional-compile-remove(one-to-n-calling) */
const isDisabled = (option?: boolean | { disabled: boolean }): boolean => {
if (option === undefined || option === true || option === false) {
return false;
}
return option.disabled;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import React, { useMemo } from 'react';
import { ControlBarButton, ControlBarButtonProps, ControlBarButtonStyles, useTheme } from '@internal/react-components';
import { concatStyleSets } from '@fluentui/react';
import { CallCompositeIcon } from '../../../common/icons';
import { controlButtonBaseStyle } from '../../styles/Buttons.styles';

const icon = (): JSX.Element => <CallCompositeIcon iconName={'ControlButtonParticipants'} />;

/**
* @private
*/
/** @beta */
export const People = (props: ControlBarButtonProps): JSX.Element => {
const { strings, onRenderOnIcon, onRenderOffIcon, onClick } = props;
const theme = useTheme();
const styles: ControlBarButtonStyles = useMemo(
() =>
concatStyleSets(
{
rootChecked: {
background: theme.palette.neutralLight
}
},
props.styles ?? {},
controlButtonBaseStyle
),
[props.styles, theme.palette.neutralLight]
);

return (
<ControlBarButton
{...props}
data-ui-id="call-composite-participants-button"
strings={strings}
labelKey={'peopleButtonLabelKey'}
onRenderOnIcon={onRenderOnIcon ?? icon}
onRenderOffIcon={onRenderOffIcon ?? icon}
onClick={onClick}
styles={styles}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
options: callControlOptions,
increaseFlyoutItemSize: mobileView
}}
/* @conditional-compile-remove(one-to-n-calling) */
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
mobileView={mobileView}
onRenderGalleryContent={() =>
callStatus === 'Connected' ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
callCompositeContainerStyles,
compositeOuterContainerStyles,
controlBarContainerStyles,
drawerContainerStyles,
modalLayerHostStyle
drawerContainerStyles
} from './styles/CallWithChatCompositeStyles';
import { CallWithChatAdapter } from './adapter/CallWithChatAdapter';
import { CallWithChatBackedCallAdapter } from './adapter/CallWithChatBackedCallAdapter';
Expand All @@ -33,6 +32,7 @@ import { FileSharingOptions } from '../ChatComposite';
import { containerDivStyles } from '../common/ContainerRectProps';
/* @conditional-compile-remove(control-bar-button-injection) */
import { CustomCallWithChatControlButtonCallback } from './CustomButton';
import { modalLayerHostStyle } from '../common/styles/ModalLocalAndRemotePIP.styles';

/**
* Props required for the {@link CallWithChatComposite}
Expand Down
Loading