New call UI and controls
This commit is contained in:
parent
33c5c683c7
commit
8bb355f971
22 changed files with 741 additions and 360 deletions
|
@ -1646,7 +1646,7 @@
|
||||||
},
|
},
|
||||||
"icu:calling__start": {
|
"icu:calling__start": {
|
||||||
"messageformat": "Start Call",
|
"messageformat": "Start Call",
|
||||||
"description": "Button label in the call lobby for starting a call"
|
"description": "(deleted 2023/10/13) Button label in the call lobby for starting a call"
|
||||||
},
|
},
|
||||||
"icu:calling__join": {
|
"icu:calling__join": {
|
||||||
"messageformat": "Join Call",
|
"messageformat": "Join Call",
|
||||||
|
@ -1668,9 +1668,21 @@
|
||||||
"messageformat": "Call is full",
|
"messageformat": "Call is full",
|
||||||
"description": "Text in the call lobby when you can't join because the call is full"
|
"description": "Text in the call lobby when you can't join because the call is full"
|
||||||
},
|
},
|
||||||
|
"icu:CallingLobbyJoinButton--join": {
|
||||||
|
"messageformat": "Join",
|
||||||
|
"description": "Button label in the call lobby for joining a call"
|
||||||
|
},
|
||||||
|
"icu:CallingLobbyJoinButton--start": {
|
||||||
|
"messageformat": "Start",
|
||||||
|
"description": "Button label in the call lobby for starting a call"
|
||||||
|
},
|
||||||
|
"icu:CallingLobbyJoinButton--call-full": {
|
||||||
|
"messageformat": "Call full",
|
||||||
|
"description": "Button in the call lobby when you can't join because the call is full"
|
||||||
|
},
|
||||||
"icu:calling__button--video__label": {
|
"icu:calling__button--video__label": {
|
||||||
"messageformat": "Camera",
|
"messageformat": "Camera",
|
||||||
"description": "Label under the video button"
|
"description": "(deleted 2023/10/13) Label under the video button"
|
||||||
},
|
},
|
||||||
"icu:calling__button--video-disabled": {
|
"icu:calling__button--video-disabled": {
|
||||||
"messageformat": "Camera disabled",
|
"messageformat": "Camera disabled",
|
||||||
|
@ -1686,7 +1698,7 @@
|
||||||
},
|
},
|
||||||
"icu:calling__button--audio__label": {
|
"icu:calling__button--audio__label": {
|
||||||
"messageformat": "Mute",
|
"messageformat": "Mute",
|
||||||
"description": "Label under the audio button"
|
"description": "(deleted 2023/10/13) Label under the audio button"
|
||||||
},
|
},
|
||||||
"icu:calling__button--audio-disabled": {
|
"icu:calling__button--audio-disabled": {
|
||||||
"messageformat": "Microphone disabled",
|
"messageformat": "Microphone disabled",
|
||||||
|
@ -1702,7 +1714,7 @@
|
||||||
},
|
},
|
||||||
"icu:calling__button--presenting__label": {
|
"icu:calling__button--presenting__label": {
|
||||||
"messageformat": "Share",
|
"messageformat": "Share",
|
||||||
"description": "Label under the share screen button"
|
"description": "(deleted 2023/10/13) Label under the share screen button"
|
||||||
},
|
},
|
||||||
"icu:calling__button--presenting-disabled": {
|
"icu:calling__button--presenting-disabled": {
|
||||||
"messageformat": "Presenting disabled",
|
"messageformat": "Presenting disabled",
|
||||||
|
@ -1718,7 +1730,7 @@
|
||||||
},
|
},
|
||||||
"icu:calling__button--ring__label": {
|
"icu:calling__button--ring__label": {
|
||||||
"messageformat": "Ring",
|
"messageformat": "Ring",
|
||||||
"description": "Label under the ring button"
|
"description": "(deleted 2023/10/13) Label under the ring button"
|
||||||
},
|
},
|
||||||
"icu:calling__button--ring__disabled-because-group-is-too-large": {
|
"icu:calling__button--ring__disabled-because-group-is-too-large": {
|
||||||
"messageformat": "Group is too large to ring the participants.",
|
"messageformat": "Group is too large to ring the participants.",
|
||||||
|
@ -1732,6 +1744,14 @@
|
||||||
"messageformat": "Enable ringing",
|
"messageformat": "Enable ringing",
|
||||||
"description": "Button tooltip label for turning ringing on"
|
"description": "Button tooltip label for turning ringing on"
|
||||||
},
|
},
|
||||||
|
"icu:CallingButton__ring-off": {
|
||||||
|
"messageformat": "Turn off ringing",
|
||||||
|
"description": "Button tooltip label for turning ringing off"
|
||||||
|
},
|
||||||
|
"icu:CallingButton--ring-on": {
|
||||||
|
"messageformat": "Turn on ringing",
|
||||||
|
"description": "Button tooltip label for turning ringing on"
|
||||||
|
},
|
||||||
"icu:calling__your-video-is-off": {
|
"icu:calling__your-video-is-off": {
|
||||||
"messageformat": "Your camera is off",
|
"messageformat": "Your camera is off",
|
||||||
"description": "Label in the calling lobby indicating that your camera is off"
|
"description": "Label in the calling lobby indicating that your camera is off"
|
||||||
|
@ -3509,7 +3529,39 @@
|
||||||
},
|
},
|
||||||
"icu:callDuration": {
|
"icu:callDuration": {
|
||||||
"messageformat": "Signal {duration}",
|
"messageformat": "Signal {duration}",
|
||||||
"description": "Shown in the call screen to indicate how long the call has been connected"
|
"description": "(deleted 2023/10/13) Shown in the call screen to indicate how long the call has been connected"
|
||||||
|
},
|
||||||
|
"icu:CallControls__InfoDisplay--participants": {
|
||||||
|
"messageformat": "{count, plural, one {# person} other {# people}}",
|
||||||
|
"description": "Shown in the call screen and lobby for group calls to specify the number of members in the call or in the group. Count is at always at least 1."
|
||||||
|
},
|
||||||
|
"icu:CallControls__InfoDisplay--audio-call": {
|
||||||
|
"messageformat": "Audio call",
|
||||||
|
"description": "Shown in the call lobby for a direct 1:1 call when the caller's video is disabled, to specify that an audio call will be placed when clicking the Start button."
|
||||||
|
},
|
||||||
|
"icu:CallControls__JoinLeaveButton--hangup-1-1": {
|
||||||
|
"messageformat": "End",
|
||||||
|
"description": "Title for the hangup button for a direct 1:1 call with only 2 participants."
|
||||||
|
},
|
||||||
|
"icu:CallControls__JoinLeaveButton--hangup-group": {
|
||||||
|
"messageformat": "Leave",
|
||||||
|
"description": "Title for the hangup button for a group call."
|
||||||
|
},
|
||||||
|
"icu:CallControls__MutedToast--muted": {
|
||||||
|
"messageformat": "Mic off",
|
||||||
|
"description": "Shown in a call when the user mutes their audio input using the Mute toggle button."
|
||||||
|
},
|
||||||
|
"icu:CallControls__MutedToast--unmuted": {
|
||||||
|
"messageformat": "Mic on",
|
||||||
|
"description": "Shown in a call when the user is muted and then unmutes their audio input using the Mute toggle button."
|
||||||
|
},
|
||||||
|
"icu:CallControls__RingingToast--ringing-on": {
|
||||||
|
"messageformat": "Ringing on",
|
||||||
|
"description": "Shown in a group call lobby when call ringing is disabled, then the user enables ringing using the Ringing toggle button."
|
||||||
|
},
|
||||||
|
"icu:CallControls__RingingToast--ringing-off": {
|
||||||
|
"messageformat": "Ringing off",
|
||||||
|
"description": "Shown in a group call lobby when call ringing is enabled, then the user disables ringing using the Ringing toggle button."
|
||||||
},
|
},
|
||||||
"icu:callingDeviceSelection__settings": {
|
"icu:callingDeviceSelection__settings": {
|
||||||
"messageformat": "Settings",
|
"messageformat": "Settings",
|
||||||
|
|
|
@ -18,6 +18,7 @@ $color-gray-60: #5e5e5e;
|
||||||
$color-gray-62: #545454;
|
$color-gray-62: #545454;
|
||||||
$color-gray-65: #4a4a4a;
|
$color-gray-65: #4a4a4a;
|
||||||
$color-gray-75: #3b3b3b;
|
$color-gray-75: #3b3b3b;
|
||||||
|
$color-gray-78: #343434;
|
||||||
$color-gray-80: #2e2e2e;
|
$color-gray-80: #2e2e2e;
|
||||||
$color-gray-90: #1b1b1b;
|
$color-gray-90: #1b1b1b;
|
||||||
$color-gray-95: #121212;
|
$color-gray-95: #121212;
|
||||||
|
|
|
@ -3568,21 +3568,6 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buttons {
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding-bottom: 32px;
|
|
||||||
padding-top: 32px;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--inline {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__background {
|
&__background {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -3648,8 +3633,8 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call {
|
.module-ongoing-call {
|
||||||
$local-preview-width: 136px;
|
$local-preview-width: 107px;
|
||||||
$local-preview-height: 102px;
|
$local-preview-height: 80px;
|
||||||
|
|
||||||
&__remote-video-enabled {
|
&__remote-video-enabled {
|
||||||
background-color: $color-gray-95;
|
background-color: $color-gray-95;
|
||||||
|
@ -3817,7 +3802,7 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 5px;
|
border-radius: 10px;
|
||||||
// stylelint-disable-next-line declaration-property-value-disallowed-list
|
// stylelint-disable-next-line declaration-property-value-disallowed-list
|
||||||
transform: translate(0, 0);
|
transform: translate(0, 0);
|
||||||
transition: transform 200ms linear, width 200ms linear, height 200ms linear;
|
transition: transform 200ms linear, width 200ms linear, height 200ms linear;
|
||||||
|
@ -3829,7 +3814,7 @@ button.module-image__border-overlay:focus {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 0 solid transparent;
|
border: 0 solid transparent;
|
||||||
border-radius: 5px;
|
border-radius: 10px;
|
||||||
transition-property: border-width, border-color;
|
transition-property: border-width, border-color;
|
||||||
// Turn on the transition when the user stops speaking to fade out.
|
// Turn on the transition when the user stops speaking to fade out.
|
||||||
transition-duration: 300ms;
|
transition-duration: 300ms;
|
||||||
|
@ -3918,14 +3903,13 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__local-preview-fullsize {
|
&__local-preview-fullsize {
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
inset-inline-start: 0;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
z-index: $z-index-negative;
|
z-index: $z-index-negative;
|
||||||
|
|
||||||
video {
|
video {
|
||||||
|
@ -3959,14 +3943,15 @@ button.module-image__border-overlay:focus {
|
||||||
&__local-preview-offset {
|
&__local-preview-offset {
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
max-width: $local-preview-width;
|
max-width: $local-preview-width;
|
||||||
|
margin-inline-start: 16px;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__local-preview {
|
&__local-preview {
|
||||||
border-radius: 5px;
|
border-radius: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: $local-preview-height;
|
height: $local-preview-height;
|
||||||
margin-block: 2px 16px;
|
margin-block-end: 16px;
|
||||||
margin-inline: 0 16px;
|
margin-inline: 0 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -4020,7 +4005,11 @@ button.module-image__border-overlay:focus {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
margin-inline-end: 25px;
|
margin-inline-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button:last-child {
|
||||||
|
margin-inline-end: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4118,24 +4107,32 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-calling-participants-list {
|
.module-calling-participants-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 320px;
|
||||||
|
height: 440px;
|
||||||
background-color: $color-gray-80;
|
background-color: $color-gray-80;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
margin-inline-end: 12px;
|
filter: drop-shadow(0px 4px 3px $color-black-alpha-20);
|
||||||
margin-top: 54px;
|
margin-inline-end: 340px;
|
||||||
|
margin-block-end: 85px;
|
||||||
|
margin-block-start: 20px;
|
||||||
|
outline: 1px solid $color-gray-62;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 14px;
|
padding-block: 5px 0;
|
||||||
width: 280px;
|
padding-inline: 5px;
|
||||||
padding-bottom: 0;
|
|
||||||
|
|
||||||
&__overlay {
|
&__overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
width: var(--window-width);
|
width: var(--window-width);
|
||||||
height: var(--window-height);
|
height: var(--window-height);
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
inset-inline-start: 0;
|
inset-inline-start: 0;
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
z-index: $z-index-calling;
|
z-index: $z-index-calling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4149,36 +4146,34 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
&__list {
|
&__list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: 0;
|
overflow: auto;
|
||||||
margin-inline: -14px;
|
margin: 0;
|
||||||
margin-top: 22px;
|
padding-block: 0;
|
||||||
overflow: scroll;
|
padding-inline: 0;
|
||||||
padding-bottom: 24px;
|
|
||||||
padding-inline: 14px;
|
|
||||||
padding-top: 0;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 4px;
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: $color-gray-45;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-corner,
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background-color: $color-gray-80;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__contact {
|
&__contact {
|
||||||
@include font-body-1;
|
@include font-body-1;
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-block: 2px;
|
||||||
|
padding-block: 5px;
|
||||||
|
padding-inline: 10px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-bottom: 16px;
|
border-radius: 6px;
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-gray-62;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar-and-name {
|
&__avatar-and-name {
|
||||||
|
@ -4189,6 +4184,7 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 13px;
|
||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -4199,6 +4195,9 @@ button.module-image__border-overlay:focus {
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-block-end: 2px;
|
||||||
|
padding-block: 8px;
|
||||||
|
padding-inline: 10px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__close {
|
&__close {
|
||||||
|
@ -4206,17 +4205,22 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
@include color-svg('../images/icons/v3/x/x.svg', $color-gray-15);
|
@include color-svg('../images/icons/v3/x/x.svg', $color-gray-15);
|
||||||
|
|
||||||
height: 20px;
|
height: 18px;
|
||||||
width: 20px;
|
width: 18px;
|
||||||
z-index: $z-index-above-base;
|
z-index: $z-index-above-base;
|
||||||
|
|
||||||
@include keyboard-mode {
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 2px solid $color-ultramarine;
|
background: $color-ultramarine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
&__muted {
|
&__muted {
|
||||||
&--video {
|
&--video {
|
||||||
@include color-svg(
|
@include color-svg(
|
||||||
|
@ -4224,9 +4228,9 @@ button.module-image__border-overlay:focus {
|
||||||
$color-white
|
$color-white
|
||||||
);
|
);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-inline-start: 18px;
|
margin-inline-start: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
width: 18px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--audio {
|
&--audio {
|
||||||
|
@ -4235,9 +4239,9 @@ button.module-image__border-overlay:focus {
|
||||||
$color-white
|
$color-white
|
||||||
);
|
);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-inline-start: 18px;
|
margin-inline-start: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
width: 18px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4247,9 +4251,9 @@ button.module-image__border-overlay:focus {
|
||||||
$color-white
|
$color-white
|
||||||
);
|
);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-inline-start: 18px;
|
margin-inline-start: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
width: 18px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ $color-gray-60: #5e5e5e;
|
||||||
$color-gray-62: #545454;
|
$color-gray-62: #545454;
|
||||||
$color-gray-65: #4a4a4a;
|
$color-gray-65: #4a4a4a;
|
||||||
$color-gray-75: #3b3b3b;
|
$color-gray-75: #3b3b3b;
|
||||||
|
$color-gray-78: #343434;
|
||||||
$color-gray-80: #2e2e2e;
|
$color-gray-80: #2e2e2e;
|
||||||
$color-gray-90: #1b1b1b;
|
$color-gray-90: #1b1b1b;
|
||||||
$color-gray-95: #121212;
|
$color-gray-95: #121212;
|
||||||
|
@ -253,7 +254,7 @@ $header-height: 52px;
|
||||||
|
|
||||||
$ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
|
$ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
|
||||||
$calling-background-color: $color-gray-95;
|
$calling-background-color: $color-gray-90;
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
|
||||||
|
|
108
stylesheets/components/CallControls.scss
Normal file
108
stylesheets/components/CallControls.scss
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.CallControls {
|
||||||
|
position: static;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 480px;
|
||||||
|
min-width: 480px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: $color-gray-78;
|
||||||
|
border-radius: 18px;
|
||||||
|
margin-block-end: 16px;
|
||||||
|
padding-block: 22px;
|
||||||
|
padding-inline: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__InfoDisplay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__CallTitle {
|
||||||
|
display: flex;
|
||||||
|
max-height: 40px;
|
||||||
|
color: $color-gray-15;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__Status {
|
||||||
|
display: flex;
|
||||||
|
min-height: 18px;
|
||||||
|
max-height: 36px;
|
||||||
|
color: $color-gray-20;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus-within {
|
||||||
|
outline: 2px solid $color-ultramarine;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__Status--ParticipantCount {
|
||||||
|
@include button-reset;
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 100%;
|
||||||
|
align-items: center;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: flex;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-inline-start: 1px;
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v3/chevron/chevron-right.svg',
|
||||||
|
$color-gray-20
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__ButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__JoinLeaveButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__JoinLeaveButton {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 17px;
|
||||||
|
padding-block: 7px;
|
||||||
|
padding-inline: 16px;
|
||||||
|
border-radius: 40px;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 1px $color-gray-80, 0 0 0 3px $color-ultramarine !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__JoinLeaveButton--hangup {
|
||||||
|
background-color: $color-accent-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallControls__JoinLeaveButton .module-spinner__container {
|
||||||
|
margin-block: -5px;
|
||||||
|
}
|
53
stylesheets/components/CallSettingsButton.scss
Normal file
53
stylesheets/components/CallSettingsButton.scss
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
@mixin CallSettingsButton-icon($path) {
|
||||||
|
@include color-svg($path, $color-gray-15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Button {
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba($color-gray-80, 0.7);
|
||||||
|
border: none;
|
||||||
|
border-radius: 40px;
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
justify-content: center;
|
||||||
|
outline: none;
|
||||||
|
width: 32px;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
outline-offset: 1px;
|
||||||
|
outline: 2px solid $color-ultramarine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon--Cancel {
|
||||||
|
@include CallSettingsButton-icon('../images/icons/v3/x/x.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon--GridView {
|
||||||
|
@include CallSettingsButton-icon('../images/icons/v3/grid/grid.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon--Pip {
|
||||||
|
@include CallSettingsButton-icon('../images/icons/v3/pip/pip.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon--Settings {
|
||||||
|
@include CallSettingsButton-icon('../images/icons/v3/settings/settings.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallSettingsButton__Icon--SpeakerView {
|
||||||
|
@include CallSettingsButton-icon(
|
||||||
|
'../images/icons/v3/speaker_view/speaker_view.svg'
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,22 +11,28 @@
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 52px;
|
border-radius: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 52px;
|
height: 36px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 52px;
|
width: 36px;
|
||||||
margin-inline: 6px;
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
outline-offset: 1px;
|
||||||
|
outline: 2px solid $color-ultramarine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin calling-button-icon($icon, $background-color, $icon-color) {
|
@mixin calling-button-icon($icon, $background-color, $icon-color) {
|
||||||
background-color: $background-color;
|
background-color: $background-color;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@include color-svg($icon, $icon-color);
|
@include color-svg($icon, $icon-color);
|
||||||
height: 24px;
|
height: 22px;
|
||||||
width: 24px;
|
width: 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,55 +117,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__participants {
|
&__button-container {
|
||||||
@include icon('../images/icons/v3/group/group.svg');
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&--container {
|
|
||||||
@include button-reset;
|
|
||||||
border: none;
|
|
||||||
color: $color-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--shown {
|
|
||||||
background-color: $color-gray-75;
|
|
||||||
border-radius: 16px;
|
|
||||||
padding-block: 6px;
|
|
||||||
padding-inline: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--count {
|
|
||||||
@include font-body-2;
|
|
||||||
margin-inline-start: 5px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__settings {
|
|
||||||
@include icon('../images/icons/v3/tune/tune.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&__grid-view {
|
|
||||||
@include icon('../images/icons/v3/grid/grid.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&__speaker-view {
|
|
||||||
@include icon('../images/icons/v3/speaker_view/speaker_view.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&__pip {
|
|
||||||
@include icon('../images/icons/v3/pip/pip.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&__cancel {
|
|
||||||
@include icon('../images/icons/v3/x/x.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&__container {
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-inline: 6px;
|
|
||||||
max-width: 64px;
|
max-width: 64px;
|
||||||
|
margin-inline: 10px;
|
||||||
|
|
||||||
transition: margin-inline-start 0.3s ease-out, opacity 0.3s ease-out;
|
transition: margin-inline-start 0.3s ease-out, opacity 0.3s ease-out;
|
||||||
@media (prefers-reduced-motion) {
|
@media (prefers-reduced-motion) {
|
||||||
|
@ -179,12 +141,40 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__tooltip {
|
||||||
@include font-subtitle;
|
background-color: $color-gray-80;
|
||||||
margin-top: 8px;
|
color: $color-gray-15;
|
||||||
text-align: center;
|
font-size: 13px;
|
||||||
color: $color-white;
|
outline: 1px solid $color-gray-62;
|
||||||
@include calling-text-shadow;
|
padding-block: 5px;
|
||||||
user-select: none;
|
padding-inline: 12px;
|
||||||
|
filter: drop-shadow(0px 4px 3px $color-black-alpha-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip .module-tooltip-arrow::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip[data-placement='bottom'] .module-tooltip-arrow::before {
|
||||||
|
border-color: transparent transparent $color-gray-62 transparent;
|
||||||
|
margin-block-start: -14px;
|
||||||
|
margin-inline-start: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip[data-placement='bottom'] .module-tooltip-arrow::after {
|
||||||
|
border-bottom-color: $color-gray-80 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip[data-placement='top'] .module-tooltip-arrow::before {
|
||||||
|
border-color: $color-gray-62 transparent transparent transparent;
|
||||||
|
margin-block-start: 0;
|
||||||
|
margin-inline-start: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip[data-placement='top'] .module-tooltip-arrow::after {
|
||||||
|
border-top-color: $color-gray-80 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
|
|
||||||
&--camera-is-on {
|
&--camera-is-on {
|
||||||
@include lonely-local-video-preview;
|
@include lonely-local-video-preview;
|
||||||
|
top: 15px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(100% - 127px);
|
||||||
|
width: auto;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
.module-CallingLobbyJoinButton {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
|
@ -38,8 +38,9 @@
|
||||||
@import './components/CallsTab.scss';
|
@import './components/CallsTab.scss';
|
||||||
@import './components/CallingAudioIndicator.scss';
|
@import './components/CallingAudioIndicator.scss';
|
||||||
@import './components/CallingButton.scss';
|
@import './components/CallingButton.scss';
|
||||||
|
@import './components/CallControls.scss';
|
||||||
|
@import './components/CallSettingsButton.scss';
|
||||||
@import './components/CallingLobby.scss';
|
@import './components/CallingLobby.scss';
|
||||||
@import './components/CallingLobbyJoinButton.scss';
|
|
||||||
@import './components/CallingPreCallInfo.scss';
|
@import './components/CallingPreCallInfo.scss';
|
||||||
@import './components/CallingScreenSharingController.scss';
|
@import './components/CallingScreenSharingController.scss';
|
||||||
@import './components/CallingSelectPresentingSourcesModal.scss';
|
@import './components/CallingSelectPresentingSourcesModal.scss';
|
||||||
|
|
51
ts/components/CallParticipantCount.tsx
Normal file
51
ts/components/CallParticipantCount.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
|
export type PropsType = {
|
||||||
|
i18n: LocalizerType;
|
||||||
|
groupMemberCount?: number;
|
||||||
|
participantCount: number;
|
||||||
|
toggleParticipants: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CallParticipantCount({
|
||||||
|
i18n,
|
||||||
|
groupMemberCount,
|
||||||
|
participantCount,
|
||||||
|
toggleParticipants,
|
||||||
|
}: PropsType): JSX.Element {
|
||||||
|
const count = participantCount || groupMemberCount || 1;
|
||||||
|
const innerText = i18n('icu:CallControls__InfoDisplay--participants', {
|
||||||
|
count: String(count),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call not started, can't click to show participants
|
||||||
|
if (!participantCount) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
aria-label={i18n('icu:calling__participants', {
|
||||||
|
people: String(count),
|
||||||
|
})}
|
||||||
|
className="CallControls__Status--InactiveCallParticipantCount"
|
||||||
|
>
|
||||||
|
{innerText}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-label={i18n('icu:calling__participants', {
|
||||||
|
people: String(count),
|
||||||
|
})}
|
||||||
|
className="CallControls__Status--ParticipantCount"
|
||||||
|
onClick={toggleParticipants}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{innerText}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -319,6 +319,18 @@ export function GroupCallMany(): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GroupCallSpeakerView(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<CallScreen
|
||||||
|
{...createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
viewMode: CallViewMode.Speaker,
|
||||||
|
remoteParticipants: allRemoteParticipants.slice(0, 3),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function GroupCallReconnecting(): JSX.Element {
|
export function GroupCallReconnecting(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CallScreen
|
<CallScreen
|
||||||
|
|
|
@ -17,6 +17,8 @@ import { Avatar, AvatarSize } from './Avatar';
|
||||||
import { CallingHeader } from './CallingHeader';
|
import { CallingHeader } from './CallingHeader';
|
||||||
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
||||||
import { CallingButton, CallingButtonType } from './CallingButton';
|
import { CallingButton, CallingButtonType } from './CallingButton';
|
||||||
|
import { Button, ButtonVariant } from './Button';
|
||||||
|
import { TooltipPlacement } from './Tooltip';
|
||||||
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
||||||
import type {
|
import type {
|
||||||
ActiveCallType,
|
ActiveCallType,
|
||||||
|
@ -33,11 +35,13 @@ import {
|
||||||
import { AvatarColors } from '../types/Colors';
|
import { AvatarColors } from '../types/Colors';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
|
useMutedToast,
|
||||||
useReconnectingToast,
|
useReconnectingToast,
|
||||||
useScreenSharingStoppedToast,
|
useScreenSharingStoppedToast,
|
||||||
} from './CallingToastManager';
|
} from './CallingToastManager';
|
||||||
import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
|
import { DirectCallRemoteParticipant } from './DirectCallRemoteParticipant';
|
||||||
import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
|
import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
|
||||||
|
import { CallParticipantCount } from './CallParticipantCount';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { NeedsScreenRecordingPermissionsModal } from './NeedsScreenRecordingPermissionsModal';
|
import { NeedsScreenRecordingPermissionsModal } from './NeedsScreenRecordingPermissionsModal';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
@ -52,7 +56,7 @@ import {
|
||||||
useKeyboardShortcuts,
|
useKeyboardShortcuts,
|
||||||
} from '../hooks/useKeyboardShortcuts';
|
} from '../hooks/useKeyboardShortcuts';
|
||||||
import { useValueAtFixedRate } from '../hooks/useValueAtFixedRate';
|
import { useValueAtFixedRate } from '../hooks/useValueAtFixedRate';
|
||||||
import { isReconnecting } from '../util/callingIsReconnecting';
|
import { isReconnecting as callingIsReconnecting } from '../util/callingIsReconnecting';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -82,12 +86,6 @@ export type PropsType = {
|
||||||
toggleSpeakerView: () => void;
|
toggleSpeakerView: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DirectCallHeaderMessagePropsType = {
|
|
||||||
i18n: LocalizerType;
|
|
||||||
callState: CallState;
|
|
||||||
joinedAt: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isInSpeakerView = (
|
export const isInSpeakerView = (
|
||||||
call: Pick<ActiveCallStateType, 'viewMode'> | undefined
|
call: Pick<ActiveCallStateType, 'viewMode'> | undefined
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
@ -97,11 +95,11 @@ export const isInSpeakerView = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function DirectCallHeaderMessage({
|
function CallDuration({
|
||||||
callState,
|
|
||||||
i18n,
|
|
||||||
joinedAt,
|
joinedAt,
|
||||||
}: DirectCallHeaderMessagePropsType): JSX.Element | null {
|
}: {
|
||||||
|
joinedAt: number | null;
|
||||||
|
}): JSX.Element | null {
|
||||||
const [acceptedDuration, setAcceptedDuration] = useState<
|
const [acceptedDuration, setAcceptedDuration] = useState<
|
||||||
number | undefined
|
number | undefined
|
||||||
>();
|
>();
|
||||||
|
@ -117,14 +115,8 @@ function DirectCallHeaderMessage({
|
||||||
return clearInterval.bind(null, interval);
|
return clearInterval.bind(null, interval);
|
||||||
}, [joinedAt]);
|
}, [joinedAt]);
|
||||||
|
|
||||||
if (callState === CallState.Accepted && acceptedDuration) {
|
if (acceptedDuration) {
|
||||||
return (
|
return <>{renderDuration(acceptedDuration)}</>;
|
||||||
<>
|
|
||||||
{i18n('icu:callDuration', {
|
|
||||||
duration: renderDuration(acceptedDuration),
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +153,6 @@ export function CallScreen({
|
||||||
presentingSource,
|
presentingSource,
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
showNeedsScreenRecordingPermissionsWarning,
|
showNeedsScreenRecordingPermissionsWarning,
|
||||||
showParticipantsList,
|
|
||||||
} = activeCall;
|
} = activeCall;
|
||||||
|
|
||||||
const isSpeaking = useValueAtFixedRate(
|
const isSpeaking = useValueAtFixedRate(
|
||||||
|
@ -260,6 +251,7 @@ export function CallScreen({
|
||||||
};
|
};
|
||||||
}, [toggleAudio, toggleVideo]);
|
}, [toggleAudio, toggleVideo]);
|
||||||
|
|
||||||
|
useMutedToast(hasLocalAudio, i18n);
|
||||||
useReconnectingToast({ activeCall, i18n });
|
useReconnectingToast({ activeCall, i18n });
|
||||||
useScreenSharingStoppedToast({ activeCall, i18n });
|
useScreenSharingStoppedToast({ activeCall, i18n });
|
||||||
|
|
||||||
|
@ -272,10 +264,10 @@ export function CallScreen({
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSendingVideo = hasLocalVideo || presentingSource;
|
const isSendingVideo = hasLocalVideo || presentingSource;
|
||||||
|
const isReconnecting: boolean = callingIsReconnecting(activeCall);
|
||||||
|
|
||||||
let isRinging: boolean;
|
let isRinging: boolean;
|
||||||
let hasCallStarted: boolean;
|
let hasCallStarted: boolean;
|
||||||
let headerMessage: ReactNode | undefined;
|
|
||||||
let headerTitle: string | undefined;
|
let headerTitle: string | undefined;
|
||||||
let isConnected: boolean;
|
let isConnected: boolean;
|
||||||
let participantCount: number;
|
let participantCount: number;
|
||||||
|
@ -287,14 +279,6 @@ export function CallScreen({
|
||||||
activeCall.callState === CallState.Prering ||
|
activeCall.callState === CallState.Prering ||
|
||||||
activeCall.callState === CallState.Ringing;
|
activeCall.callState === CallState.Ringing;
|
||||||
hasCallStarted = !isRinging;
|
hasCallStarted = !isRinging;
|
||||||
headerMessage = (
|
|
||||||
<DirectCallHeaderMessage
|
|
||||||
i18n={i18n}
|
|
||||||
callState={activeCall.callState || CallState.Prering}
|
|
||||||
joinedAt={activeCall.joinedAt}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
headerTitle = isRinging ? undefined : conversation.title;
|
|
||||||
isConnected = activeCall.callState === CallState.Accepted;
|
isConnected = activeCall.callState === CallState.Accepted;
|
||||||
participantCount = isConnected ? 2 : 0;
|
participantCount = isConnected ? 2 : 0;
|
||||||
remoteParticipantsElement = hasCallStarted ? (
|
remoteParticipantsElement = hasCallStarted ? (
|
||||||
|
@ -302,7 +286,7 @@ export function CallScreen({
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
hasRemoteVideo={hasRemoteVideo}
|
hasRemoteVideo={hasRemoteVideo}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isReconnecting={isReconnecting(activeCall)}
|
isReconnecting={isReconnecting}
|
||||||
setRendererCanvas={setRendererCanvas}
|
setRendererCanvas={setRendererCanvas}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -338,7 +322,7 @@ export function CallScreen({
|
||||||
remoteParticipants={activeCall.remoteParticipants}
|
remoteParticipants={activeCall.remoteParticipants}
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
remoteAudioLevels={activeCall.remoteAudioLevels}
|
remoteAudioLevels={activeCall.remoteAudioLevels}
|
||||||
isCallReconnecting={isReconnecting(activeCall)}
|
isCallReconnecting={isReconnecting}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
@ -454,6 +438,46 @@ export function CallScreen({
|
||||||
presentingButtonType = CallingButtonType.PRESENTING_OFF;
|
presentingButtonType = CallingButtonType.PRESENTING_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const callStatus: ReactNode | string = React.useMemo(() => {
|
||||||
|
if (isRinging) {
|
||||||
|
return i18n('icu:outgoingCallRinging');
|
||||||
|
}
|
||||||
|
if (isReconnecting) {
|
||||||
|
return i18n('icu:callReconnecting');
|
||||||
|
}
|
||||||
|
if (isGroupCall) {
|
||||||
|
return (
|
||||||
|
<CallParticipantCount
|
||||||
|
i18n={i18n}
|
||||||
|
participantCount={participantCount}
|
||||||
|
toggleParticipants={toggleParticipants}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// joinedAt is only available for direct calls
|
||||||
|
if (isConnected) {
|
||||||
|
return <CallDuration joinedAt={activeCall.joinedAt} />;
|
||||||
|
}
|
||||||
|
if (hasLocalVideo) {
|
||||||
|
return i18n('icu:ContactListItem__menu__video-call');
|
||||||
|
}
|
||||||
|
if (hasLocalAudio) {
|
||||||
|
return i18n('icu:CallControls__InfoDisplay--audio-call');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [
|
||||||
|
i18n,
|
||||||
|
isRinging,
|
||||||
|
isConnected,
|
||||||
|
activeCall.joinedAt,
|
||||||
|
isReconnecting,
|
||||||
|
isGroupCall,
|
||||||
|
participantCount,
|
||||||
|
hasLocalVideo,
|
||||||
|
hasLocalAudio,
|
||||||
|
toggleParticipants,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -489,11 +513,8 @@ export function CallScreen({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isInSpeakerView={isInSpeakerView(activeCall)}
|
isInSpeakerView={isInSpeakerView(activeCall)}
|
||||||
isGroupCall={isGroupCall}
|
isGroupCall={isGroupCall}
|
||||||
message={headerMessage}
|
|
||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
showParticipantsList={showParticipantsList}
|
|
||||||
title={headerTitle}
|
title={headerTitle}
|
||||||
toggleParticipants={toggleParticipants}
|
|
||||||
togglePip={togglePip}
|
togglePip={togglePip}
|
||||||
toggleSettings={toggleSettings}
|
toggleSettings={toggleSettings}
|
||||||
toggleSpeakerView={toggleSpeakerView}
|
toggleSpeakerView={toggleSpeakerView}
|
||||||
|
@ -516,16 +537,23 @@ export function CallScreen({
|
||||||
<div className="module-ongoing-call__footer__local-preview-offset" />
|
<div className="module-ongoing-call__footer__local-preview-offset" />
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
'CallControls',
|
||||||
'module-ongoing-call__footer__actions',
|
'module-ongoing-call__footer__actions',
|
||||||
controlsFadeClass
|
controlsFadeClass
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<div className="CallControls__InfoDisplay">
|
||||||
|
<div className="CallControls__CallTitle">{conversation.title}</div>
|
||||||
|
<div className="CallControls__Status">{callStatus}</div>
|
||||||
|
</div>
|
||||||
|
<div className="CallControls__ButtonContainer">
|
||||||
<CallingButton
|
<CallingButton
|
||||||
buttonType={presentingButtonType}
|
buttonType={presentingButtonType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onMouseEnter={onControlsMouseEnter}
|
onMouseEnter={onControlsMouseEnter}
|
||||||
onMouseLeave={onControlsMouseLeave}
|
onMouseLeave={onControlsMouseLeave}
|
||||||
onClick={togglePresenting}
|
onClick={togglePresenting}
|
||||||
|
tooltipDirection={TooltipPlacement.Top}
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
<CallingButton
|
||||||
buttonType={videoButtonType}
|
buttonType={videoButtonType}
|
||||||
|
@ -533,6 +561,7 @@ export function CallScreen({
|
||||||
onMouseEnter={onControlsMouseEnter}
|
onMouseEnter={onControlsMouseEnter}
|
||||||
onMouseLeave={onControlsMouseLeave}
|
onMouseLeave={onControlsMouseLeave}
|
||||||
onClick={toggleVideo}
|
onClick={toggleVideo}
|
||||||
|
tooltipDirection={TooltipPlacement.Top}
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
<CallingButton
|
||||||
buttonType={audioButtonType}
|
buttonType={audioButtonType}
|
||||||
|
@ -540,14 +569,24 @@ export function CallScreen({
|
||||||
onMouseEnter={onControlsMouseEnter}
|
onMouseEnter={onControlsMouseEnter}
|
||||||
onMouseLeave={onControlsMouseLeave}
|
onMouseLeave={onControlsMouseLeave}
|
||||||
onClick={toggleAudio}
|
onClick={toggleAudio}
|
||||||
|
tooltipDirection={TooltipPlacement.Top}
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
</div>
|
||||||
buttonType={CallingButtonType.HANG_UP}
|
<div
|
||||||
i18n={i18n}
|
className="CallControls__JoinLeaveButtonContainer"
|
||||||
onMouseEnter={onControlsMouseEnter}
|
onMouseEnter={onControlsMouseEnter}
|
||||||
onMouseLeave={onControlsMouseLeave}
|
onMouseLeave={onControlsMouseLeave}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="CallControls__JoinLeaveButton CallControls__JoinLeaveButton--hangup"
|
||||||
onClick={hangUp}
|
onClick={hangUp}
|
||||||
/>
|
variant={ButtonVariant.Destructive}
|
||||||
|
>
|
||||||
|
{isGroupCall
|
||||||
|
? i18n('icu:CallControls__JoinLeaveButton--hangup-group')
|
||||||
|
: i18n('icu:CallControls__JoinLeaveButton--hangup-1-1')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="module-ongoing-call__footer__local-preview">
|
<div className="module-ongoing-call__footer__local-preview">
|
||||||
{localPreviewNode}
|
{localPreviewNode}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
buttonType: CallingButtonType.HANG_UP,
|
buttonType: CallingButtonType.RING_ON,
|
||||||
i18n,
|
i18n,
|
||||||
onClick: action('on-click'),
|
onClick: action('on-click'),
|
||||||
onMouseEnter: action('on-mouse-enter'),
|
onMouseEnter: action('on-mouse-enter'),
|
||||||
|
|
|
@ -13,7 +13,6 @@ export enum CallingButtonType {
|
||||||
AUDIO_DISABLED = 'AUDIO_DISABLED',
|
AUDIO_DISABLED = 'AUDIO_DISABLED',
|
||||||
AUDIO_OFF = 'AUDIO_OFF',
|
AUDIO_OFF = 'AUDIO_OFF',
|
||||||
AUDIO_ON = 'AUDIO_ON',
|
AUDIO_ON = 'AUDIO_ON',
|
||||||
HANG_UP = 'HANG_UP',
|
|
||||||
PRESENTING_DISABLED = 'PRESENTING_DISABLED',
|
PRESENTING_DISABLED = 'PRESENTING_DISABLED',
|
||||||
PRESENTING_OFF = 'PRESENTING_OFF',
|
PRESENTING_OFF = 'PRESENTING_OFF',
|
||||||
PRESENTING_ON = 'PRESENTING_ON',
|
PRESENTING_ON = 'PRESENTING_ON',
|
||||||
|
@ -48,88 +47,69 @@ export function CallingButton({
|
||||||
|
|
||||||
let classNameSuffix = '';
|
let classNameSuffix = '';
|
||||||
let tooltipContent = '';
|
let tooltipContent = '';
|
||||||
let label = '';
|
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
if (buttonType === CallingButtonType.AUDIO_DISABLED) {
|
if (buttonType === CallingButtonType.AUDIO_DISABLED) {
|
||||||
classNameSuffix = 'audio--disabled';
|
classNameSuffix = 'audio--disabled';
|
||||||
tooltipContent = i18n('icu:calling__button--audio-disabled');
|
tooltipContent = i18n('icu:calling__button--audio-disabled');
|
||||||
label = i18n('icu:calling__button--audio__label');
|
|
||||||
disabled = true;
|
disabled = true;
|
||||||
} else if (buttonType === CallingButtonType.AUDIO_OFF) {
|
} else if (buttonType === CallingButtonType.AUDIO_OFF) {
|
||||||
classNameSuffix = 'audio--off';
|
classNameSuffix = 'audio--off';
|
||||||
tooltipContent = i18n('icu:calling__button--audio-on');
|
tooltipContent = i18n('icu:calling__button--audio-on');
|
||||||
label = i18n('icu:calling__button--audio__label');
|
|
||||||
} else if (buttonType === CallingButtonType.AUDIO_ON) {
|
} else if (buttonType === CallingButtonType.AUDIO_ON) {
|
||||||
classNameSuffix = 'audio--on';
|
classNameSuffix = 'audio--on';
|
||||||
tooltipContent = i18n('icu:calling__button--audio-off');
|
tooltipContent = i18n('icu:calling__button--audio-off');
|
||||||
label = i18n('icu:calling__button--audio__label');
|
|
||||||
} else if (buttonType === CallingButtonType.VIDEO_DISABLED) {
|
} else if (buttonType === CallingButtonType.VIDEO_DISABLED) {
|
||||||
classNameSuffix = 'video--disabled';
|
classNameSuffix = 'video--disabled';
|
||||||
tooltipContent = i18n('icu:calling__button--video-disabled');
|
tooltipContent = i18n('icu:calling__button--video-disabled');
|
||||||
disabled = true;
|
disabled = true;
|
||||||
label = i18n('icu:calling__button--video__label');
|
|
||||||
} else if (buttonType === CallingButtonType.VIDEO_OFF) {
|
} else if (buttonType === CallingButtonType.VIDEO_OFF) {
|
||||||
classNameSuffix = 'video--off';
|
classNameSuffix = 'video--off';
|
||||||
tooltipContent = i18n('icu:calling__button--video-on');
|
tooltipContent = i18n('icu:calling__button--video-on');
|
||||||
label = i18n('icu:calling__button--video__label');
|
|
||||||
} else if (buttonType === CallingButtonType.VIDEO_ON) {
|
} else if (buttonType === CallingButtonType.VIDEO_ON) {
|
||||||
classNameSuffix = 'video--on';
|
classNameSuffix = 'video--on';
|
||||||
tooltipContent = i18n('icu:calling__button--video-off');
|
tooltipContent = i18n('icu:calling__button--video-off');
|
||||||
label = i18n('icu:calling__button--video__label');
|
|
||||||
} else if (buttonType === CallingButtonType.HANG_UP) {
|
|
||||||
classNameSuffix = 'hangup';
|
|
||||||
tooltipContent = i18n('icu:calling__hangup');
|
|
||||||
label = i18n('icu:calling__hangup');
|
|
||||||
} else if (buttonType === CallingButtonType.RING_DISABLED) {
|
} else if (buttonType === CallingButtonType.RING_DISABLED) {
|
||||||
classNameSuffix = 'ring--disabled';
|
classNameSuffix = 'ring--disabled';
|
||||||
disabled = true;
|
disabled = true;
|
||||||
tooltipContent = i18n(
|
tooltipContent = i18n(
|
||||||
'icu:calling__button--ring__disabled-because-group-is-too-large'
|
'icu:calling__button--ring__disabled-because-group-is-too-large'
|
||||||
);
|
);
|
||||||
label = i18n('icu:calling__button--ring__label');
|
|
||||||
} else if (buttonType === CallingButtonType.RING_OFF) {
|
} else if (buttonType === CallingButtonType.RING_OFF) {
|
||||||
classNameSuffix = 'ring--off';
|
classNameSuffix = 'ring--off';
|
||||||
tooltipContent = i18n('icu:calling__button--ring__on');
|
tooltipContent = i18n('icu:CallingButton--ring-on');
|
||||||
label = i18n('icu:calling__button--ring__label');
|
|
||||||
} else if (buttonType === CallingButtonType.RING_ON) {
|
} else if (buttonType === CallingButtonType.RING_ON) {
|
||||||
classNameSuffix = 'ring--on';
|
classNameSuffix = 'ring--on';
|
||||||
tooltipContent = i18n('icu:calling__button--ring__off');
|
tooltipContent = i18n('icu:CallingButton__ring-off');
|
||||||
label = i18n('icu:calling__button--ring__label');
|
|
||||||
} else if (buttonType === CallingButtonType.PRESENTING_DISABLED) {
|
} else if (buttonType === CallingButtonType.PRESENTING_DISABLED) {
|
||||||
classNameSuffix = 'presenting--disabled';
|
classNameSuffix = 'presenting--disabled';
|
||||||
tooltipContent = i18n('icu:calling__button--presenting-disabled');
|
tooltipContent = i18n('icu:calling__button--presenting-disabled');
|
||||||
disabled = true;
|
disabled = true;
|
||||||
label = i18n('icu:calling__button--presenting__label');
|
|
||||||
} else if (buttonType === CallingButtonType.PRESENTING_ON) {
|
} else if (buttonType === CallingButtonType.PRESENTING_ON) {
|
||||||
classNameSuffix = 'presenting--on';
|
classNameSuffix = 'presenting--on';
|
||||||
tooltipContent = i18n('icu:calling__button--presenting-off');
|
tooltipContent = i18n('icu:calling__button--presenting-off');
|
||||||
label = i18n('icu:calling__button--presenting__label');
|
|
||||||
} else if (buttonType === CallingButtonType.PRESENTING_OFF) {
|
} else if (buttonType === CallingButtonType.PRESENTING_OFF) {
|
||||||
classNameSuffix = 'presenting--off';
|
classNameSuffix = 'presenting--off';
|
||||||
tooltipContent = i18n('icu:calling__button--presenting-on');
|
tooltipContent = i18n('icu:calling__button--presenting-on');
|
||||||
label = i18n('icu:calling__button--presenting__label');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = classNames(
|
|
||||||
'CallingButton__icon',
|
|
||||||
`CallingButton__icon--${classNameSuffix}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="CallingButton">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
className="CallingButton__tooltip"
|
||||||
|
wrapperClassName={classNames(
|
||||||
|
'CallingButton__button-container',
|
||||||
|
!isVisible && 'CallingButton__button-container--hidden'
|
||||||
|
)}
|
||||||
content={tooltipContent}
|
content={tooltipContent}
|
||||||
direction={tooltipDirection}
|
direction={tooltipDirection}
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'CallingButton__container',
|
|
||||||
!isVisible && 'CallingButton__container--hidden'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label={tooltipContent}
|
aria-label={tooltipContent}
|
||||||
className={className}
|
className={classNames(
|
||||||
|
'CallingButton__icon',
|
||||||
|
`CallingButton__icon--${classNameSuffix}`
|
||||||
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
id={uniqueButtonId}
|
id={uniqueButtonId}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
@ -139,10 +119,7 @@ export function CallingButton({
|
||||||
>
|
>
|
||||||
<div />
|
<div />
|
||||||
</button>
|
</button>
|
||||||
<label className="CallingButton__label" htmlFor={uniqueButtonId}>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,7 @@ export default {
|
||||||
isGroupCall: false,
|
isGroupCall: false,
|
||||||
message: '',
|
message: '',
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
showParticipantsList: false,
|
|
||||||
title: 'With Someone',
|
title: 'With Someone',
|
||||||
toggleParticipants: action('toggle-participants'),
|
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
},
|
},
|
||||||
|
@ -52,14 +50,7 @@ export function WithParticipants(args: PropsType): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WithParticipantsShown(args: PropsType): JSX.Element {
|
export function WithParticipantsShown(args: PropsType): JSX.Element {
|
||||||
return (
|
return <CallingHeader {...args} isGroupCall participantCount={10} />;
|
||||||
<CallingHeader
|
|
||||||
{...args}
|
|
||||||
isGroupCall
|
|
||||||
participantCount={10}
|
|
||||||
showParticipantsList
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LongTitle(args: PropsType): JSX.Element {
|
export function LongTitle(args: PropsType): JSX.Element {
|
||||||
|
|
|
@ -15,9 +15,7 @@ export type PropsType = {
|
||||||
message?: ReactNode;
|
message?: ReactNode;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
participantCount: number;
|
participantCount: number;
|
||||||
showParticipantsList: boolean;
|
|
||||||
title?: string;
|
title?: string;
|
||||||
toggleParticipants?: () => void;
|
|
||||||
togglePip?: () => void;
|
togglePip?: () => void;
|
||||||
toggleSettings: () => void;
|
toggleSettings: () => void;
|
||||||
toggleSpeakerView?: () => void;
|
toggleSpeakerView?: () => void;
|
||||||
|
@ -30,9 +28,7 @@ export function CallingHeader({
|
||||||
message,
|
message,
|
||||||
onCancel,
|
onCancel,
|
||||||
participantCount,
|
participantCount,
|
||||||
showParticipantsList,
|
|
||||||
title,
|
title,
|
||||||
toggleParticipants,
|
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
toggleSpeakerView,
|
toggleSpeakerView,
|
||||||
|
@ -46,48 +42,24 @@ export function CallingHeader({
|
||||||
<div className="module-ongoing-call__header-message">{message}</div>
|
<div className="module-ongoing-call__header-message">{message}</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="module-calling-tools">
|
<div className="module-calling-tools">
|
||||||
{isGroupCall && participantCount ? (
|
{togglePip && (
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={i18n('icu:calling__participants', {
|
content={i18n('icu:calling__pip--on')}
|
||||||
people: String(participantCount),
|
className="CallingButton__tooltip"
|
||||||
})}
|
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('icu:calling__participants', {
|
aria-label={i18n('icu:calling__pip--on')}
|
||||||
people: String(participantCount),
|
className="CallSettingsButton__Button"
|
||||||
})}
|
onClick={togglePip}
|
||||||
className={classNames(
|
|
||||||
'CallingButton__participants--container',
|
|
||||||
{
|
|
||||||
'CallingButton__participants--shown': showParticipantsList,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
onClick={toggleParticipants}
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="CallingButton__participants" />
|
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Pip" />
|
||||||
<span className="CallingButton__participants--count">
|
|
||||||
{participantCount}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
<div className="module-calling-tools__button">
|
|
||||||
<Tooltip
|
|
||||||
content={i18n('icu:callingDeviceSelection__settings')}
|
|
||||||
theme={Theme.Dark}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
aria-label={i18n('icu:callingDeviceSelection__settings')}
|
|
||||||
className="CallingButton__settings"
|
|
||||||
onClick={toggleSettings}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
|
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -96,6 +68,7 @@ export function CallingHeader({
|
||||||
? i18n('icu:calling__switch-view--to-grid')
|
? i18n('icu:calling__switch-view--to-grid')
|
||||||
: i18n('icu:calling__switch-view--to-speaker')
|
: i18n('icu:calling__switch-view--to-speaker')
|
||||||
}
|
}
|
||||||
|
className="CallingButton__tooltip"
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
@ -104,38 +77,53 @@ export function CallingHeader({
|
||||||
? i18n('icu:calling__switch-view--to-grid')
|
? i18n('icu:calling__switch-view--to-grid')
|
||||||
: i18n('icu:calling__switch-view--to-speaker')
|
: i18n('icu:calling__switch-view--to-speaker')
|
||||||
}
|
}
|
||||||
className={
|
className="CallSettingsButton__Button"
|
||||||
isInSpeakerView
|
|
||||||
? 'CallingButton__grid-view'
|
|
||||||
: 'CallingButton__speaker-view'
|
|
||||||
}
|
|
||||||
onClick={toggleSpeakerView}
|
onClick={toggleSpeakerView}
|
||||||
type="button"
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'CallSettingsButton__Icon',
|
||||||
|
isInSpeakerView
|
||||||
|
? 'CallSettingsButton__Icon--GridView'
|
||||||
|
: 'CallSettingsButton__Icon--SpeakerView'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{togglePip && (
|
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip content={i18n('icu:calling__pip--on')} theme={Theme.Dark}>
|
<Tooltip
|
||||||
|
content={i18n('icu:callingDeviceSelection__settings')}
|
||||||
|
className="CallingButton__tooltip"
|
||||||
|
theme={Theme.Dark}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('icu:calling__pip--on')}
|
aria-label={i18n('icu:callingDeviceSelection__settings')}
|
||||||
className="CallingButton__pip"
|
className="CallSettingsButton__Button"
|
||||||
onClick={togglePip}
|
onClick={toggleSettings}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
>
|
||||||
|
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Settings" />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<div className="module-calling-tools__button">
|
<div className="module-calling-tools__button">
|
||||||
<Tooltip content={i18n('icu:cancel')} theme={Theme.Dark}>
|
<Tooltip
|
||||||
|
content={i18n('icu:cancel')}
|
||||||
|
theme={Theme.Dark}
|
||||||
|
className="CallingButton__tooltip"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('icu:cancel')}
|
aria-label={i18n('icu:cancel')}
|
||||||
className="CallingButton__cancel"
|
className="CallSettingsButton__Button CallSettingsButton__Button--Cancel"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
>
|
||||||
|
<span className="CallSettingsButton__Icon CallSettingsButton__Icon--Cancel" />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
import { CallingButton, CallingButtonType } from './CallingButton';
|
import { CallingButton, CallingButtonType } from './CallingButton';
|
||||||
import { TooltipPlacement } from './Tooltip';
|
import { TooltipPlacement } from './Tooltip';
|
||||||
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
||||||
|
import { CallParticipantCount } from './CallParticipantCount';
|
||||||
import { CallingHeader } from './CallingHeader';
|
import { CallingHeader } from './CallingHeader';
|
||||||
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
|
||||||
import {
|
import {
|
||||||
|
@ -23,6 +24,7 @@ import { useIsOnline } from '../hooks/useIsOnline';
|
||||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { useCallingToasts } from './CallingToast';
|
import { useCallingToasts } from './CallingToast';
|
||||||
|
import { useMutedToast } from './CallingToastManager';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
availableCameras: Array<MediaDeviceInfo>;
|
availableCameras: Array<MediaDeviceInfo>;
|
||||||
|
@ -84,7 +86,6 @@ export function CallingLobby({
|
||||||
setLocalPreview,
|
setLocalPreview,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
showParticipantsList,
|
|
||||||
toggleParticipants,
|
toggleParticipants,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
outgoingRing,
|
outgoingRing,
|
||||||
|
@ -143,8 +144,6 @@ export function CallingLobby({
|
||||||
|
|
||||||
const [isCallConnecting, setIsCallConnecting] = React.useState(false);
|
const [isCallConnecting, setIsCallConnecting] = React.useState(false);
|
||||||
|
|
||||||
useWasInitiallyMutedToast(hasLocalAudio, i18n);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-nested-ternary
|
// eslint-disable-next-line no-nested-ternary
|
||||||
const videoButtonType = hasLocalVideo
|
const videoButtonType = hasLocalVideo
|
||||||
? CallingButtonType.VIDEO_ON
|
? CallingButtonType.VIDEO_ON
|
||||||
|
@ -201,6 +200,38 @@ export function CallingLobby({
|
||||||
callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Start;
|
callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const callStatus = React.useMemo(() => {
|
||||||
|
if (isGroupCall) {
|
||||||
|
return (
|
||||||
|
<CallParticipantCount
|
||||||
|
i18n={i18n}
|
||||||
|
groupMemberCount={groupMembers?.length ?? 0}
|
||||||
|
participantCount={peekedParticipants.length}
|
||||||
|
toggleParticipants={toggleParticipants}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (hasLocalVideo) {
|
||||||
|
return i18n('icu:ContactListItem__menu__video-call');
|
||||||
|
}
|
||||||
|
if (hasLocalAudio) {
|
||||||
|
return i18n('icu:CallControls__InfoDisplay--audio-call');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [
|
||||||
|
isGroupCall,
|
||||||
|
peekedParticipants.length,
|
||||||
|
i18n,
|
||||||
|
hasLocalVideo,
|
||||||
|
hasLocalAudio,
|
||||||
|
groupMembers?.length,
|
||||||
|
toggleParticipants,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useMutedToast(hasLocalAudio, i18n);
|
||||||
|
useWasInitiallyMutedToast(hasLocalAudio, i18n);
|
||||||
|
useOutgoingRingToast(isRingButtonVisible, outgoingRing, i18n);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FocusTrap>
|
<FocusTrap>
|
||||||
<div className="module-calling__container">
|
<div className="module-calling__container">
|
||||||
|
@ -222,8 +253,6 @@ export function CallingLobby({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGroupCall={isGroupCall}
|
isGroupCall={isGroupCall}
|
||||||
participantCount={peekedParticipants.length}
|
participantCount={peekedParticipants.length}
|
||||||
showParticipantsList={showParticipantsList}
|
|
||||||
toggleParticipants={toggleParticipants}
|
|
||||||
toggleSettings={toggleSettings}
|
toggleSettings={toggleSettings}
|
||||||
onCancel={onCallCanceled}
|
onCancel={onCallCanceled}
|
||||||
/>
|
/>
|
||||||
|
@ -249,7 +278,12 @@ export function CallingLobby({
|
||||||
{i18n('icu:calling__your-video-is-off')}
|
{i18n('icu:calling__your-video-is-off')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="module-calling__buttons module-calling__buttons--inline">
|
<div className="CallControls">
|
||||||
|
<div className="CallControls__InfoDisplay">
|
||||||
|
<div className="CallControls__CallTitle">{conversation.title}</div>
|
||||||
|
<div className="CallControls__Status">{callStatus}</div>
|
||||||
|
</div>
|
||||||
|
<div className="CallControls__ButtonContainer">
|
||||||
<CallingButton
|
<CallingButton
|
||||||
buttonType={videoButtonType}
|
buttonType={videoButtonType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -270,7 +304,7 @@ export function CallingLobby({
|
||||||
tooltipDirection={TooltipPlacement.Top}
|
tooltipDirection={TooltipPlacement.Top}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="CallControls__JoinLeaveButtonContainer">
|
||||||
<CallingLobbyJoinButton
|
<CallingLobbyJoinButton
|
||||||
disabled={!canJoin}
|
disabled={!canJoin}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -281,6 +315,8 @@ export function CallingLobby({
|
||||||
variant={callingLobbyJoinButtonVariant}
|
variant={callingLobbyJoinButtonVariant}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -313,3 +349,51 @@ function useWasInitiallyMutedToast(
|
||||||
}
|
}
|
||||||
}, [hideToast, wasInitiallyMuted, hasLocalAudio]);
|
}, [hideToast, wasInitiallyMuted, hasLocalAudio]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useOutgoingRingToast(
|
||||||
|
isRingButtonVisible: boolean,
|
||||||
|
outgoingRing: boolean,
|
||||||
|
i18n: LocalizerType
|
||||||
|
): void {
|
||||||
|
const [previousOutgoingRing, setPreviousOutgoingRing] = React.useState<
|
||||||
|
undefined | boolean
|
||||||
|
>(undefined);
|
||||||
|
const { showToast, hideToast } = useCallingToasts();
|
||||||
|
const RINGING_TOAST_KEY = 'ringing';
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isRingButtonVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviousOutgoingRing(outgoingRing);
|
||||||
|
}, [isRingButtonVisible, outgoingRing]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isRingButtonVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
previousOutgoingRing !== undefined &&
|
||||||
|
outgoingRing !== previousOutgoingRing
|
||||||
|
) {
|
||||||
|
hideToast(RINGING_TOAST_KEY);
|
||||||
|
showToast({
|
||||||
|
key: RINGING_TOAST_KEY,
|
||||||
|
content: outgoingRing
|
||||||
|
? i18n('icu:CallControls__RingingToast--ringing-on')
|
||||||
|
: i18n('icu:CallControls__RingingToast--ringing-off'),
|
||||||
|
autoClose: true,
|
||||||
|
dismissable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isRingButtonVisible,
|
||||||
|
outgoingRing,
|
||||||
|
previousOutgoingRing,
|
||||||
|
hideToast,
|
||||||
|
showToast,
|
||||||
|
i18n,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
|
@ -9,9 +9,6 @@ import type { LocalizerType } from '../types/Util';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
const PADDING_HORIZONTAL = 48;
|
|
||||||
const PADDING_VERTICAL = 12;
|
|
||||||
|
|
||||||
export enum CallingLobbyJoinButtonVariant {
|
export enum CallingLobbyJoinButtonVariant {
|
||||||
CallIsFull = 'CallIsFull',
|
CallIsFull = 'CallIsFull',
|
||||||
Join = 'Join',
|
Join = 'Join',
|
||||||
|
@ -47,18 +44,24 @@ export function CallingLobbyJoinButton({
|
||||||
|
|
||||||
const childrenByVariant: Record<CallingLobbyJoinButtonVariant, ReactChild> = {
|
const childrenByVariant: Record<CallingLobbyJoinButtonVariant, ReactChild> = {
|
||||||
[CallingLobbyJoinButtonVariant.CallIsFull]: i18n(
|
[CallingLobbyJoinButtonVariant.CallIsFull]: i18n(
|
||||||
'icu:calling__call-is-full'
|
'icu:CallingLobbyJoinButton--call-full'
|
||||||
|
),
|
||||||
|
[CallingLobbyJoinButtonVariant.Loading]: (
|
||||||
|
<Spinner size="18px" svgSize="small" />
|
||||||
|
),
|
||||||
|
[CallingLobbyJoinButtonVariant.Join]: i18n(
|
||||||
|
'icu:CallingLobbyJoinButton--join'
|
||||||
|
),
|
||||||
|
[CallingLobbyJoinButtonVariant.Start]: i18n(
|
||||||
|
'icu:CallingLobbyJoinButton--start'
|
||||||
),
|
),
|
||||||
[CallingLobbyJoinButtonVariant.Loading]: <Spinner svgSize="small" />,
|
|
||||||
[CallingLobbyJoinButtonVariant.Join]: i18n('icu:calling__join'),
|
|
||||||
[CallingLobbyJoinButtonVariant.Start]: i18n('icu:calling__start'),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Boolean(width && height) && (
|
{Boolean(width && height) && (
|
||||||
<Button
|
<Button
|
||||||
className="module-CallingLobbyJoinButton"
|
className="CallingLobbyJoinButton CallControls__JoinLeaveButton"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ width, height }}
|
style={{ width, height }}
|
||||||
|
@ -79,7 +82,7 @@ export function CallingLobbyJoinButton({
|
||||||
{Object.values(CallingLobbyJoinButtonVariant).map(candidateVariant => (
|
{Object.values(CallingLobbyJoinButtonVariant).map(candidateVariant => (
|
||||||
<Button
|
<Button
|
||||||
key={candidateVariant}
|
key={candidateVariant}
|
||||||
className="module-CallingLobbyJoinButton"
|
className="CallingLobbyJoinButton CallControls__JoinLeaveButton"
|
||||||
variant={ButtonVariant.Calling}
|
variant={ButtonVariant.Calling}
|
||||||
onClick={noop}
|
onClick={noop}
|
||||||
ref={(button: HTMLButtonElement | null) => {
|
ref={(button: HTMLButtonElement | null) => {
|
||||||
|
@ -95,14 +98,10 @@ export function CallingLobbyJoinButton({
|
||||||
// we compute the size, then the font makes the text a bit larger, and
|
// we compute the size, then the font makes the text a bit larger, and
|
||||||
// there's a layout issue.
|
// there's a layout issue.
|
||||||
setWidth((previousWidth = 0) =>
|
setWidth((previousWidth = 0) =>
|
||||||
Math.ceil(
|
Math.ceil(Math.max(previousWidth, variantWidth))
|
||||||
Math.max(previousWidth, variantWidth + PADDING_HORIZONTAL)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
setHeight((previousHeight = 0) =>
|
setHeight((previousHeight = 0) =>
|
||||||
Math.ceil(
|
Math.ceil(Math.max(previousHeight, variantHeight))
|
||||||
Math.max(previousHeight, variantHeight + PADDING_VERTICAL)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -146,16 +146,16 @@ export const CallingParticipantsList = React.memo(
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="module-calling-participants-list__status">
|
||||||
{participant.hasRemoteAudio === false ? (
|
|
||||||
<span className="module-calling-participants-list__muted--audio" />
|
|
||||||
) : null}
|
|
||||||
{participant.hasRemoteVideo === false ? (
|
{participant.hasRemoteVideo === false ? (
|
||||||
<span className="module-calling-participants-list__muted--video" />
|
<span className="module-calling-participants-list__muted--video" />
|
||||||
) : null}
|
) : null}
|
||||||
{participant.presenting ? (
|
{participant.presenting ? (
|
||||||
<span className="module-calling-participants-list__presenting" />
|
<span className="module-calling-participants-list__presenting" />
|
||||||
) : null}
|
) : null}
|
||||||
|
{participant.hasRemoteAudio === false ? (
|
||||||
|
<span className="module-calling-participants-list__muted--audio" />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
|
@ -98,3 +98,35 @@ export function useScreenSharingStoppedToast({
|
||||||
}
|
}
|
||||||
}, [activeCall, previousPresenter, showToast, i18n]);
|
}, [activeCall, previousPresenter, showToast, i18n]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useMutedToast(
|
||||||
|
hasLocalAudio: boolean,
|
||||||
|
i18n: LocalizerType
|
||||||
|
): void {
|
||||||
|
const [previousHasLocalAudio, setPreviousHasLocalAudio] = useState<
|
||||||
|
undefined | boolean
|
||||||
|
>(undefined);
|
||||||
|
const { showToast, hideToast } = useCallingToasts();
|
||||||
|
const MUTED_TOAST_KEY = 'muted';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPreviousHasLocalAudio(hasLocalAudio);
|
||||||
|
}, [hasLocalAudio]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
previousHasLocalAudio !== undefined &&
|
||||||
|
hasLocalAudio !== previousHasLocalAudio
|
||||||
|
) {
|
||||||
|
hideToast(MUTED_TOAST_KEY);
|
||||||
|
showToast({
|
||||||
|
key: MUTED_TOAST_KEY,
|
||||||
|
content: hasLocalAudio
|
||||||
|
? i18n('icu:CallControls__MutedToast--unmuted')
|
||||||
|
: i18n('icu:CallControls__MutedToast--muted'),
|
||||||
|
autoClose: true,
|
||||||
|
dismissable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [hasLocalAudio, previousHasLocalAudio, hideToast, showToast, i18n]);
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ const OVERFLOW_SCROLLED_TO_EDGE_THRESHOLD = 20;
|
||||||
const OVERFLOW_SCROLL_BUTTON_RATIO = 0.75;
|
const OVERFLOW_SCROLL_BUTTON_RATIO = 0.75;
|
||||||
|
|
||||||
// This should be an integer, as sub-pixel widths can cause performance issues.
|
// This should be an integer, as sub-pixel widths can cause performance issues.
|
||||||
export const OVERFLOW_PARTICIPANT_WIDTH = 140;
|
export const OVERFLOW_PARTICIPANT_WIDTH = 107;
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
getFrameBuffer: () => Buffer;
|
getFrameBuffer: () => Buffer;
|
||||||
|
|
Loading…
Reference in a new issue