Calling: Lobby
This commit is contained in:
parent
358ee4ab72
commit
59a181bd30
21 changed files with 1146 additions and 388 deletions
|
@ -1150,6 +1150,34 @@
|
||||||
"message": "Calling",
|
"message": "Calling",
|
||||||
"description": "Header for calling options on the settings screen"
|
"description": "Header for calling options on the settings screen"
|
||||||
},
|
},
|
||||||
|
"calling__start": {
|
||||||
|
"message": "Start Call",
|
||||||
|
"description": "Button label in the call lobby for starting a call"
|
||||||
|
},
|
||||||
|
"calling__join": {
|
||||||
|
"message": "Join Call",
|
||||||
|
"description": "Button label in the call lobby for joining a call"
|
||||||
|
},
|
||||||
|
"calling__button--video-off": {
|
||||||
|
"message": "Turn off camera",
|
||||||
|
"description": "Button tooltip label for turning off the camera"
|
||||||
|
},
|
||||||
|
"calling__button--video-on": {
|
||||||
|
"message": "Turn on camera",
|
||||||
|
"description": "Button tooltip label for turning on the camera"
|
||||||
|
},
|
||||||
|
"calling__button--audio-off": {
|
||||||
|
"message": "Turn off microphone",
|
||||||
|
"description": "Button tooltip label for turning off the microphone"
|
||||||
|
},
|
||||||
|
"calling__button--audio-on": {
|
||||||
|
"message": "Turn on microphone",
|
||||||
|
"description": "Button tooltip label for turning on the microphone"
|
||||||
|
},
|
||||||
|
"calling__your-video-is-off": {
|
||||||
|
"message": "Your video is off",
|
||||||
|
"description": "Label in the calling lobby indicating that your camera is off"
|
||||||
|
},
|
||||||
"alwaysRelayCallsDescription": {
|
"alwaysRelayCallsDescription": {
|
||||||
"message": "Always relay calls",
|
"message": "Always relay calls",
|
||||||
"description": "Description of the always relay calls setting"
|
"description": "Description of the always relay calls setting"
|
||||||
|
@ -2889,6 +2917,10 @@
|
||||||
"message": "Settings",
|
"message": "Settings",
|
||||||
"description": "Title for device selection settings"
|
"description": "Title for device selection settings"
|
||||||
},
|
},
|
||||||
|
"calling__participants": {
|
||||||
|
"message": "Participants",
|
||||||
|
"description": "Title for participants list toggle"
|
||||||
|
},
|
||||||
"calling__pip": {
|
"calling__pip": {
|
||||||
"message": "Picture-in-picture",
|
"message": "Picture-in-picture",
|
||||||
"description": "Title for picture-in-picture toggle"
|
"description": "Title for picture-in-picture toggle"
|
||||||
|
|
1
images/icons/v2/group-solid-24.svg
Normal file
1
images/icons/v2/group-solid-24.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>group-solid-24</title><path d="M10.25,16.25m-2.5-8.5a3.5,3.5,0,1,0,3.5,3.5A3.5,3.5,0,0,0,7.75,7.75Zm3.539,7.027a4.988,4.988,0,0,1-7.078,0A4.735,4.735,0,0,0,1,19.25V20a1,1,0,0,0,1,1H13.5a1,1,0,0,0,1-1v-.75A4.735,4.735,0,0,0,11.289,14.777ZM16.25,2.75a3.5,3.5,0,1,0,3.5,3.5A3.5,3.5,0,0,0,16.25,2.75Zm3.539,7.027a4.989,4.989,0,0,1-7.074.005c-.063.022-.131.033-.193.057a4.675,4.675,0,0,1-.333,3.663A6.294,6.294,0,0,1,15.078,16H22a1,1,0,0,0,1-1v-.75A4.735,4.735,0,0,0,19.789,9.777Z"/></svg>
|
After Width: | Height: | Size: 574 B |
|
@ -5815,6 +5815,65 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module: Calling
|
// Module: Calling
|
||||||
|
.module-calling {
|
||||||
|
&__container {
|
||||||
|
align-items: center;
|
||||||
|
background-color: $color-gray-95;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
color: #ffffff;
|
||||||
|
font-style: normal;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&--header-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 21px;
|
||||||
|
letter-spacing: -0.009em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
padding-top: 32px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__background {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&--blur {
|
||||||
|
backdrop-filter: blur(7px);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
background-color: $color-black-alpha-40;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.module-incoming-call {
|
.module-incoming-call {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -5862,114 +5921,140 @@ button.module-image__border-overlay:focus {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-incoming-call__button--accept-video-as-audio {
|
.module-incoming-call__button {
|
||||||
background-color: $color-gray-45;
|
&--accept-video-as-audio {
|
||||||
|
background-color: $color-gray-45;
|
||||||
|
|
||||||
@include keyboard-mode {
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/video-off-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mouse-mode {
|
&--accept-video {
|
||||||
&:hover {
|
background-color: $color-accent-green;
|
||||||
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
@include color-svg('../images/icons/v2/video-solid-24.svg', $color-white);
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
&--accept-audio {
|
||||||
@include color-svg(
|
background-color: $color-accent-green;
|
||||||
'../images/icons/v2/video-off-solid-24.svg',
|
|
||||||
$color-white
|
@include keyboard-mode {
|
||||||
);
|
&:focus {
|
||||||
height: 24px;
|
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
||||||
width: 24px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/phone-right-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--decline {
|
||||||
|
background-color: $color-accent-red;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
@include color-svg('../images/icons/v2/phone-down-24.svg', $color-white);
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-incoming-call__button--accept-video {
|
.module-incoming-call__button,
|
||||||
background-color: $color-accent-green;
|
.module-calling-button__icon {
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include mouse-mode {
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
@include color-svg('../images/icons/v2/video-solid-24.svg', $color-white);
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-incoming-call__button--accept-audio {
|
|
||||||
background-color: $color-accent-green;
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include mouse-mode {
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/phone-right-solid-24.svg',
|
|
||||||
$color-white
|
|
||||||
);
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-incoming-call__button--decline {
|
|
||||||
background-color: $color-accent-red;
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include mouse-mode {
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
@include color-svg('../images/icons/v2/phone-down-24.svg', $color-white);
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-incoming-call__icon,
|
|
||||||
.module-ongoing-call__icon {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-left: 24px;
|
margin-left: 12px;
|
||||||
|
margin-right: 12px;
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call__icon {
|
.module-calling-button {
|
||||||
|
&__participants {
|
||||||
|
@include color-svg('../images/icons/v2/group-solid-24.svg', $color-white);
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__settings {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/settings-solid-16.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__pip {
|
||||||
|
@include color-svg('../images/icons/v2/collapse-24.svg', $color-white);
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-calling-button__icon {
|
||||||
border-radius: 56px;
|
border-radius: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
width: 56px;
|
width: 56px;
|
||||||
|
@ -6036,79 +6121,6 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call,
|
|
||||||
.module-call-need-permission-screen {
|
|
||||||
background-color: $color-gray-95;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__remote-video-enabled {
|
|
||||||
background-color: $color-gray-95;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__remote-video-disabled {
|
|
||||||
background-color: $color-gray-95;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__local-video {
|
|
||||||
transform: rotateY(180deg);
|
|
||||||
background-color: transparent;
|
|
||||||
bottom: 160px;
|
|
||||||
height: 152px;
|
|
||||||
position: absolute;
|
|
||||||
right: 32px;
|
|
||||||
width: 210px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__header {
|
|
||||||
background: linear-gradient($color-black-alpha-40, transparent);
|
|
||||||
padding-bottom: 24px;
|
|
||||||
padding-top: 24px;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
font-style: normal;
|
|
||||||
color: #ffffff;
|
|
||||||
text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__header-name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 21px;
|
|
||||||
letter-spacing: -0.009em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__header-message {
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 18px;
|
|
||||||
letter-spacing: -0.0025em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__actions {
|
|
||||||
background: linear-gradient(transparent, $color-black-alpha-40);
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding-bottom: 32px;
|
|
||||||
padding-top: 32px;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes module-ongoing-call__controls--fade-in {
|
@keyframes module-ongoing-call__controls--fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -6127,48 +6139,129 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call__controls--fadeIn {
|
.module-ongoing-call {
|
||||||
animation: {
|
&__remote-video-enabled {
|
||||||
name: module-ongoing-call__controls--fade-in;
|
background-color: $color-gray-95;
|
||||||
duration: 400ms;
|
height: 100vh;
|
||||||
timing-function: $ease-out-expo;
|
width: 100%;
|
||||||
fill-mode: forwards;
|
}
|
||||||
|
|
||||||
|
&__remote-video-disabled {
|
||||||
|
background-color: $color-gray-95;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__local-video {
|
||||||
|
background-color: transparent;
|
||||||
|
bottom: 160px;
|
||||||
|
height: 152px;
|
||||||
|
position: absolute;
|
||||||
|
right: 32px;
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header-message {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: -0.0025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
background: linear-gradient(transparent, $color-black-alpha-40);
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
padding-top: 32px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__controls--fadeIn {
|
||||||
|
animation: {
|
||||||
|
name: module-ongoing-call__controls--fade-in;
|
||||||
|
duration: 400ms;
|
||||||
|
timing-function: $ease-out-expo;
|
||||||
|
fill-mode: forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__controls--fadeOut {
|
||||||
|
animation: {
|
||||||
|
name: module-ongoing-call__controls--fade-out;
|
||||||
|
duration: 1200ms;
|
||||||
|
timing-function: $ease-out-expo;
|
||||||
|
fill-mode: forwards;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call__controls--fadeOut {
|
.module-calling-tools {
|
||||||
animation: {
|
display: flex;
|
||||||
name: module-ongoing-call__controls--fade-out;
|
justify-content: flex-end;
|
||||||
duration: 1200ms;
|
|
||||||
timing-function: $ease-out-expo;
|
|
||||||
fill-mode: forwards;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-ongoing-call__settings {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 25px;
|
top: 24px;
|
||||||
right: 65px;
|
width: 100%;
|
||||||
|
|
||||||
&--button {
|
&__button {
|
||||||
@include color-svg(
|
margin-right: 25px;
|
||||||
'../images/icons/v2/settings-solid-16.svg',
|
|
||||||
$color-white
|
|
||||||
);
|
|
||||||
height: 22px;
|
|
||||||
width: 22px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-ongoing-call__pip {
|
.module-calling-lobby {
|
||||||
position: absolute;
|
&__actions {
|
||||||
top: 25px;
|
flex: 0 0 100px;
|
||||||
right: 25px;
|
}
|
||||||
|
|
||||||
&--button {
|
&__button {
|
||||||
@include color-svg('../images/icons/v2/collapse-24.svg', $color-white);
|
margin-left: 8px;
|
||||||
height: 22px;
|
margin-right: 8px;
|
||||||
width: 22px;
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__video {
|
||||||
|
@include font-body-2;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $color-gray-80;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: $color-white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
margin-top: 24px;
|
||||||
|
max-width: 640px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__video-off {
|
||||||
|
&--icon {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/video-off-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
height: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--text {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6203,25 +6296,6 @@ button.module-image__border-overlay:focus {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--background {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--blur {
|
|
||||||
backdrop-filter: blur(7px);
|
|
||||||
backface-visibility: hidden;
|
|
||||||
background-color: $color-black-alpha-40;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--avatar img {
|
&--avatar img {
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
@ -6270,11 +6344,15 @@ button.module-image__border-overlay:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-call-need-permission-screen {
|
.module-call-need-permission-screen {
|
||||||
|
align-items: center;
|
||||||
|
background-color: $color-gray-95;
|
||||||
|
color: $color-gray-05;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
position: relative;
|
||||||
color: $color-gray-05;
|
width: 100%;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
margin: 2em 1em;
|
margin: 2em 1em;
|
||||||
|
@ -9342,6 +9420,52 @@ button.module-image__border-overlay:focus {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-button {
|
||||||
|
&__gray {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
background-color: $color-gray-45;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
color: $color-white;
|
||||||
|
outline: none;
|
||||||
|
padding: 7px 14px;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__green {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
background-color: $color-accent-green;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
color: $color-white;
|
||||||
|
outline: none;
|
||||||
|
padding: 7px 14px;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-background-color {
|
||||||
|
&__default {
|
||||||
|
background-color: $color-black-alpha-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $color, $value in $conversation-colors {
|
||||||
|
&__#{$color} {
|
||||||
|
background-color: $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Third-party module: react-tooltip-lite */
|
/* Third-party module: react-tooltip-lite */
|
||||||
|
|
||||||
.react-tooltip-lite {
|
.react-tooltip-lite {
|
||||||
|
|
36
ts/components/CallBackgroundBlur.tsx
Normal file
36
ts/components/CallBackgroundBlur.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ColorType } from '../types/Colors';
|
||||||
|
|
||||||
|
export type PropsType = {
|
||||||
|
avatarPath?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
color?: ColorType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CallBackgroundBlur = ({
|
||||||
|
avatarPath,
|
||||||
|
children,
|
||||||
|
color,
|
||||||
|
}: PropsType): JSX.Element => {
|
||||||
|
const backgroundProps = avatarPath
|
||||||
|
? {
|
||||||
|
style: {
|
||||||
|
backgroundImage: `url("${avatarPath}")`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
className: classNames(
|
||||||
|
'module-calling__background',
|
||||||
|
`module-background-color__${color || 'default'}`
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="module-calling__background" {...backgroundProps} />
|
||||||
|
<div className="module-calling__background--blur" />
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -15,6 +15,7 @@ const callDetails = {
|
||||||
isIncoming: true,
|
isIncoming: true,
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
|
|
||||||
|
id: '3051234567',
|
||||||
avatarPath: undefined,
|
avatarPath: undefined,
|
||||||
color: 'ultramarine' as ColorType,
|
color: 'ultramarine' as ColorType,
|
||||||
title: 'Rick Sanchez',
|
title: 'Rick Sanchez',
|
||||||
|
@ -27,6 +28,7 @@ const defaultProps = {
|
||||||
acceptCall: action('accept-call'),
|
acceptCall: action('accept-call'),
|
||||||
callDetails,
|
callDetails,
|
||||||
callState: CallState.Accepted,
|
callState: CallState.Accepted,
|
||||||
|
cancelCall: action('cancel-call'),
|
||||||
closeNeedPermissionScreen: action('close-need-permission-screen'),
|
closeNeedPermissionScreen: action('close-need-permission-screen'),
|
||||||
declineCall: action('decline-call'),
|
declineCall: action('decline-call'),
|
||||||
hangUp: action('hang-up'),
|
hangUp: action('hang-up'),
|
||||||
|
@ -41,6 +43,8 @@ const defaultProps = {
|
||||||
setLocalVideo: action('set-local-video'),
|
setLocalVideo: action('set-local-video'),
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
settingsDialogOpen: false,
|
settingsDialogOpen: false,
|
||||||
|
startCall: action('start-call'),
|
||||||
|
toggleParticipants: action('toggle-participants'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CallingPip } from './CallingPip';
|
import { CallingPip } from './CallingPip';
|
||||||
import { CallNeedPermissionScreen } from './CallNeedPermissionScreen';
|
import { CallNeedPermissionScreen } from './CallNeedPermissionScreen';
|
||||||
|
import { CallingLobby } from './CallingLobby';
|
||||||
import { CallScreen, PropsType as CallScreenPropsType } from './CallScreen';
|
import { CallScreen, PropsType as CallScreenPropsType } from './CallScreen';
|
||||||
import {
|
import {
|
||||||
IncomingCallBar,
|
IncomingCallBar,
|
||||||
PropsType as IncomingCallBarPropsType,
|
PropsType as IncomingCallBarPropsType,
|
||||||
} from './IncomingCallBar';
|
} from './IncomingCallBar';
|
||||||
import { CallState, CallEndedReason } from '../types/Calling';
|
import { CallState, CallEndedReason } from '../types/Calling';
|
||||||
import { CallDetailsType } from '../state/ducks/calling';
|
import { CallDetailsType, OutgoingCallType } from '../state/ducks/calling';
|
||||||
|
|
||||||
type CallManagerPropsType = {
|
type CallManagerPropsType = {
|
||||||
callDetails?: CallDetailsType;
|
callDetails?: CallDetailsType;
|
||||||
callState?: CallState;
|
|
||||||
callEndedReason?: CallEndedReason;
|
callEndedReason?: CallEndedReason;
|
||||||
|
callState?: CallState;
|
||||||
|
cancelCall: () => void;
|
||||||
pip: boolean;
|
pip: boolean;
|
||||||
closeNeedPermissionScreen: () => void;
|
closeNeedPermissionScreen: () => void;
|
||||||
renderDeviceSelection: () => JSX.Element;
|
renderDeviceSelection: () => JSX.Element;
|
||||||
settingsDialogOpen: boolean;
|
settingsDialogOpen: boolean;
|
||||||
|
startCall: (payload: OutgoingCallType) => void;
|
||||||
|
toggleParticipants: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropsType = IncomingCallBarPropsType &
|
type PropsType = IncomingCallBarPropsType &
|
||||||
|
@ -28,6 +32,7 @@ export const CallManager = ({
|
||||||
callDetails,
|
callDetails,
|
||||||
callState,
|
callState,
|
||||||
callEndedReason,
|
callEndedReason,
|
||||||
|
cancelCall,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
declineCall,
|
declineCall,
|
||||||
hangUp,
|
hangUp,
|
||||||
|
@ -42,10 +47,12 @@ export const CallManager = ({
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
settingsDialogOpen,
|
settingsDialogOpen,
|
||||||
|
startCall,
|
||||||
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
}: PropsType): JSX.Element | null => {
|
}: PropsType): JSX.Element | null => {
|
||||||
if (!callDetails || !callState) {
|
if (!callDetails) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const incoming = callDetails.isIncoming;
|
const incoming = callDetails.isIncoming;
|
||||||
|
@ -68,6 +75,31 @@ export const CallManager = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!callState) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CallingLobby
|
||||||
|
callDetails={callDetails}
|
||||||
|
callState={callState}
|
||||||
|
hasLocalAudio={hasLocalAudio}
|
||||||
|
hasLocalVideo={hasLocalVideo}
|
||||||
|
i18n={i18n}
|
||||||
|
isGroupCall={false}
|
||||||
|
onCallCanceled={cancelCall}
|
||||||
|
onJoinCall={() => {
|
||||||
|
startCall({ callDetails });
|
||||||
|
}}
|
||||||
|
setLocalPreview={setLocalPreview}
|
||||||
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalVideo={setLocalVideo}
|
||||||
|
toggleParticipants={toggleParticipants}
|
||||||
|
toggleSettings={toggleSettings}
|
||||||
|
/>
|
||||||
|
{settingsDialogOpen && renderDeviceSelection()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (outgoing || ongoing) {
|
if (outgoing || ongoing) {
|
||||||
if (pip) {
|
if (pip) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
import { CallState } from '../types/Calling';
|
import { CallState } from '../types/Calling';
|
||||||
import { ColorType } from '../types/Colors';
|
import { ColorType } from '../types/Colors';
|
||||||
import { CallScreen } from './CallScreen';
|
import { CallScreen, PropsType } from './CallScreen';
|
||||||
import { setup as setupI18n } from '../../js/modules/i18n';
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ const callDetails = {
|
||||||
isIncoming: true,
|
isIncoming: true,
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
|
|
||||||
|
id: '3051234567',
|
||||||
avatarPath: undefined,
|
avatarPath: undefined,
|
||||||
color: 'ultramarine' as ColorType,
|
color: 'ultramarine' as ColorType,
|
||||||
title: 'Rick Sanchez',
|
title: 'Rick Sanchez',
|
||||||
|
@ -24,13 +25,20 @@ const callDetails = {
|
||||||
profileName: 'Rick Sanchez',
|
profileName: 'Rick Sanchez',
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
callDetails,
|
callDetails,
|
||||||
callState: CallState.Accepted,
|
callState: select(
|
||||||
|
'callState',
|
||||||
|
CallState,
|
||||||
|
overrideProps.callState || CallState.Accepted
|
||||||
|
),
|
||||||
hangUp: action('hang-up'),
|
hangUp: action('hang-up'),
|
||||||
hasLocalAudio: true,
|
hasLocalAudio: boolean('hasLocalAudio', overrideProps.hasLocalAudio || false),
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
||||||
hasRemoteVideo: true,
|
hasRemoteVideo: boolean(
|
||||||
|
'hasRemoteVideo',
|
||||||
|
overrideProps.hasRemoteVideo || false
|
||||||
|
),
|
||||||
i18n,
|
i18n,
|
||||||
setLocalAudio: action('set-local-audio'),
|
setLocalAudio: action('set-local-audio'),
|
||||||
setLocalPreview: action('set-local-preview'),
|
setLocalPreview: action('set-local-preview'),
|
||||||
|
@ -38,82 +46,38 @@ const defaultProps = {
|
||||||
setRendererCanvas: action('set-renderer-canvas'),
|
setRendererCanvas: action('set-renderer-canvas'),
|
||||||
togglePip: action('toggle-pip'),
|
togglePip: action('toggle-pip'),
|
||||||
toggleSettings: action('toggle-settings'),
|
toggleSettings: action('toggle-settings'),
|
||||||
};
|
});
|
||||||
|
|
||||||
const permutations = [
|
const story = storiesOf('Components/CallScreen', module);
|
||||||
{
|
|
||||||
title: 'Call Screen',
|
|
||||||
props: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Call Screen (Pre-ring)',
|
|
||||||
props: {
|
|
||||||
callState: CallState.Prering,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Call Screen (Ringing)',
|
|
||||||
props: {
|
|
||||||
callState: CallState.Ringing,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Call Screen (Reconnecting)',
|
|
||||||
props: {
|
|
||||||
callState: CallState.Reconnecting,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Call Screen (Ended)',
|
|
||||||
props: {
|
|
||||||
callState: CallState.Ended,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Calling (no local audio)',
|
|
||||||
props: {
|
|
||||||
...defaultProps,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Calling (no local video)',
|
|
||||||
props: {
|
|
||||||
...defaultProps,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Calling (no remote video)',
|
|
||||||
props: {
|
|
||||||
...defaultProps,
|
|
||||||
hasRemoteVideo: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
storiesOf('Components/CallScreen', module)
|
story.add('Default', () => {
|
||||||
.add('Knobs Playground', () => {
|
return <CallScreen {...createProps()} />;
|
||||||
const callState = select('callState', CallState, CallState.Accepted);
|
});
|
||||||
const hasLocalAudio = boolean('hasLocalAudio', true);
|
|
||||||
const hasLocalVideo = boolean('hasLocalVideo', true);
|
|
||||||
const hasRemoteVideo = boolean('hasRemoteVideo', true);
|
|
||||||
|
|
||||||
return (
|
story.add('Pre-Ring', () => {
|
||||||
<CallScreen
|
return <CallScreen {...createProps({ callState: CallState.Prering })} />;
|
||||||
{...defaultProps}
|
});
|
||||||
callState={callState}
|
|
||||||
hasLocalAudio={hasLocalAudio}
|
story.add('Ringing', () => {
|
||||||
hasLocalVideo={hasLocalVideo}
|
return <CallScreen {...createProps({ callState: CallState.Ringing })} />;
|
||||||
hasRemoteVideo={hasRemoteVideo}
|
});
|
||||||
/>
|
|
||||||
);
|
story.add('Reconnecting', () => {
|
||||||
})
|
return <CallScreen {...createProps({ callState: CallState.Reconnecting })} />;
|
||||||
.add('Iterations', () => {
|
});
|
||||||
return permutations.map(({ props, title }) => (
|
|
||||||
<>
|
story.add('Ended', () => {
|
||||||
<h3>{title}</h3>
|
return <CallScreen {...createProps({ callState: CallState.Ended })} />;
|
||||||
<CallScreen {...defaultProps} {...props} />
|
});
|
||||||
</>
|
|
||||||
));
|
story.add('hasLocalAudio', () => {
|
||||||
});
|
return <CallScreen {...createProps({ hasLocalAudio: true })} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('hasLocalVideo', () => {
|
||||||
|
return <CallScreen {...createProps({ hasLocalVideo: true })} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('hasRemoteVideo', () => {
|
||||||
|
return <CallScreen {...createProps({ hasRemoteVideo: true })} />;
|
||||||
|
});
|
||||||
|
|
|
@ -9,30 +9,10 @@ import {
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
|
import { CallingButton, CallingButtonType } from './CallingButton';
|
||||||
import { CallState } from '../types/Calling';
|
import { CallState } from '../types/Calling';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
type CallingButtonProps = {
|
|
||||||
classNameSuffix: string;
|
|
||||||
onClick: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CallingButton = ({
|
|
||||||
classNameSuffix,
|
|
||||||
onClick,
|
|
||||||
}: CallingButtonProps): JSX.Element => {
|
|
||||||
const className = classNames(
|
|
||||||
'module-ongoing-call__icon',
|
|
||||||
`module-ongoing-call__icon${classNameSuffix}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button type="button" className={className} onClick={onClick}>
|
|
||||||
<div />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
callDetails?: CallDetailsType;
|
callDetails?: CallDetailsType;
|
||||||
callState?: CallState;
|
callState?: CallState;
|
||||||
|
@ -137,10 +117,10 @@ export class CallScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
let eventHandled = false;
|
let eventHandled = false;
|
||||||
|
|
||||||
if (event.key === 'V') {
|
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
||||||
this.toggleVideo();
|
this.toggleVideo();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
} else if (event.key === 'M') {
|
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
||||||
this.toggleAudio();
|
this.toggleAudio();
|
||||||
eventHandled = true;
|
eventHandled = true;
|
||||||
}
|
}
|
||||||
|
@ -225,42 +205,41 @@ export class CallScreen extends React.Component<PropsType, StateType> {
|
||||||
!showControls && !isAudioOnly && callState === CallState.Accepted,
|
!showControls && !isAudioOnly && callState === CallState.Accepted,
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleAudioSuffix = hasLocalAudio
|
const videoButtonType = hasLocalVideo
|
||||||
? '--audio--enabled'
|
? CallingButtonType.VIDEO_ON
|
||||||
: '--audio--disabled';
|
: CallingButtonType.VIDEO_OFF;
|
||||||
const toggleVideoSuffix = hasLocalVideo
|
const audioButtonType = hasLocalAudio
|
||||||
? '--video--enabled'
|
? CallingButtonType.AUDIO_ON
|
||||||
: '--video--disabled';
|
: CallingButtonType.AUDIO_OFF;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="module-ongoing-call"
|
className="module-calling__container"
|
||||||
onMouseMove={this.showControls}
|
onMouseMove={this.showControls}
|
||||||
role="group"
|
role="group"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
'module-calling__header',
|
||||||
'module-ongoing-call__header',
|
'module-ongoing-call__header',
|
||||||
controlsFadeClass
|
controlsFadeClass
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="module-ongoing-call__header-name">
|
<div className="module-calling__header--header-name">
|
||||||
{callDetails.title}
|
{callDetails.title}
|
||||||
</div>
|
</div>
|
||||||
{this.renderMessage(callState)}
|
{this.renderMessage(callState)}
|
||||||
<div className="module-ongoing-call__settings">
|
<div className="module-calling-tools">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={i18n('callingDeviceSelection__settings')}
|
aria-label={i18n('callingDeviceSelection__settings')}
|
||||||
className="module-ongoing-call__settings--button"
|
className="module-calling-tools__button module-calling-button__settings"
|
||||||
onClick={toggleSettings}
|
onClick={toggleSettings}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="module-ongoing-call__pip">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={i18n('calling__pip')}
|
aria-label={i18n('calling__pip')}
|
||||||
className="module-ongoing-call__pip--button"
|
className="module-calling-tools__button module-calling-button__pip"
|
||||||
onClick={togglePip}
|
onClick={togglePip}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,18 +255,24 @@ export class CallScreen extends React.Component<PropsType, StateType> {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CallingButton
|
<CallingButton
|
||||||
classNameSuffix={toggleVideoSuffix}
|
buttonType={videoButtonType}
|
||||||
|
i18n={i18n}
|
||||||
onClick={this.toggleVideo}
|
onClick={this.toggleVideo}
|
||||||
|
tooltipDistance={24}
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
<CallingButton
|
||||||
classNameSuffix={toggleAudioSuffix}
|
buttonType={audioButtonType}
|
||||||
|
i18n={i18n}
|
||||||
onClick={this.toggleAudio}
|
onClick={this.toggleAudio}
|
||||||
|
tooltipDistance={24}
|
||||||
/>
|
/>
|
||||||
<CallingButton
|
<CallingButton
|
||||||
classNameSuffix="--hangup"
|
buttonType={CallingButtonType.HANG_UP}
|
||||||
|
i18n={i18n}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
hangUp({ callId: callDetails.callId });
|
hangUp({ callId: callDetails.callId });
|
||||||
}}
|
}}
|
||||||
|
tooltipDistance={24}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
76
ts/components/CallingButton.stories.tsx
Normal file
76
ts/components/CallingButton.stories.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { number, select } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CallingButton,
|
||||||
|
CallingButtonType,
|
||||||
|
PropsType,
|
||||||
|
TooltipDirection,
|
||||||
|
} from './CallingButton';
|
||||||
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
|
buttonType: select(
|
||||||
|
'buttonType',
|
||||||
|
CallingButtonType,
|
||||||
|
overrideProps.buttonType || CallingButtonType.HANG_UP
|
||||||
|
),
|
||||||
|
i18n,
|
||||||
|
onClick: action('on-click'),
|
||||||
|
tooltipDirection: select(
|
||||||
|
'tooltipDirection',
|
||||||
|
TooltipDirection,
|
||||||
|
overrideProps.tooltipDirection || TooltipDirection.DOWN
|
||||||
|
),
|
||||||
|
tooltipDistance: number(
|
||||||
|
'tooltipDistance',
|
||||||
|
overrideProps.tooltipDistance || 16
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const story = storiesOf('Components/CallingButton', module);
|
||||||
|
|
||||||
|
story.add('Default', () => {
|
||||||
|
const props = createProps();
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Audio On', () => {
|
||||||
|
const props = createProps({
|
||||||
|
buttonType: CallingButtonType.AUDIO_ON,
|
||||||
|
});
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Audio Off', () => {
|
||||||
|
const props = createProps({
|
||||||
|
buttonType: CallingButtonType.AUDIO_OFF,
|
||||||
|
});
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Video On', () => {
|
||||||
|
const props = createProps({
|
||||||
|
buttonType: CallingButtonType.VIDEO_ON,
|
||||||
|
});
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Video Off', () => {
|
||||||
|
const props = createProps({
|
||||||
|
buttonType: CallingButtonType.VIDEO_OFF,
|
||||||
|
});
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Tooltip right', () => {
|
||||||
|
const props = createProps({
|
||||||
|
tooltipDirection: TooltipDirection.RIGHT,
|
||||||
|
});
|
||||||
|
return <CallingButton {...props} />;
|
||||||
|
});
|
76
ts/components/CallingButton.tsx
Normal file
76
ts/components/CallingButton.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Tooltip from 'react-tooltip-lite';
|
||||||
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
|
export enum TooltipDirection {
|
||||||
|
UP = 'up',
|
||||||
|
RIGHT = 'right',
|
||||||
|
DOWN = 'down',
|
||||||
|
LEFT = 'left',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CallingButtonType {
|
||||||
|
AUDIO_OFF = 'AUDIO_OFF',
|
||||||
|
AUDIO_ON = 'AUDIO_ON',
|
||||||
|
HANG_UP = 'HANG_UP',
|
||||||
|
VIDEO_OFF = 'VIDEO_OFF',
|
||||||
|
VIDEO_ON = 'VIDEO_ON',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PropsType = {
|
||||||
|
buttonType: CallingButtonType;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
onClick: () => void;
|
||||||
|
tooltipDirection?: TooltipDirection;
|
||||||
|
tooltipDistance?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CallingButton = ({
|
||||||
|
buttonType,
|
||||||
|
i18n,
|
||||||
|
onClick,
|
||||||
|
tooltipDirection = TooltipDirection.DOWN,
|
||||||
|
tooltipDistance = 16,
|
||||||
|
}: PropsType): JSX.Element => {
|
||||||
|
let classNameSuffix = '';
|
||||||
|
let tooltipContent = '';
|
||||||
|
if (buttonType === CallingButtonType.AUDIO_OFF) {
|
||||||
|
classNameSuffix = 'audio--disabled';
|
||||||
|
tooltipContent = i18n('calling__button--audio-on');
|
||||||
|
} else if (buttonType === CallingButtonType.AUDIO_ON) {
|
||||||
|
classNameSuffix = 'audio--enabled';
|
||||||
|
tooltipContent = i18n('calling__button--audio-off');
|
||||||
|
} else if (buttonType === CallingButtonType.VIDEO_OFF) {
|
||||||
|
classNameSuffix = 'video--disabled';
|
||||||
|
tooltipContent = i18n('calling__button--video-on');
|
||||||
|
} else if (buttonType === CallingButtonType.VIDEO_ON) {
|
||||||
|
classNameSuffix = 'video--enabled';
|
||||||
|
tooltipContent = i18n('calling__button--video-off');
|
||||||
|
} else if (buttonType === CallingButtonType.HANG_UP) {
|
||||||
|
classNameSuffix = 'hangup';
|
||||||
|
tooltipContent = i18n('calling__hangup');
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = classNames(
|
||||||
|
'module-calling-button__icon',
|
||||||
|
`module-calling-button__icon--${classNameSuffix}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-label={tooltipContent}
|
||||||
|
type="button"
|
||||||
|
className={className}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
arrowSize={6}
|
||||||
|
content={tooltipContent}
|
||||||
|
direction={tooltipDirection}
|
||||||
|
distance={tooltipDistance}
|
||||||
|
hoverDelay={0}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
59
ts/components/CallingLobby.stories.tsx
Normal file
59
ts/components/CallingLobby.stories.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { boolean } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import { ColorType } from '../types/Colors';
|
||||||
|
import { CallingLobby, PropsType } from './CallingLobby';
|
||||||
|
import { setup as setupI18n } from '../../js/modules/i18n';
|
||||||
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
const callDetails = {
|
||||||
|
callId: 0,
|
||||||
|
isIncoming: true,
|
||||||
|
isVideoCall: true,
|
||||||
|
|
||||||
|
id: '3051234567',
|
||||||
|
avatarPath: undefined,
|
||||||
|
color: 'ultramarine' as ColorType,
|
||||||
|
title: 'Rick Sanchez',
|
||||||
|
name: 'Rick Sanchez',
|
||||||
|
phoneNumber: '3051234567',
|
||||||
|
profileName: 'Rick Sanchez',
|
||||||
|
};
|
||||||
|
|
||||||
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
|
callDetails,
|
||||||
|
hasLocalAudio: boolean('hasLocalAudio', overrideProps.hasLocalAudio || false),
|
||||||
|
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
||||||
|
i18n,
|
||||||
|
isGroupCall: boolean('isGroupCall', overrideProps.isGroupCall || false),
|
||||||
|
onCallCanceled: action('on-call-canceled'),
|
||||||
|
onJoinCall: action('on-join-call'),
|
||||||
|
setLocalAudio: action('set-local-audio'),
|
||||||
|
setLocalPreview: action('set-local-preview'),
|
||||||
|
setLocalVideo: action('set-local-video'),
|
||||||
|
toggleParticipants: action('toggle-participants'),
|
||||||
|
toggleSettings: action('toggle-settings'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const story = storiesOf('Components/CallingLobby', module);
|
||||||
|
|
||||||
|
story.add('Default', () => {
|
||||||
|
const props = createProps();
|
||||||
|
return <CallingLobby {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Local Video', () => {
|
||||||
|
const props = createProps({
|
||||||
|
hasLocalVideo: true,
|
||||||
|
});
|
||||||
|
return <CallingLobby {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Group Call', () => {
|
||||||
|
const props = createProps({ isGroupCall: true });
|
||||||
|
return <CallingLobby {...props} />;
|
||||||
|
});
|
181
ts/components/CallingLobby.tsx
Normal file
181
ts/components/CallingLobby.tsx
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
CallDetailsType,
|
||||||
|
SetLocalAudioType,
|
||||||
|
SetLocalPreviewType,
|
||||||
|
SetLocalVideoType,
|
||||||
|
} from '../state/ducks/calling';
|
||||||
|
import { CallState } from '../types/Calling';
|
||||||
|
import {
|
||||||
|
CallingButton,
|
||||||
|
CallingButtonType,
|
||||||
|
TooltipDirection,
|
||||||
|
} from './CallingButton';
|
||||||
|
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
||||||
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
|
export type PropsType = {
|
||||||
|
callDetails: CallDetailsType;
|
||||||
|
callState?: CallState;
|
||||||
|
hasLocalAudio: boolean;
|
||||||
|
hasLocalVideo: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
isGroupCall: boolean;
|
||||||
|
onCallCanceled: () => void;
|
||||||
|
onJoinCall: () => void;
|
||||||
|
setLocalAudio: (_: SetLocalAudioType) => void;
|
||||||
|
setLocalVideo: (_: SetLocalVideoType) => void;
|
||||||
|
setLocalPreview: (_: SetLocalPreviewType) => void;
|
||||||
|
toggleParticipants: () => void;
|
||||||
|
toggleSettings: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CallingLobby = ({
|
||||||
|
callDetails,
|
||||||
|
hasLocalAudio,
|
||||||
|
hasLocalVideo,
|
||||||
|
i18n,
|
||||||
|
isGroupCall = false,
|
||||||
|
onCallCanceled,
|
||||||
|
onJoinCall,
|
||||||
|
setLocalAudio,
|
||||||
|
setLocalPreview,
|
||||||
|
setLocalVideo,
|
||||||
|
toggleParticipants,
|
||||||
|
toggleSettings,
|
||||||
|
}: PropsType): JSX.Element => {
|
||||||
|
const localVideoRef = React.useRef(null);
|
||||||
|
|
||||||
|
const toggleAudio = React.useCallback((): void => {
|
||||||
|
if (!callDetails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalAudio({ enabled: !hasLocalAudio });
|
||||||
|
}, [callDetails, hasLocalAudio, setLocalAudio]);
|
||||||
|
|
||||||
|
const toggleVideo = React.useCallback((): void => {
|
||||||
|
if (!callDetails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalVideo({ enabled: !hasLocalVideo });
|
||||||
|
}, [callDetails, hasLocalVideo, setLocalVideo]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setLocalPreview({ element: localVideoRef });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setLocalPreview({ element: undefined });
|
||||||
|
};
|
||||||
|
}, [setLocalPreview]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleKeyDown(event: KeyboardEvent): void {
|
||||||
|
let eventHandled = false;
|
||||||
|
|
||||||
|
if (event.shiftKey && (event.key === 'V' || event.key === 'v')) {
|
||||||
|
toggleVideo();
|
||||||
|
eventHandled = true;
|
||||||
|
} else if (event.shiftKey && (event.key === 'M' || event.key === 'm')) {
|
||||||
|
toggleAudio();
|
||||||
|
eventHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventHandled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [toggleVideo, toggleAudio]);
|
||||||
|
|
||||||
|
const videoButtonType = hasLocalVideo
|
||||||
|
? CallingButtonType.VIDEO_ON
|
||||||
|
: CallingButtonType.VIDEO_OFF;
|
||||||
|
const audioButtonType = hasLocalAudio
|
||||||
|
? CallingButtonType.AUDIO_ON
|
||||||
|
: CallingButtonType.AUDIO_OFF;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-calling__container">
|
||||||
|
<div className="module-calling__header">
|
||||||
|
<div className="module-calling__header--header-name">
|
||||||
|
{callDetails.title}
|
||||||
|
</div>
|
||||||
|
<div className="module-calling-tools">
|
||||||
|
{isGroupCall ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={i18n('calling__participants')}
|
||||||
|
className="module-calling-tools__button module-calling-button__participants"
|
||||||
|
onClick={toggleParticipants}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={i18n('callingDeviceSelection__settings')}
|
||||||
|
className="module-calling-tools__button module-calling-button__settings"
|
||||||
|
onClick={toggleSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="module-calling-lobby__video">
|
||||||
|
{hasLocalVideo ? (
|
||||||
|
<video ref={localVideoRef} autoPlay />
|
||||||
|
) : (
|
||||||
|
<CallBackgroundBlur
|
||||||
|
avatarPath={callDetails.avatarPath}
|
||||||
|
color={callDetails.color}
|
||||||
|
>
|
||||||
|
<div className="module-calling-lobby__video-off--icon" />
|
||||||
|
<span className="module-calling-lobby__video-off--text">
|
||||||
|
{i18n('calling__your-video-is-off')}
|
||||||
|
</span>
|
||||||
|
</CallBackgroundBlur>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="module-calling__buttons">
|
||||||
|
<CallingButton
|
||||||
|
buttonType={videoButtonType}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={toggleVideo}
|
||||||
|
tooltipDirection={TooltipDirection.UP}
|
||||||
|
tooltipDistance={24}
|
||||||
|
/>
|
||||||
|
<CallingButton
|
||||||
|
buttonType={audioButtonType}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={toggleAudio}
|
||||||
|
tooltipDirection={TooltipDirection.UP}
|
||||||
|
tooltipDistance={24}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="module-calling-lobby__actions">
|
||||||
|
<button
|
||||||
|
className="module-button__gray module-calling-lobby__button"
|
||||||
|
onClick={onCallCanceled}
|
||||||
|
tabIndex={0}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{i18n('cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="module-button__green module-calling-lobby__button"
|
||||||
|
onClick={onJoinCall}
|
||||||
|
tabIndex={0}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{isGroupCall ? i18n('calling__join') : i18n('calling__start')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -15,6 +15,7 @@ const callDetails = {
|
||||||
isIncoming: true,
|
isIncoming: true,
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
|
|
||||||
|
id: '3051234567',
|
||||||
avatarPath: undefined,
|
avatarPath: undefined,
|
||||||
color: 'ultramarine' as ColorType,
|
color: 'ultramarine' as ColorType,
|
||||||
title: 'Rick Sanchez',
|
title: 'Rick Sanchez',
|
||||||
|
@ -24,7 +25,7 @@ const callDetails = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
callDetails,
|
callDetails: overrideProps.callDetails || callDetails,
|
||||||
hangUp: action('hang-up'),
|
hangUp: action('hang-up'),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
||||||
hasRemoteVideo: boolean(
|
hasRemoteVideo: boolean(
|
||||||
|
@ -43,3 +44,23 @@ story.add('Default', () => {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
return <CallingPip {...props} />;
|
return <CallingPip {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
story.add('Contact (with avatar)', () => {
|
||||||
|
const props = createProps({
|
||||||
|
callDetails: {
|
||||||
|
...callDetails,
|
||||||
|
avatarPath: 'https://www.fillmurray.com/64/64',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return <CallingPip {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
story.add('Contact (no color)', () => {
|
||||||
|
const props = createProps({
|
||||||
|
callDetails: {
|
||||||
|
...callDetails,
|
||||||
|
color: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return <CallingPip {...props} />;
|
||||||
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
|
import { CallBackgroundBlur } from './CallBackgroundBlur';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
function renderAvatar(
|
function renderAvatar(
|
||||||
|
@ -21,35 +22,24 @@ function renderAvatar(
|
||||||
title,
|
title,
|
||||||
} = callDetails;
|
} = callDetails;
|
||||||
|
|
||||||
const backgroundStyle = avatarPath
|
|
||||||
? {
|
|
||||||
backgroundImage: `url("${avatarPath}")`,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
backgroundColor: color,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-calling-pip__video--remote">
|
<div className="module-calling-pip__video--remote">
|
||||||
<div
|
<CallBackgroundBlur avatarPath={avatarPath} color={color}>
|
||||||
className="module-calling-pip__video--background"
|
<div className="module-calling-pip__video--avatar">
|
||||||
style={backgroundStyle}
|
<Avatar
|
||||||
/>
|
avatarPath={avatarPath}
|
||||||
<div className="module-calling-pip__video--blur" />
|
color={color || 'ultramarine'}
|
||||||
<div className="module-calling-pip__video--avatar">
|
noteToSelf={false}
|
||||||
<Avatar
|
conversationType="direct"
|
||||||
avatarPath={avatarPath}
|
i18n={i18n}
|
||||||
color={color || 'ultramarine'}
|
name={name}
|
||||||
noteToSelf={false}
|
phoneNumber={phoneNumber}
|
||||||
conversationType="direct"
|
profileName={profileName}
|
||||||
i18n={i18n}
|
title={title}
|
||||||
name={name}
|
size={52}
|
||||||
phoneNumber={phoneNumber}
|
/>
|
||||||
profileName={profileName}
|
</div>
|
||||||
title={title}
|
</CallBackgroundBlur>
|
||||||
size={52}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ const defaultProps = {
|
||||||
isIncoming: true,
|
isIncoming: true,
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
|
|
||||||
|
id: '3051234567',
|
||||||
avatarPath: undefined,
|
avatarPath: undefined,
|
||||||
contactColor: 'ultramarine' as ColorType,
|
contactColor: 'ultramarine' as ColorType,
|
||||||
name: 'Rick Sanchez',
|
name: 'Rick Sanchez',
|
||||||
|
|
|
@ -31,7 +31,7 @@ const CallButton = ({
|
||||||
}: CallButtonProps): JSX.Element => {
|
}: CallButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`module-incoming-call__icon module-incoming-call__button--${classSuffix}`}
|
className={`module-incoming-call__button module-incoming-call__button--${classSuffix}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
@ -90,11 +90,11 @@ export class CallingClass {
|
||||||
RingRTC.handleLogMessage = this.handleLogMessage.bind(this);
|
RingRTC.handleLogMessage = this.handleLogMessage.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async startOutgoingCall(
|
async startCallingLobby(
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
isVideoCall: boolean
|
isVideoCall: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
window.log.info('CallingClass.startOutgoingCall()');
|
window.log.info('CallingClass.startCallingLobby()');
|
||||||
|
|
||||||
if (!this.uxActions) {
|
if (!this.uxActions) {
|
||||||
window.log.error('Missing uxActions, new call not allowed.');
|
window.log.error('Missing uxActions, new call not allowed.');
|
||||||
|
@ -113,6 +113,75 @@ export class CallingClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.log.info('CallingClass.startCallingLobby(): Getting call settings');
|
||||||
|
|
||||||
|
// Check state after awaiting to debounce call button.
|
||||||
|
if (RingRTC.call && RingRTC.call.state !== CallState.Ended) {
|
||||||
|
window.log.info('Call already in progress, new call not allowed.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationProps = conversation.cachedProps;
|
||||||
|
|
||||||
|
if (!conversationProps) {
|
||||||
|
window.log.error(
|
||||||
|
'CallingClass.startCallingLobby(): No conversation props?'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.log.info('CallingClass.startCallingLobby(): Starting lobby');
|
||||||
|
this.uxActions.showCallLobby({
|
||||||
|
callDetails: {
|
||||||
|
...conversationProps,
|
||||||
|
callId: undefined,
|
||||||
|
isIncoming: false,
|
||||||
|
isVideoCall,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.startDeviceReselectionTimer();
|
||||||
|
this.enableLocalCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCallingLobby(): void {
|
||||||
|
this.disableLocalCamera();
|
||||||
|
this.stopDeviceReselectionTimer();
|
||||||
|
this.lastMediaDeviceSettings = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async startOutgoingCall(
|
||||||
|
conversationId: string,
|
||||||
|
isVideoCall: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
window.log.info('CallingClass.startCallingLobby()');
|
||||||
|
|
||||||
|
if (!this.uxActions) {
|
||||||
|
throw new Error('Redux actions not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
|
||||||
|
if (!conversation) {
|
||||||
|
window.log.error('Could not find conversation, cannot start call');
|
||||||
|
this.stopCallingLobby();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteUserId = this.getRemoteUserIdFromConversation(conversation);
|
||||||
|
if (!remoteUserId || !this.localDeviceId) {
|
||||||
|
window.log.error('Missing identifier, new call not allowed.');
|
||||||
|
this.stopCallingLobby();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const haveMediaPermissions = await this.requestPermissions(isVideoCall);
|
||||||
|
if (!haveMediaPermissions) {
|
||||||
|
window.log.info('Permissions were denied, new call not allowed.');
|
||||||
|
this.stopCallingLobby();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.log.info('CallingClass.startOutgoingCall(): Getting call settings');
|
window.log.info('CallingClass.startOutgoingCall(): Getting call settings');
|
||||||
|
|
||||||
const callSettings = await this.getCallSettings(conversation);
|
const callSettings = await this.getCallSettings(conversation);
|
||||||
|
@ -120,6 +189,7 @@ export class CallingClass {
|
||||||
// Check state after awaiting to debounce call button.
|
// Check state after awaiting to debounce call button.
|
||||||
if (RingRTC.call && RingRTC.call.state !== CallState.Ended) {
|
if (RingRTC.call && RingRTC.call.state !== CallState.Ended) {
|
||||||
window.log.info('Call already in progress, new call not allowed.');
|
window.log.info('Call already in progress, new call not allowed.');
|
||||||
|
this.stopCallingLobby();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,6 +464,14 @@ export class CallingClass {
|
||||||
RingRTC.setAudioOutput(device.index);
|
RingRTC.setAudioOutput(device.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableLocalCamera(): void {
|
||||||
|
this.videoCapturer.enableCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
disableLocalCamera(): void {
|
||||||
|
this.videoCapturer.disable();
|
||||||
|
}
|
||||||
|
|
||||||
async setPreferredCamera(device: string): Promise<void> {
|
async setPreferredCamera(device: string): Promise<void> {
|
||||||
window.log.info('MediaDevice: setPreferredCamera', device);
|
window.log.info('MediaDevice: setPreferredCamera', device);
|
||||||
window.storage.put('preferred-video-input-device', device);
|
window.storage.put('preferred-video-input-device', device);
|
||||||
|
@ -755,12 +833,13 @@ export class CallingClass {
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
call: Call
|
call: Call
|
||||||
): CallDetailsType {
|
): CallDetailsType {
|
||||||
// Does not meet CallDetailsType interface requirements
|
const conversationProps = conversation.cachedProps;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
if (!conversationProps) {
|
||||||
// @ts-ignore
|
throw new Error('getUxCallDetails: No conversation props?');
|
||||||
return {
|
}
|
||||||
...conversation.cachedProps,
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
...conversationProps,
|
||||||
callId: call.callId,
|
callId: call.callId,
|
||||||
isIncoming: call.isIncoming,
|
isIncoming: call.isIncoming,
|
||||||
isVideoCall: call.isVideoCall,
|
isVideoCall: call.isVideoCall,
|
||||||
|
|
|
@ -25,6 +25,7 @@ export type CallDetailsType = {
|
||||||
isIncoming: boolean;
|
isIncoming: boolean;
|
||||||
isVideoCall: boolean;
|
isVideoCall: boolean;
|
||||||
|
|
||||||
|
id: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color?: ColorType;
|
color?: ColorType;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -40,6 +41,7 @@ export type CallingStateType = MediaDeviceSettings & {
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
hasRemoteVideo: boolean;
|
hasRemoteVideo: boolean;
|
||||||
|
participantsList: boolean;
|
||||||
pip: boolean;
|
pip: boolean;
|
||||||
settingsDialogOpen: boolean;
|
settingsDialogOpen: boolean;
|
||||||
};
|
};
|
||||||
|
@ -76,12 +78,12 @@ export type RemoteVideoChangeType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SetLocalAudioType = {
|
export type SetLocalAudioType = {
|
||||||
callId: CallId;
|
callId?: CallId;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SetLocalVideoType = {
|
export type SetLocalVideoType = {
|
||||||
callId: CallId;
|
callId?: CallId;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,6 +98,8 @@ export type SetRendererCanvasType = {
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const ACCEPT_CALL = 'calling/ACCEPT_CALL';
|
const ACCEPT_CALL = 'calling/ACCEPT_CALL';
|
||||||
|
const CANCEL_CALL = 'calling/CANCEL_CALL';
|
||||||
|
const SHOW_CALL_LOBBY = 'calling/SHOW_CALL_LOBBY';
|
||||||
const CALL_STATE_CHANGE = 'calling/CALL_STATE_CHANGE';
|
const CALL_STATE_CHANGE = 'calling/CALL_STATE_CHANGE';
|
||||||
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
||||||
const CHANGE_IO_DEVICE = 'calling/CHANGE_IO_DEVICE';
|
const CHANGE_IO_DEVICE = 'calling/CHANGE_IO_DEVICE';
|
||||||
|
@ -110,6 +114,8 @@ const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
|
||||||
const SET_LOCAL_AUDIO = 'calling/SET_LOCAL_AUDIO';
|
const SET_LOCAL_AUDIO = 'calling/SET_LOCAL_AUDIO';
|
||||||
const SET_LOCAL_VIDEO = 'calling/SET_LOCAL_VIDEO';
|
const SET_LOCAL_VIDEO = 'calling/SET_LOCAL_VIDEO';
|
||||||
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
|
const SET_LOCAL_VIDEO_FULFILLED = 'calling/SET_LOCAL_VIDEO_FULFILLED';
|
||||||
|
const START_CALL = 'calling/START_CALL';
|
||||||
|
const TOGGLE_PARTICIPANTS = 'calling/TOGGLE_PARTICIPANTS';
|
||||||
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
const TOGGLE_PIP = 'calling/TOGGLE_PIP';
|
||||||
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
const TOGGLE_SETTINGS = 'calling/TOGGLE_SETTINGS';
|
||||||
|
|
||||||
|
@ -118,6 +124,15 @@ type AcceptCallActionType = {
|
||||||
payload: AcceptCallType;
|
payload: AcceptCallType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CancelCallActionType = {
|
||||||
|
type: 'calling/CANCEL_CALL';
|
||||||
|
};
|
||||||
|
|
||||||
|
type CallLobbyActionType = {
|
||||||
|
type: 'calling/SHOW_CALL_LOBBY';
|
||||||
|
payload: OutgoingCallType;
|
||||||
|
};
|
||||||
|
|
||||||
type CallStateChangeActionType = {
|
type CallStateChangeActionType = {
|
||||||
type: 'calling/CALL_STATE_CHANGE';
|
type: 'calling/CALL_STATE_CHANGE';
|
||||||
payload: Promise<CallStateChangeType>;
|
payload: Promise<CallStateChangeType>;
|
||||||
|
@ -188,6 +203,14 @@ type SetLocalVideoFulfilledActionType = {
|
||||||
payload: SetLocalVideoType;
|
payload: SetLocalVideoType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StartCallActionType = {
|
||||||
|
type: 'calling/START_CALL';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToggleParticipantsActionType = {
|
||||||
|
type: 'calling/TOGGLE_PARTICIPANTS';
|
||||||
|
};
|
||||||
|
|
||||||
type TogglePipActionType = {
|
type TogglePipActionType = {
|
||||||
type: 'calling/TOGGLE_PIP';
|
type: 'calling/TOGGLE_PIP';
|
||||||
};
|
};
|
||||||
|
@ -198,6 +221,8 @@ type ToggleSettingsActionType = {
|
||||||
|
|
||||||
export type CallingActionType =
|
export type CallingActionType =
|
||||||
| AcceptCallActionType
|
| AcceptCallActionType
|
||||||
|
| CancelCallActionType
|
||||||
|
| CallLobbyActionType
|
||||||
| CallStateChangeActionType
|
| CallStateChangeActionType
|
||||||
| CallStateChangeFulfilledActionType
|
| CallStateChangeFulfilledActionType
|
||||||
| ChangeIODeviceActionType
|
| ChangeIODeviceActionType
|
||||||
|
@ -212,6 +237,8 @@ export type CallingActionType =
|
||||||
| SetLocalAudioActionType
|
| SetLocalAudioActionType
|
||||||
| SetLocalVideoActionType
|
| SetLocalVideoActionType
|
||||||
| SetLocalVideoFulfilledActionType
|
| SetLocalVideoFulfilledActionType
|
||||||
|
| StartCallActionType
|
||||||
|
| ToggleParticipantsActionType
|
||||||
| TogglePipActionType
|
| TogglePipActionType
|
||||||
| ToggleSettingsActionType;
|
| ToggleSettingsActionType;
|
||||||
|
|
||||||
|
@ -314,6 +341,14 @@ function closeNeedPermissionScreen(): CloseNeedPermissionScreenActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelCall(): CancelCallActionType {
|
||||||
|
window.Signal.Services.calling.stopCallingLobby();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: CANCEL_CALL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function declineCall(payload: DeclineCallType): DeclineCallActionType {
|
function declineCall(payload: DeclineCallType): DeclineCallActionType {
|
||||||
calling.decline(payload.callId);
|
calling.decline(payload.callId);
|
||||||
|
|
||||||
|
@ -385,7 +420,9 @@ function setRendererCanvas(payload: SetRendererCanvasType): NoopActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLocalAudio(payload: SetLocalAudioType): SetLocalAudioActionType {
|
function setLocalAudio(payload: SetLocalAudioType): SetLocalAudioActionType {
|
||||||
calling.setOutgoingAudio(payload.callId, payload.enabled);
|
if (payload.callId) {
|
||||||
|
calling.setOutgoingAudio(payload.callId, payload.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: SET_LOCAL_AUDIO,
|
type: SET_LOCAL_AUDIO,
|
||||||
|
@ -400,6 +437,31 @@ function setLocalVideo(payload: SetLocalVideoType): SetLocalVideoActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showCallLobby(payload: OutgoingCallType): CallLobbyActionType {
|
||||||
|
return {
|
||||||
|
type: SHOW_CALL_LOBBY,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCall(payload: OutgoingCallType): StartCallActionType {
|
||||||
|
const { callDetails } = payload;
|
||||||
|
window.Signal.Services.calling.startOutgoingCall(
|
||||||
|
callDetails.id,
|
||||||
|
callDetails.isVideoCall
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: START_CALL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleParticipants(): ToggleParticipantsActionType {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_PARTICIPANTS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function togglePip(): TogglePipActionType {
|
function togglePip(): TogglePipActionType {
|
||||||
return {
|
return {
|
||||||
type: TOGGLE_PIP,
|
type: TOGGLE_PIP,
|
||||||
|
@ -416,7 +478,13 @@ async function doSetLocalVideo(
|
||||||
payload: SetLocalVideoType
|
payload: SetLocalVideoType
|
||||||
): Promise<SetLocalVideoType> {
|
): Promise<SetLocalVideoType> {
|
||||||
if (await requestCameraPermissions()) {
|
if (await requestCameraPermissions()) {
|
||||||
calling.setOutgoingVideo(payload.callId, payload.enabled);
|
if (payload.callId) {
|
||||||
|
calling.setOutgoingVideo(payload.callId, payload.enabled);
|
||||||
|
} else if (payload.enabled) {
|
||||||
|
calling.enableLocalCamera();
|
||||||
|
} else {
|
||||||
|
calling.disableLocalCamera();
|
||||||
|
}
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,6 +496,7 @@ async function doSetLocalVideo(
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
|
cancelCall,
|
||||||
callStateChange,
|
callStateChange,
|
||||||
changeIODevice,
|
changeIODevice,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
|
@ -441,6 +510,9 @@ export const actions = {
|
||||||
setRendererCanvas,
|
setRendererCanvas,
|
||||||
setLocalAudio,
|
setLocalAudio,
|
||||||
setLocalVideo,
|
setLocalVideo,
|
||||||
|
showCallLobby,
|
||||||
|
startCall,
|
||||||
|
toggleParticipants,
|
||||||
togglePip,
|
togglePip,
|
||||||
toggleSettings,
|
toggleSettings,
|
||||||
};
|
};
|
||||||
|
@ -460,6 +532,7 @@ function getEmptyState(): CallingStateType {
|
||||||
hasLocalAudio: false,
|
hasLocalAudio: false,
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
hasRemoteVideo: false,
|
hasRemoteVideo: false,
|
||||||
|
participantsList: false,
|
||||||
pip: false,
|
pip: false,
|
||||||
selectedCamera: undefined,
|
selectedCamera: undefined,
|
||||||
selectedMicrophone: undefined,
|
selectedMicrophone: undefined,
|
||||||
|
@ -472,6 +545,23 @@ export function reducer(
|
||||||
state: CallingStateType = getEmptyState(),
|
state: CallingStateType = getEmptyState(),
|
||||||
action: CallingActionType
|
action: CallingActionType
|
||||||
): CallingStateType {
|
): CallingStateType {
|
||||||
|
if (action.type === SHOW_CALL_LOBBY) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
callDetails: action.payload.callDetails,
|
||||||
|
callState: undefined,
|
||||||
|
hasLocalAudio: true,
|
||||||
|
hasLocalVideo: action.payload.callDetails.isVideoCall,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === START_CALL) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
callState: CallState.Prering,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === ACCEPT_CALL) {
|
if (action.type === ACCEPT_CALL) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -481,6 +571,7 @@ export function reducer(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
action.type === CANCEL_CALL ||
|
||||||
action.type === DECLINE_CALL ||
|
action.type === DECLINE_CALL ||
|
||||||
action.type === HANG_UP ||
|
action.type === HANG_UP ||
|
||||||
action.type === CLOSE_NEED_PERMISSION_SCREEN
|
action.type === CLOSE_NEED_PERMISSION_SCREEN
|
||||||
|
@ -501,8 +592,6 @@ export function reducer(
|
||||||
...state,
|
...state,
|
||||||
callDetails: action.payload.callDetails,
|
callDetails: action.payload.callDetails,
|
||||||
callState: CallState.Prering,
|
callState: CallState.Prering,
|
||||||
hasLocalAudio: true,
|
|
||||||
hasLocalVideo: action.payload.callDetails.isVideoCall,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,6 +679,13 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === TOGGLE_PARTICIPANTS) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
participantsList: !state.participantsList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === TOGGLE_PIP) {
|
if (action.type === TOGGLE_PIP) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -19,8 +19,9 @@ function renderDeviceSelection(): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
const { calling } = state;
|
||||||
return {
|
return {
|
||||||
...state.calling,
|
...calling,
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
renderDeviceSelection,
|
renderDeviceSelection,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12847,7 +12847,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/CallScreen.js",
|
"path": "ts/components/CallScreen.js",
|
||||||
"line": " this.localVideoRef = react_1.default.createRef();",
|
"line": " this.localVideoRef = react_1.default.createRef();",
|
||||||
"lineNumber": 98,
|
"lineNumber": 94,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
"updated": "2020-09-14T23:03:44.863Z"
|
||||||
},
|
},
|
||||||
|
@ -12855,7 +12855,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/CallScreen.js",
|
"path": "ts/components/CallScreen.js",
|
||||||
"line": " this.remoteVideoRef = react_1.default.createRef();",
|
"line": " this.remoteVideoRef = react_1.default.createRef();",
|
||||||
"lineNumber": 99,
|
"lineNumber": 95,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
"updated": "2020-09-14T23:03:44.863Z"
|
||||||
},
|
},
|
||||||
|
@ -12863,7 +12863,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/CallScreen.tsx",
|
"path": "ts/components/CallScreen.tsx",
|
||||||
"line": " this.localVideoRef = React.createRef();",
|
"line": " this.localVideoRef = React.createRef();",
|
||||||
"lineNumber": 78,
|
"lineNumber": 58,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-06-02T21:51:34.813Z",
|
"updated": "2020-06-02T21:51:34.813Z",
|
||||||
"reasonDetail": "Used to render local preview video"
|
"reasonDetail": "Used to render local preview video"
|
||||||
|
@ -12872,7 +12872,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/CallScreen.tsx",
|
"path": "ts/components/CallScreen.tsx",
|
||||||
"line": " this.remoteVideoRef = React.createRef();",
|
"line": " this.remoteVideoRef = React.createRef();",
|
||||||
"lineNumber": 79,
|
"lineNumber": 59,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-14T23:03:44.863Z"
|
"updated": "2020-09-14T23:03:44.863Z"
|
||||||
},
|
},
|
||||||
|
|
|
@ -479,7 +479,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
||||||
);
|
);
|
||||||
await window.Signal.Services.calling.startOutgoingCall(
|
await window.Signal.Services.calling.startCallingLobby(
|
||||||
conversation,
|
conversation,
|
||||||
isVideoCall
|
isVideoCall
|
||||||
);
|
);
|
||||||
|
@ -504,7 +504,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
||||||
);
|
);
|
||||||
await window.Signal.Services.calling.startOutgoingCall(
|
await window.Signal.Services.calling.startCallingLobby(
|
||||||
conversation,
|
conversation,
|
||||||
isVideoCall
|
isVideoCall
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue