Add list of participants to the lobby, and add basic blocking for max participants
This commit is contained in:
parent
f8b4862ed5
commit
daef1feae8
11 changed files with 112 additions and 27 deletions
|
@ -1166,6 +1166,10 @@
|
||||||
"message": "Join Call",
|
"message": "Join Call",
|
||||||
"description": "Button label in the call lobby for joining a 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": {
|
"calling__button--video-disabled": {
|
||||||
"message": "Camera disabled",
|
"message": "Camera disabled",
|
||||||
"description": "Button tooltip label when the camera is disabled"
|
"description": "Button tooltip label when the camera is disabled"
|
||||||
|
|
|
@ -6454,6 +6454,10 @@ button.module-image__border-overlay:focus {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__video {
|
&__video {
|
||||||
|
|
|
@ -101,6 +101,8 @@ story.add('Ongoing Direct Call', () => (
|
||||||
},
|
},
|
||||||
activeCallState: getCallState(),
|
activeCallState: getCallState(),
|
||||||
conversation: getConversation(),
|
conversation: getConversation(),
|
||||||
|
isCallFull: false,
|
||||||
|
groupCallPeekedParticipants: [],
|
||||||
groupCallParticipants: [],
|
groupCallParticipants: [],
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
@ -125,6 +127,8 @@ story.add('Ongoing Group Call', () => (
|
||||||
},
|
},
|
||||||
activeCallState: getCallState(),
|
activeCallState: getCallState(),
|
||||||
conversation: getConversation(),
|
conversation: getConversation(),
|
||||||
|
isCallFull: false,
|
||||||
|
groupCallPeekedParticipants: [],
|
||||||
groupCallParticipants: [],
|
groupCallParticipants: [],
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
@ -151,6 +155,8 @@ story.add('Call Request Needed', () => (
|
||||||
}),
|
}),
|
||||||
activeCallState: getCallState(),
|
activeCallState: getCallState(),
|
||||||
conversation: getConversation(),
|
conversation: getConversation(),
|
||||||
|
isCallFull: false,
|
||||||
|
groupCallPeekedParticipants: [],
|
||||||
groupCallParticipants: [],
|
groupCallParticipants: [],
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -96,7 +96,9 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
call,
|
call,
|
||||||
activeCallState,
|
activeCallState,
|
||||||
conversation,
|
conversation,
|
||||||
|
groupCallPeekedParticipants,
|
||||||
groupCallParticipants,
|
groupCallParticipants,
|
||||||
|
isCallFull,
|
||||||
} = activeCall;
|
} = activeCall;
|
||||||
const {
|
const {
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
|
@ -157,7 +159,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showCallLobby) {
|
if (showCallLobby) {
|
||||||
const participantNames = groupCallParticipants.map(participant =>
|
const participantNames = groupCallPeekedParticipants.map(participant =>
|
||||||
participant.isSelf
|
participant.isSelf
|
||||||
? i18n('you')
|
? i18n('you')
|
||||||
: participant.firstName || participant.title
|
: participant.firstName || participant.title
|
||||||
|
@ -171,6 +173,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
hasLocalVideo={hasLocalVideo}
|
hasLocalVideo={hasLocalVideo}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGroupCall={call.callMode === CallMode.Group}
|
isGroupCall={call.callMode === CallMode.Group}
|
||||||
|
isCallFull={isCallFull}
|
||||||
me={me}
|
me={me}
|
||||||
onCallCanceled={cancelActiveCall}
|
onCallCanceled={cancelActiveCall}
|
||||||
onJoinCall={joinActiveCall}
|
onJoinCall={joinActiveCall}
|
||||||
|
|
|
@ -93,6 +93,8 @@ const createProps = (
|
||||||
type: 'direct',
|
type: 'direct',
|
||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
},
|
},
|
||||||
|
isCallFull: false,
|
||||||
|
groupCallPeekedParticipants: [],
|
||||||
groupCallParticipants: overrideProps.groupCallParticipants || [],
|
groupCallParticipants: overrideProps.groupCallParticipants || [],
|
||||||
},
|
},
|
||||||
// We allow `any` here because this is fake and actually comes from RingRTC, which we
|
// We allow `any` here because this is fake and actually comes from RingRTC, which we
|
||||||
|
|
|
@ -24,6 +24,7 @@ export type PropsType = {
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGroupCall: boolean;
|
isGroupCall: boolean;
|
||||||
|
isCallFull?: boolean;
|
||||||
me: {
|
me: {
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color?: ColorType;
|
color?: ColorType;
|
||||||
|
@ -46,6 +47,7 @@ export const CallingLobby = ({
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
i18n,
|
i18n,
|
||||||
isGroupCall = false,
|
isGroupCall = false,
|
||||||
|
isCallFull = false,
|
||||||
me,
|
me,
|
||||||
onCallCanceled,
|
onCallCanceled,
|
||||||
onJoinCall,
|
onJoinCall,
|
||||||
|
@ -112,6 +114,45 @@ export const CallingLobby = ({
|
||||||
? CallingButtonType.AUDIO_ON
|
? CallingButtonType.AUDIO_ON
|
||||||
: CallingButtonType.AUDIO_OFF;
|
: CallingButtonType.AUDIO_OFF;
|
||||||
|
|
||||||
|
let joinButton: JSX.Element;
|
||||||
|
if (isCallFull) {
|
||||||
|
joinButton = (
|
||||||
|
<button
|
||||||
|
className="module-button__green module-calling-lobby__button"
|
||||||
|
disabled
|
||||||
|
tabIndex={0}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{i18n('calling__call-is-full')}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
} else if (isCallConnecting) {
|
||||||
|
joinButton = (
|
||||||
|
<button
|
||||||
|
className="module-button__green module-calling-lobby__button"
|
||||||
|
disabled
|
||||||
|
tabIndex={0}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Spinner svgSize="small" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
joinButton = (
|
||||||
|
<button
|
||||||
|
className="module-button__green module-calling-lobby__button"
|
||||||
|
onClick={() => {
|
||||||
|
setIsCallConnecting(true);
|
||||||
|
onJoinCall();
|
||||||
|
}}
|
||||||
|
tabIndex={0}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{isGroupCall ? i18n('calling__join') : i18n('calling__start')}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-calling__container">
|
<div className="module-calling__container">
|
||||||
<CallingHeader
|
<CallingHeader
|
||||||
|
@ -191,29 +232,7 @@ export const CallingLobby = ({
|
||||||
>
|
>
|
||||||
{i18n('cancel')}
|
{i18n('cancel')}
|
||||||
</button>
|
</button>
|
||||||
{isCallConnecting && (
|
{joinButton}
|
||||||
<button
|
|
||||||
className="module-button__green module-calling-lobby__button"
|
|
||||||
disabled
|
|
||||||
tabIndex={0}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<Spinner svgSize="small" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{!isCallConnecting && (
|
|
||||||
<button
|
|
||||||
className="module-button__green module-calling-lobby__button"
|
|
||||||
onClick={() => {
|
|
||||||
setIsCallConnecting(true);
|
|
||||||
onJoinCall();
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{isGroupCall ? i18n('calling__join') : i18n('calling__start')}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,6 +59,8 @@ const createProps = (
|
||||||
},
|
},
|
||||||
call: activeCall.call || defaultCall,
|
call: activeCall.call || defaultCall,
|
||||||
conversation: activeCall.conversation || conversation,
|
conversation: activeCall.conversation || conversation,
|
||||||
|
isCallFull: false,
|
||||||
|
groupCallPeekedParticipants: [],
|
||||||
groupCallParticipants: [],
|
groupCallParticipants: [],
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
ChangeIODevicePayloadType,
|
ChangeIODevicePayloadType,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
|
GroupCallPeekedParticipantType,
|
||||||
GroupCallRemoteParticipantType,
|
GroupCallRemoteParticipantType,
|
||||||
MediaDeviceSettings,
|
MediaDeviceSettings,
|
||||||
} from '../../types/Calling';
|
} from '../../types/Calling';
|
||||||
|
@ -71,6 +72,8 @@ export interface ActiveCallType {
|
||||||
activeCallState: ActiveCallStateType;
|
activeCallState: ActiveCallStateType;
|
||||||
call: DirectCallStateType | GroupCallStateType;
|
call: DirectCallStateType | GroupCallStateType;
|
||||||
conversation: ConversationType;
|
conversation: ConversationType;
|
||||||
|
isCallFull: boolean;
|
||||||
|
groupCallPeekedParticipants: Array<GroupCallPeekedParticipantType>;
|
||||||
groupCallParticipants: Array<GroupCallRemoteParticipantType>;
|
groupCallParticipants: Array<GroupCallRemoteParticipantType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,11 @@ import { calling as callingService } from '../../services/calling';
|
||||||
import { getMe, getConversationSelector } from '../selectors/conversations';
|
import { getMe, getConversationSelector } from '../selectors/conversations';
|
||||||
import { getActiveCall, GroupCallParticipantInfoType } from '../ducks/calling';
|
import { getActiveCall, GroupCallParticipantInfoType } from '../ducks/calling';
|
||||||
import { getIncomingCall } from '../selectors/calling';
|
import { getIncomingCall } from '../selectors/calling';
|
||||||
import { CallMode, GroupCallRemoteParticipantType } from '../../types/Calling';
|
import {
|
||||||
|
CallMode,
|
||||||
|
GroupCallPeekedParticipantType,
|
||||||
|
GroupCallRemoteParticipantType,
|
||||||
|
} from '../../types/Calling';
|
||||||
import { StateType } from '../reducer';
|
import { StateType } from '../reducer';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
@ -47,8 +51,34 @@ const mapStateToActiveCallProp = (state: StateType) => {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: The way we deal with remote participants isn't ideal. See DESKTOP-949.
|
||||||
|
let isCallFull = false;
|
||||||
|
const groupCallPeekedParticipants: Array<GroupCallPeekedParticipantType> = [];
|
||||||
const groupCallParticipants: Array<GroupCallRemoteParticipantType> = [];
|
const groupCallParticipants: Array<GroupCallRemoteParticipantType> = [];
|
||||||
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(
|
call.remoteParticipants.forEach(
|
||||||
(remoteParticipant: GroupCallParticipantInfoType) => {
|
(remoteParticipant: GroupCallParticipantInfoType) => {
|
||||||
const remoteConversation = conversationSelector(
|
const remoteConversation = conversationSelector(
|
||||||
|
@ -83,6 +113,8 @@ const mapStateToActiveCallProp = (state: StateType) => {
|
||||||
activeCallState,
|
activeCallState,
|
||||||
call,
|
call,
|
||||||
conversation,
|
conversation,
|
||||||
|
isCallFull,
|
||||||
|
groupCallPeekedParticipants,
|
||||||
groupCallParticipants,
|
groupCallParticipants,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,6 +58,16 @@ export enum GroupCallJoinState {
|
||||||
Joined = 2,
|
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 {
|
export interface GroupCallRemoteParticipantType {
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color?: ColorType;
|
color?: ColorType;
|
||||||
|
|
|
@ -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": 60,
|
"lineNumber": 62,
|
||||||
"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."
|
||||||
|
|
Loading…
Reference in a new issue