Participant list improvements

This commit is contained in:
Josh Perez 2020-11-20 14:39:50 -05:00 committed by Josh Perez
parent 7ca063a274
commit f8b4862ed5
13 changed files with 119 additions and 14 deletions

View file

@ -6110,8 +6110,30 @@ button.module-image__border-overlay:focus {
.module-calling-button { .module-calling-button {
&__participants { &__participants {
@include color-svg('../images/icons/v2/group-solid-24.svg', $color-white); @include color-svg('../images/icons/v2/group-solid-24.svg', $color-white);
display: inline-block;
height: 22px; height: 22px;
width: 22px; width: 22px;
&--container {
@include button-reset;
border: none;
color: $color-white;
}
&--shown {
background-color: $color-gray-75;
border-radius: 16px;
padding: 6px 8px;
padding-bottom: 2px;
margin-top: -6px;
margin-right: -8px;
}
&--count {
@include font-body-2-bold;
margin-left: 5px;
vertical-align: top;
}
} }
&__settings { &__settings {
@ -6592,6 +6614,10 @@ button.module-image__border-overlay:focus {
@include font-body-2-bold; @include font-body-2-bold;
} }
&__contact-icon {
background-color: $color-gray-25;
}
&__list { &__list {
height: 100%; height: 100%;
margin-bottom: 0; margin-bottom: 0;

View file

@ -178,6 +178,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
setLocalPreview={setLocalPreview} setLocalPreview={setLocalPreview}
setLocalAudio={setLocalAudio} setLocalAudio={setLocalAudio}
setLocalVideo={setLocalVideo} setLocalVideo={setLocalVideo}
showParticipantsList={showParticipantsList}
toggleParticipants={toggleParticipants} toggleParticipants={toggleParticipants}
toggleSettings={toggleSettings} toggleSettings={toggleSettings}
/> />

View file

@ -195,7 +195,11 @@ export const CallScreen: React.FC<PropsType> = ({
}); });
const remoteParticipants = const remoteParticipants =
call.callMode === CallMode.Group ? call.remoteParticipants.length : 0; call.callMode === CallMode.Group
? activeCall.groupCallParticipants.length
: 0;
const { showParticipantsList } = activeCall.activeCallState;
return ( return (
<div <div
@ -232,6 +236,7 @@ export const CallScreen: React.FC<PropsType> = ({
i18n={i18n} i18n={i18n}
isGroupCall={call.callMode === CallMode.Group} isGroupCall={call.callMode === CallMode.Group}
remoteParticipants={remoteParticipants} remoteParticipants={remoteParticipants}
showParticipantsList={showParticipantsList}
toggleParticipants={toggleParticipants} toggleParticipants={toggleParticipants}
togglePip={togglePip} togglePip={togglePip}
toggleSettings={toggleSettings} toggleSettings={toggleSettings}

View file

@ -21,6 +21,10 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
'remoteParticipants', 'remoteParticipants',
overrideProps.remoteParticipants || 0 overrideProps.remoteParticipants || 0
), ),
showParticipantsList: boolean(
'showParticipantsList',
Boolean(overrideProps.showParticipantsList)
),
toggleParticipants: () => action('toggle-participants'), toggleParticipants: () => action('toggle-participants'),
togglePip: () => action('toggle-pip'), togglePip: () => action('toggle-pip'),
toggleSettings: () => action('toggle-settings'), toggleSettings: () => action('toggle-settings'),
@ -44,6 +48,17 @@ story.add('With Participants', () => (
/> />
)); ));
story.add('With Participants (shown)', () => (
<CallingHeader
{...createProps({
canPip: true,
isGroupCall: true,
remoteParticipants: 10,
showParticipantsList: true,
})}
/>
));
story.add('Long Title', () => ( story.add('Long Title', () => (
<CallingHeader <CallingHeader
{...createProps({ {...createProps({

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { Tooltip, TooltipTheme } from './Tooltip'; import { Tooltip, TooltipTheme } from './Tooltip';
@ -11,6 +12,7 @@ export type PropsType = {
i18n: LocalizerType; i18n: LocalizerType;
isGroupCall?: boolean; isGroupCall?: boolean;
remoteParticipants?: number; remoteParticipants?: number;
showParticipantsList: boolean;
toggleParticipants?: () => void; toggleParticipants?: () => void;
togglePip?: () => void; togglePip?: () => void;
toggleSettings: () => void; toggleSettings: () => void;
@ -22,6 +24,7 @@ export const CallingHeader = ({
i18n, i18n,
isGroupCall = false, isGroupCall = false,
remoteParticipants, remoteParticipants,
showParticipantsList,
toggleParticipants, toggleParticipants,
togglePip, togglePip,
toggleSettings, toggleSettings,
@ -43,10 +46,20 @@ export const CallingHeader = ({
aria-label={i18n('calling__participants', [ aria-label={i18n('calling__participants', [
String(remoteParticipants), String(remoteParticipants),
])} ])}
className="module-calling-button__participants" className={classNames(
'module-calling-button__participants--container',
{
'module-calling-button__participants--shown': showParticipantsList,
}
)}
onClick={toggleParticipants} onClick={toggleParticipants}
type="button" type="button"
/> >
<i className="module-calling-button__participants" />
<span className="module-calling-button__participants--count">
{remoteParticipants}
</span>
</button>
</Tooltip> </Tooltip>
</div> </div>
) : null} ) : null}

View file

@ -39,6 +39,10 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
setLocalAudio: action('set-local-audio'), setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'), setLocalPreview: action('set-local-preview'),
setLocalVideo: action('set-local-video'), setLocalVideo: action('set-local-video'),
showParticipantsList: boolean(
'showParticipantsList',
Boolean(overrideProps.showParticipantsList)
),
toggleParticipants: action('toggle-participants'), toggleParticipants: action('toggle-participants'),
toggleSettings: action('toggle-settings'), toggleSettings: action('toggle-settings'),
}); });
@ -115,3 +119,12 @@ story.add('Group Call - 4', () => {
}); });
return <CallingLobby {...props} />; return <CallingLobby {...props} />;
}); });
story.add('Group Call - 4 (participants list)', () => {
const props = createProps({
isGroupCall: true,
participantNames: ['Sam', 'Cayce', 'April', 'Logan', 'Carl'],
showParticipantsList: true,
});
return <CallingLobby {...props} />;
});

View file

@ -34,6 +34,7 @@ export type PropsType = {
setLocalAudio: (_: SetLocalAudioType) => void; setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void; setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void; setLocalPreview: (_: SetLocalPreviewType) => void;
showParticipantsList: boolean;
toggleParticipants: () => void; toggleParticipants: () => void;
toggleSettings: () => void; toggleSettings: () => void;
}; };
@ -52,6 +53,7 @@ export const CallingLobby = ({
setLocalAudio, setLocalAudio,
setLocalPreview, setLocalPreview,
setLocalVideo, setLocalVideo,
showParticipantsList,
toggleParticipants, toggleParticipants,
toggleSettings, toggleSettings,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
@ -117,6 +119,7 @@ export const CallingLobby = ({
i18n={i18n} i18n={i18n}
isGroupCall={isGroupCall} isGroupCall={isGroupCall}
remoteParticipants={participantNames.length} remoteParticipants={participantNames.length}
showParticipantsList={showParticipantsList}
toggleParticipants={toggleParticipants} toggleParticipants={toggleParticipants}
toggleSettings={toggleSettings} toggleSettings={toggleSettings}
/> />

View file

@ -24,6 +24,7 @@ function createParticipant(
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio), hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo), hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isSelf: Boolean(participantProps.isSelf), isSelf: Boolean(participantProps.isSelf),
name: participantProps.name,
profileName: participantProps.title, profileName: participantProps.title,
title: String(participantProps.title), title: String(participantProps.title),
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -64,6 +65,7 @@ story.add('Many Participants', () => {
createParticipant({ createParticipant({
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
name: 'Rage Trunks',
title: 'Rage Trunks', title: 'Rage Trunks',
}), }),
createParticipant({ createParticipant({
@ -73,6 +75,7 @@ story.add('Many Participants', () => {
createParticipant({ createParticipant({
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
name: 'Goku Black',
title: 'Goku Black', title: 'Goku Black',
}), }),
createParticipant({ createParticipant({

View file

@ -7,6 +7,7 @@ import React from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Avatar } from './Avatar'; import { Avatar } from './Avatar';
import { ContactName } from './conversation/ContactName'; import { ContactName } from './conversation/ContactName';
import { InContactsIcon } from './InContactsIcon';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { GroupCallRemoteParticipantType } from '../types/Calling'; import { GroupCallRemoteParticipantType } from '../types/Calling';
@ -31,14 +32,24 @@ export const CallingParticipantsList = React.memo(
}; };
}, []); }, []);
const handleCancel = React.useCallback(
(e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
},
[onClose]
);
if (!root) { if (!root) {
return null; return null;
} }
return createPortal( return createPortal(
<div <div
role="presentation"
className="module-calling-participants-list__overlay" className="module-calling-participants-list__overlay"
onClick={handleCancel}
role="presentation"
> >
<div className="module-calling-participants-list"> <div className="module-calling-participants-list">
<div className="module-calling-participants-list__header"> <div className="module-calling-participants-list__header">
@ -80,11 +91,22 @@ export const CallingParticipantsList = React.memo(
{i18n('you')} {i18n('you')}
</span> </span>
) : ( ) : (
<ContactName <>
i18n={i18n} <ContactName
module="module-calling-participants-list__name" i18n={i18n}
title={participant.title} module="module-calling-participants-list__name"
/> title={participant.title}
/>
{participant.name ? (
<span>
{' '}
<InContactsIcon
className="module-calling-participants-list__contact-icon"
i18n={i18n}
/>
</span>
) : null}
</>
)} )}
</div> </div>
<div> <div>

View file

@ -2,26 +2,28 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
import classNames from 'classnames';
import { Tooltip } from './Tooltip'; import { Tooltip } from './Tooltip';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
type PropsType = { type PropsType = {
className?: string;
i18n: LocalizerType; i18n: LocalizerType;
}; };
export const InContactsIcon = (props: PropsType): JSX.Element => { export const InContactsIcon = (props: PropsType): JSX.Element => {
const { i18n } = props; const { className, i18n } = props;
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
return ( return (
<span className="module-in-contacts-icon__tooltip"> <span className="module-in-contacts-icon__tooltip">
<Tooltip content={i18n('contactInAddressBook')}> <Tooltip content={i18n('contactInAddressBook')}>
<span <span
tabIndex={0}
role="img"
aria-label={i18n('contactInAddressBook')} aria-label={i18n('contactInAddressBook')}
className="module-in-contacts-icon__icon" className={classNames('module-in-contacts-icon__icon', className)}
role="img"
tabIndex={0}
/> />
</Tooltip> </Tooltip>
</span> </span>

View file

@ -70,6 +70,7 @@ const mapStateToActiveCallProp = (state: StateType) => {
hasRemoteAudio: remoteParticipant.hasRemoteAudio, hasRemoteAudio: remoteParticipant.hasRemoteAudio,
hasRemoteVideo: remoteParticipant.hasRemoteVideo, hasRemoteVideo: remoteParticipant.hasRemoteVideo,
isSelf: remoteParticipant.isSelf, isSelf: remoteParticipant.isSelf,
name: remoteConversation.name,
profileName: remoteConversation.profileName, profileName: remoteConversation.profileName,
title: remoteConversation.title, title: remoteConversation.title,
videoAspectRatio: remoteParticipant.videoAspectRatio, videoAspectRatio: remoteParticipant.videoAspectRatio,

View file

@ -66,6 +66,7 @@ export interface GroupCallRemoteParticipantType {
hasRemoteAudio: boolean; hasRemoteAudio: boolean;
hasRemoteVideo: boolean; hasRemoteVideo: boolean;
isSelf: boolean; isSelf: boolean;
name?: string;
profileName?: string; profileName?: string;
title: string; title: string;
videoAspectRatio: number; videoAspectRatio: number;

View file

@ -14400,7 +14400,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/CallingLobby.tsx", "path": "ts/components/CallingLobby.tsx",
"line": " const localVideoRef = React.useRef(null);", "line": " const localVideoRef = React.useRef(null);",
"lineNumber": 58, "lineNumber": 60,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-10-26T19:12:24.410Z", "updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Used to get the local video element for rendering." "reasonDetail": "Used to get the local video element for rendering."