From daef1feae8e9aeebbdff9bef8f95d6f2b6c8d84d Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Fri, 20 Nov 2020 14:14:07 -0600 Subject: [PATCH] Add list of participants to the lobby, and add basic blocking for max participants --- _locales/en/messages.json | 4 ++ stylesheets/_modules.scss | 4 ++ ts/components/CallManager.stories.tsx | 6 +++ ts/components/CallManager.tsx | 5 ++- ts/components/CallScreen.stories.tsx | 2 + ts/components/CallingLobby.tsx | 65 +++++++++++++++++---------- ts/components/CallingPip.stories.tsx | 2 + ts/state/ducks/calling.ts | 3 ++ ts/state/smart/CallManager.tsx | 36 ++++++++++++++- ts/types/Calling.ts | 10 +++++ ts/util/lint/exceptions.json | 2 +- 11 files changed, 112 insertions(+), 27 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b78e1a8f2a4..483fd4979fe 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1166,6 +1166,10 @@ "message": "Join Call", "description": "Button label in the call lobby for joining a call" }, + "calling__call-is-full": { + "message": "Call is full", + "description": "Button label in the call lobby when you can't join because the call is full" + }, "calling__button--video-disabled": { "message": "Camera disabled", "description": "Button tooltip label when the camera is disabled" diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index b7be6a3f483..8ed9d5df72d 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -6454,6 +6454,10 @@ button.module-image__border-overlay:focus { margin-left: 8px; margin-right: 8px; width: 160px; + + &[disabled] { + opacity: 0.5; + } } &__video { diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 1b9ba9437af..b12139fd0c1 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -101,6 +101,8 @@ story.add('Ongoing Direct Call', () => ( }, activeCallState: getCallState(), conversation: getConversation(), + isCallFull: false, + groupCallPeekedParticipants: [], groupCallParticipants: [], }, })} @@ -125,6 +127,8 @@ story.add('Ongoing Group Call', () => ( }, activeCallState: getCallState(), conversation: getConversation(), + isCallFull: false, + groupCallPeekedParticipants: [], groupCallParticipants: [], }, })} @@ -151,6 +155,8 @@ story.add('Call Request Needed', () => ( }), activeCallState: getCallState(), conversation: getConversation(), + isCallFull: false, + groupCallPeekedParticipants: [], groupCallParticipants: [], }, })} diff --git a/ts/components/CallManager.tsx b/ts/components/CallManager.tsx index 81f18a05741..6f319d87d5d 100644 --- a/ts/components/CallManager.tsx +++ b/ts/components/CallManager.tsx @@ -96,7 +96,9 @@ const ActiveCallManager: React.FC = ({ call, activeCallState, conversation, + groupCallPeekedParticipants, groupCallParticipants, + isCallFull, } = activeCall; const { hasLocalAudio, @@ -157,7 +159,7 @@ const ActiveCallManager: React.FC = ({ } if (showCallLobby) { - const participantNames = groupCallParticipants.map(participant => + const participantNames = groupCallPeekedParticipants.map(participant => participant.isSelf ? i18n('you') : participant.firstName || participant.title @@ -171,6 +173,7 @@ const ActiveCallManager: React.FC = ({ hasLocalVideo={hasLocalVideo} i18n={i18n} isGroupCall={call.callMode === CallMode.Group} + isCallFull={isCallFull} me={me} onCallCanceled={cancelActiveCall} onJoinCall={joinActiveCall} diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index 73550ac96f1..ac65bac3438 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -93,6 +93,8 @@ const createProps = ( type: 'direct', lastUpdated: Date.now(), }, + isCallFull: false, + groupCallPeekedParticipants: [], groupCallParticipants: overrideProps.groupCallParticipants || [], }, // We allow `any` here because this is fake and actually comes from RingRTC, which we diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx index 23cf1f4397d..60ab4c0f235 100644 --- a/ts/components/CallingLobby.tsx +++ b/ts/components/CallingLobby.tsx @@ -24,6 +24,7 @@ export type PropsType = { hasLocalVideo: boolean; i18n: LocalizerType; isGroupCall: boolean; + isCallFull?: boolean; me: { avatarPath?: string; color?: ColorType; @@ -46,6 +47,7 @@ export const CallingLobby = ({ hasLocalVideo, i18n, isGroupCall = false, + isCallFull = false, me, onCallCanceled, onJoinCall, @@ -112,6 +114,45 @@ export const CallingLobby = ({ ? CallingButtonType.AUDIO_ON : CallingButtonType.AUDIO_OFF; + let joinButton: JSX.Element; + if (isCallFull) { + joinButton = ( + + ); + } else if (isCallConnecting) { + joinButton = ( + + ); + } else { + joinButton = ( + + ); + } + return (
{i18n('cancel')} - {isCallConnecting && ( - - )} - {!isCallConnecting && ( - - )} + {joinButton}
); diff --git a/ts/components/CallingPip.stories.tsx b/ts/components/CallingPip.stories.tsx index 6dcae8c6abe..dd55b85c243 100644 --- a/ts/components/CallingPip.stories.tsx +++ b/ts/components/CallingPip.stories.tsx @@ -59,6 +59,8 @@ const createProps = ( }, call: activeCall.call || defaultCall, conversation: activeCall.conversation || conversation, + isCallFull: false, + groupCallPeekedParticipants: [], groupCallParticipants: [], }, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index f7983b4c9fa..f8db1a9e309 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -17,6 +17,7 @@ import { ChangeIODevicePayloadType, GroupCallConnectionState, GroupCallJoinState, + GroupCallPeekedParticipantType, GroupCallRemoteParticipantType, MediaDeviceSettings, } from '../../types/Calling'; @@ -71,6 +72,8 @@ export interface ActiveCallType { activeCallState: ActiveCallStateType; call: DirectCallStateType | GroupCallStateType; conversation: ConversationType; + isCallFull: boolean; + groupCallPeekedParticipants: Array; groupCallParticipants: Array; } diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx index 5f911082aaa..6cc96f569fc 100644 --- a/ts/state/smart/CallManager.tsx +++ b/ts/state/smart/CallManager.tsx @@ -9,7 +9,11 @@ import { calling as callingService } from '../../services/calling'; import { getMe, getConversationSelector } from '../selectors/conversations'; import { getActiveCall, GroupCallParticipantInfoType } from '../ducks/calling'; import { getIncomingCall } from '../selectors/calling'; -import { CallMode, GroupCallRemoteParticipantType } from '../../types/Calling'; +import { + CallMode, + GroupCallPeekedParticipantType, + GroupCallRemoteParticipantType, +} from '../../types/Calling'; import { StateType } from '../reducer'; import { getIntl } from '../selectors/user'; @@ -47,8 +51,34 @@ const mapStateToActiveCallProp = (state: StateType) => { return undefined; } + // TODO: The way we deal with remote participants isn't ideal. See DESKTOP-949. + let isCallFull = false; + const groupCallPeekedParticipants: Array = []; const groupCallParticipants: Array = []; - if (call && call.callMode === CallMode.Group) { + if (call.callMode === CallMode.Group) { + isCallFull = call.peekInfo.deviceCount >= call.peekInfo.maxDevices; + + call.peekInfo.conversationIds.forEach((conversationId: string) => { + const peekedConversation = conversationSelector(conversationId); + + if (!peekedConversation) { + window.log.error( + 'Peeked participant has no corresponding conversation' + ); + return; + } + + groupCallPeekedParticipants.push({ + avatarPath: peekedConversation.avatarPath, + color: peekedConversation.color, + firstName: peekedConversation.firstName, + isSelf: conversationId === state.user.ourConversationId, + name: peekedConversation.name, + profileName: peekedConversation.profileName, + title: peekedConversation.title, + }); + }); + call.remoteParticipants.forEach( (remoteParticipant: GroupCallParticipantInfoType) => { const remoteConversation = conversationSelector( @@ -83,6 +113,8 @@ const mapStateToActiveCallProp = (state: StateType) => { activeCallState, call, conversation, + isCallFull, + groupCallPeekedParticipants, groupCallParticipants, }; }; diff --git a/ts/types/Calling.ts b/ts/types/Calling.ts index 1e7f86f2df2..5aad000cc59 100644 --- a/ts/types/Calling.ts +++ b/ts/types/Calling.ts @@ -58,6 +58,16 @@ export enum GroupCallJoinState { Joined = 2, } +// TODO: The way we deal with remote participants isn't ideal. See DESKTOP-949. +export interface GroupCallPeekedParticipantType { + avatarPath?: string; + color?: ColorType; + firstName?: string; + isSelf: boolean; + name?: string; + profileName?: string; + title: string; +} export interface GroupCallRemoteParticipantType { avatarPath?: string; color?: ColorType; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 2f4979e3f04..0820863f072 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14400,7 +14400,7 @@ "rule": "React-useRef", "path": "ts/components/CallingLobby.tsx", "line": " const localVideoRef = React.useRef(null);", - "lineNumber": 60, + "lineNumber": 62, "reasonCategory": "usageTrusted", "updated": "2020-10-26T19:12:24.410Z", "reasonDetail": "Used to get the local video element for rendering."