mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-07 15:22:29 +00:00
Compare commits
88 Commits
android-sd
...
5170
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f8fa4f059 | ||
|
|
becaf0806a | ||
|
|
5b77d722d7 | ||
|
|
f4cde2192e | ||
|
|
e91df47d1b | ||
|
|
2d04f3852c | ||
|
|
2209394d09 | ||
|
|
1e76dc0aa2 | ||
|
|
75edfc1fab | ||
|
|
8c20dd8e47 | ||
|
|
fefe451180 | ||
|
|
b268e01a42 | ||
|
|
d62e378528 | ||
|
|
e8ad2365b6 | ||
|
|
b7389e1c31 | ||
|
|
eeddf6b350 | ||
|
|
665b7730ee | ||
|
|
7854437e31 | ||
|
|
600af62945 | ||
|
|
88ddb8d9b4 | ||
|
|
5182a720f9 | ||
|
|
415562c315 | ||
|
|
53d0a892b5 | ||
|
|
9b220f3870 | ||
|
|
c6e50ad439 | ||
|
|
36cb896680 | ||
|
|
249515ac60 | ||
|
|
80b49266ab | ||
|
|
1afae50923 | ||
|
|
b332fb474b | ||
|
|
a12ad99ecf | ||
|
|
400f47963d | ||
|
|
65fbc6f256 | ||
|
|
e7a324185f | ||
|
|
14a5c45fa3 | ||
|
|
05e6dde341 | ||
|
|
e13473d42f | ||
|
|
4b72fefd7e | ||
|
|
ba9398a1e2 | ||
|
|
8d4cf7165e | ||
|
|
0b3991d9e1 | ||
|
|
47be509d17 | ||
|
|
ba64d3e0c8 | ||
|
|
cd05c34d19 | ||
|
|
24550777c6 | ||
|
|
ee101f8947 | ||
|
|
8ca85f9e1c | ||
|
|
34ccd56691 | ||
|
|
f49c05c666 | ||
|
|
e7280e5040 | ||
|
|
eb1add681f | ||
|
|
8419dc725c | ||
|
|
f984faef3f | ||
|
|
0c76d7532c | ||
|
|
cb0b68f840 | ||
|
|
08a4da22f3 | ||
|
|
bdd6638067 | ||
|
|
8b44e06f2c | ||
|
|
79edc1b358 | ||
|
|
6597bfc2aa | ||
|
|
e0a2320d75 | ||
|
|
81e9fca03b | ||
|
|
76f8302aeb | ||
|
|
7263829763 | ||
|
|
b7ab3ea052 | ||
|
|
c657f360e1 | ||
|
|
ae5edf5a62 | ||
|
|
2bac757ca6 | ||
|
|
c10805f81b | ||
|
|
251eec19cd | ||
|
|
4276f82c03 | ||
|
|
4c3aae1e28 | ||
|
|
12be14bd4b | ||
|
|
420a7d8110 | ||
|
|
17f77a4246 | ||
|
|
6f9944a2d0 | ||
|
|
73328810e4 | ||
|
|
bb8c30a6c9 | ||
|
|
c5438ecd0c | ||
|
|
e22a25b216 | ||
|
|
4075e5deb7 | ||
|
|
ea0d953d1c | ||
|
|
b3e03fe50c | ||
|
|
8f81a75a61 | ||
|
|
0ab905bf75 | ||
|
|
5a3607f63f | ||
|
|
d57e202d19 | ||
|
|
1223c63f69 |
@@ -40,7 +40,8 @@
|
||||
|
||||
<service
|
||||
android:name=".ConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
|
||||
11
config.js
11
config.js
@@ -70,6 +70,9 @@ var config = {
|
||||
// callStatsThreshold: 5 // enable callstats for 5% of the users.
|
||||
},
|
||||
|
||||
// Enables reactions feature.
|
||||
enableReactions: false,
|
||||
|
||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||
// signalling.
|
||||
// webrtcIceUdpDisable: false,
|
||||
@@ -460,7 +463,7 @@ var config = {
|
||||
// - if `toolbarButtons` is undefined, we fallback to enabling all buttons on the UI
|
||||
// toolbarButtons: [
|
||||
// 'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
|
||||
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
// 'fodeviceselection', 'hangup', 'profile', 'participants-pane', 'chat', 'recording',
|
||||
// 'livestreaming', 'etherpad', 'sharedvideo', 'shareaudio', 'settings', 'raisehand',
|
||||
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
// 'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
|
||||
@@ -603,6 +606,9 @@ var config = {
|
||||
// conference (if set to true, these sounds will not be played).
|
||||
// disableJoinLeaveSounds: false,
|
||||
|
||||
// Disables the sounds that play when a chat message is received.
|
||||
// disableIncomingMessageSound: false,
|
||||
|
||||
// Information for the chrome extension banner
|
||||
// chromeExtensionBanner: {
|
||||
// // The chrome extension to be installed address
|
||||
@@ -732,6 +738,9 @@ var config = {
|
||||
// Hides the conference subject
|
||||
// hideConferenceSubject: true,
|
||||
|
||||
// Hides the recording label
|
||||
// hideRecordingLabel: false,
|
||||
|
||||
// Hides the conference timer.
|
||||
// hideConferenceTimer: true,
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { isVpaasMeeting } from './react/features/billing-counter/functions';
|
||||
import { getJaasJWT } from './react/features/jaas/functions';
|
||||
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
|
||||
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -89,8 +88,9 @@ export async function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const state = APP.store.getState();
|
||||
let { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
if (!jwt && isVpaasMeeting(state, false)) {
|
||||
if (!iAmRecorder && !iAmSipGateway && !jwt && isVpaasMeeting(state)) {
|
||||
jwt = await getJaasJWT(state);
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
}
|
||||
|
||||
@@ -4,16 +4,28 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: $drawerZ;
|
||||
background-color: #141414;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.drawer-portal::after {
|
||||
content: '';
|
||||
background-color: $participantsPaneBgColor;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.drawer-menu-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
max-height: calc(80vh - 64px);
|
||||
background: #242528;
|
||||
border-radius: 16px 16px 0 0;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
.drawer-toggle {
|
||||
display: flex;
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
width: 20%;
|
||||
bottom: 0;
|
||||
left: 40%;
|
||||
height: 48px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.reactions-menu-popup-container,
|
||||
@@ -111,8 +111,8 @@ $reactionCount: 20;
|
||||
line-height: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
top: 32px;
|
||||
left: 10px;
|
||||
top: 0;
|
||||
left: 20px;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
|
||||
@@ -123,8 +123,8 @@ $reactionCount: 20;
|
||||
@for $i from 1 through $reactionCount {
|
||||
&.reaction-#{$i} {
|
||||
animation: animation-#{$i} 5s forwards ease-in-out;
|
||||
top: #{random(50, 0)}px;
|
||||
left: #{random(-10, 10)}px;
|
||||
top: #{random(-40, 10)}px;
|
||||
left: #{random(0, 30)}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,31 @@
|
||||
z-index: $zindex3;
|
||||
|
||||
&.visible {
|
||||
top: 0px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.recording {
|
||||
top: 0;
|
||||
|
||||
.subject-details-container {
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease-in;
|
||||
}
|
||||
|
||||
.subject-info-container .show-always {
|
||||
transition: margin-left .3s ease-in;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
.subject-details-container {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subject-details-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.subject-info-container {
|
||||
|
||||
@@ -105,12 +105,15 @@
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
pointer-events: all;
|
||||
background-color: #131519;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.toolbox-content-wrapper::after {
|
||||
content: '';
|
||||
background: $newToolbarBackgroundColor;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.toolbox-content-items {
|
||||
background: $newToolbarBackgroundColor;
|
||||
border-radius: 6px;
|
||||
@@ -118,6 +121,7 @@
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
pointer-events: all;
|
||||
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
|
||||
|
||||
>div {
|
||||
margin-left: 8px;
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
z-index: $subtitlesZ;
|
||||
|
||||
&.lifted {
|
||||
// Lift subtitle above toolbar+invite box.
|
||||
bottom: $newToolbarSize + 112px + 40px;
|
||||
// Lift subtitle above toolbar+dominant speaker box.
|
||||
bottom: $newToolbarSize + 36px + 40px;
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
@@ -46,6 +46,7 @@ $menuBG:#242528;
|
||||
$newToolbarFontSize: 24px;
|
||||
$newToolbarHangupFontSize: 32px;
|
||||
$newToolbarSize: 48px;
|
||||
$newToolbarSizeMobile: 60px;
|
||||
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$overflowMenuItemColor: #fff;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: margin-bottom .3s ease-in;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
@@ -52,11 +53,23 @@
|
||||
margin-left: $sidebarWidth;
|
||||
width: calc(100% - #{$sidebarWidth});
|
||||
|
||||
.remote-videos{
|
||||
.remote-videos {
|
||||
width: calc(100vw - #{$sidebarWidth});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
#remoteVideos {
|
||||
height: calc(100% - #{$newToolbarSizeMobile}) !important;
|
||||
margin-bottom: $newToolbarSizeMobile;
|
||||
}
|
||||
|
||||
.remote-videos {
|
||||
// !important is needed here as overflow is set via element.style in a FixedSizeGrid.
|
||||
overflow: hidden auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -174,7 +174,7 @@ var interfaceConfig = {
|
||||
RECENT_LIST_ENABLED: true,
|
||||
REMOTE_THUMBNAIL_RATIO: 1, // 1:1
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar', 'sounds' ],
|
||||
|
||||
/**
|
||||
* Specify which sharing features should be displayed. If the value is not set
|
||||
|
||||
@@ -58,6 +58,7 @@ target 'JitsiMeetSDK' do
|
||||
pod 'react-native-calendar-events', :path => '../node_modules/react-native-calendar-events'
|
||||
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake'
|
||||
pod 'react-native-netinfo', :path => '../node_modules/@react-native-community/netinfo'
|
||||
pod 'react-native-slider', :path => '../node_modules/@react-native-community/slider'
|
||||
pod 'react-native-splash-screen', :path => '../node_modules/react-native-splash-screen'
|
||||
pod 'react-native-webview', :path => '../node_modules/react-native-webview'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
PODS:
|
||||
- AppAuth (1.2.0):
|
||||
- AppAuth/Core (= 1.2.0)
|
||||
- AppAuth/ExternalUserAgent (= 1.2.0)
|
||||
- AppAuth/Core (1.2.0)
|
||||
- AppAuth/ExternalUserAgent (1.2.0)
|
||||
- AppAuth (1.4.0):
|
||||
- AppAuth/Core (= 1.4.0)
|
||||
- AppAuth/ExternalUserAgent (= 1.4.0)
|
||||
- AppAuth/Core (1.4.0)
|
||||
- AppAuth/ExternalUserAgent (1.4.0)
|
||||
- boost-for-react-native (1.63.0)
|
||||
- CocoaLumberjack (3.5.3):
|
||||
- CocoaLumberjack/Core (= 3.5.3)
|
||||
@@ -48,7 +48,7 @@ PODS:
|
||||
- GoogleUtilities/Environment (~> 6.7)
|
||||
- GoogleUtilities/Logger (~> 6.7)
|
||||
- nanopb (~> 1.30906.0)
|
||||
- FirebaseCrashlytics (4.6.1):
|
||||
- FirebaseCrashlytics (4.6.2):
|
||||
- FirebaseCore (~> 6.10)
|
||||
- FirebaseInstallations (~> 1.6)
|
||||
- GoogleDataTransport (~> 7.2)
|
||||
@@ -77,9 +77,9 @@ PODS:
|
||||
- GoogleUtilities/Network (~> 6.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 6.7)"
|
||||
- nanopb (~> 1.30906.0)
|
||||
- GoogleDataTransport (7.4.0):
|
||||
- GoogleDataTransport (7.5.1):
|
||||
- nanopb (~> 1.30906.0)
|
||||
- GoogleSignIn (5.0.1):
|
||||
- GoogleSignIn (5.0.2):
|
||||
- AppAuth (~> 1.2)
|
||||
- GTMAppAuth (~> 1.0)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
@@ -102,21 +102,17 @@ PODS:
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (6.7.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (1.0.0):
|
||||
- AppAuth/Core (~> 1.0)
|
||||
- GTMSessionFetcher (~> 1.1)
|
||||
- GTMSessionFetcher (1.2.2):
|
||||
- GTMSessionFetcher/Full (= 1.2.2)
|
||||
- GTMSessionFetcher/Core (1.2.2)
|
||||
- GTMSessionFetcher/Full (1.2.2):
|
||||
- GTMSessionFetcher/Core (= 1.2.2)
|
||||
- GTMAppAuth (1.2.2):
|
||||
- AppAuth/Core (~> 1.4)
|
||||
- GTMSessionFetcher/Core (~> 1.5)
|
||||
- GTMSessionFetcher/Core (1.6.1)
|
||||
- nanopb (1.30906.0):
|
||||
- nanopb/decode (= 1.30906.0)
|
||||
- nanopb/encode (= 1.30906.0)
|
||||
- nanopb/decode (1.30906.0)
|
||||
- nanopb/encode (1.30906.0)
|
||||
- ObjectiveDropboxOfficial (3.9.4)
|
||||
- PromisesObjC (1.2.10)
|
||||
- PromisesObjC (1.2.12)
|
||||
- RCTRequired (0.61.5-jitsi.2)
|
||||
- RCTTypeSafety (0.61.5-jitsi.2):
|
||||
- FBLazyVector (= 0.61.5-jitsi.2)
|
||||
@@ -288,6 +284,8 @@ PODS:
|
||||
- React
|
||||
- react-native-netinfo (4.1.5):
|
||||
- React
|
||||
- react-native-slider (3.0.3):
|
||||
- React
|
||||
- react-native-splash-screen (3.2.0):
|
||||
- React
|
||||
- react-native-webrtc (1.89.1):
|
||||
@@ -394,6 +392,7 @@ DEPENDENCIES:
|
||||
- react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
|
||||
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
@@ -475,6 +474,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-keep-awake"
|
||||
react-native-netinfo:
|
||||
:path: "../node_modules/@react-native-community/netinfo"
|
||||
react-native-slider:
|
||||
:path: "../node_modules/@react-native-community/slider"
|
||||
react-native-splash-screen:
|
||||
:path: "../node_modules/react-native-splash-screen"
|
||||
react-native-webrtc:
|
||||
@@ -519,7 +520,7 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppAuth: bce82c76043657c99d91e7882e8a9e1a93650cd4
|
||||
AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
|
||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||
@@ -529,20 +530,20 @@ SPEC CHECKSUMS:
|
||||
FirebaseAnalytics: 5dd088bd2e67bb9d13dbf792d1164ceaf3052193
|
||||
FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd
|
||||
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
|
||||
FirebaseCrashlytics: 5777d3462fb8c3ab9e80a2473bd7d667a2e8411c
|
||||
FirebaseCrashlytics: 1a747c9cc084a24dc6d9511c991db1cd078154eb
|
||||
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
|
||||
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
|
||||
GoogleDataTransport: b7f406340a291370045a270c599e53c6fa6ec20f
|
||||
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
|
||||
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
|
||||
GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213
|
||||
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
|
||||
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
|
||||
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
|
||||
GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89
|
||||
GTMSessionFetcher: 36689134877faeb055b27dfa4ccc9ceaa42e029e
|
||||
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
|
||||
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
|
||||
PromisesObjC: b14b1c6b68e306650688599de8a45e49fae81151
|
||||
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
|
||||
RCTRequired: a686731276578c125dff205f08b6ec9cee6ede32
|
||||
RCTTypeSafety: 88e5500e801c00d16a3d1895e3470d13beed6584
|
||||
React: 8b2bcf6a93846e47a7a365a54ec6edeb78b37701
|
||||
@@ -556,6 +557,7 @@ SPEC CHECKSUMS:
|
||||
react-native-calendar-events: 1442fad71a00388f933cfa25512588fec300fcf8
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-webrtc: ccb0c21eb4fb04326648fbdb4a5d49977e2cf274
|
||||
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
|
||||
@@ -578,6 +580,6 @@ SPEC CHECKSUMS:
|
||||
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
|
||||
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
|
||||
|
||||
PODFILE CHECKSUM: d059cebf82da14a53940a16c24c3330752d4b0c8
|
||||
PODFILE CHECKSUM: 1fa5a1e259f145d32c1ca968b26dac65cff34b49
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
|
||||
@@ -258,6 +258,7 @@
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantButton": "Mute",
|
||||
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantsVideoDialog": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on, but they can turn it back on at any time.",
|
||||
"muteParticipantTitle": "Mute this participant?",
|
||||
"muteParticipantsVideoButton": "Disable camera",
|
||||
"muteParticipantsVideoTitle": "Disable camera of this participant?",
|
||||
@@ -418,6 +419,10 @@
|
||||
"invitePhone": "To join by phone instead, tap this: {{number}},,{{conferenceID}}#\n",
|
||||
"invitePhoneAlternatives": "Looking for a different dial-in number?\nSee meeting dial-in numbers: {{url}}\n\n\nIf also dialing-in through a room phone, join without connecting to audio: {{silentUrl}}",
|
||||
"inviteSipEndpoint": "To join using the SIP address, enter this: {{sipUri}}",
|
||||
"inviteTextiOSPersonal": "{{name}} is inviting you to a meeting.",
|
||||
"inviteTextiOSJoinSilent": "If you are dialing-in through a room phone, use this link to join without connecting to audio: {{silentUrl}}.",
|
||||
"inviteTextiOSInviteUrl": "Click the following link to join: {{inviteUrl}}.",
|
||||
"inviteTextiOSPhone": "To join via phone, use this number: {{number}},,{{conferenceID}}#. If you are looking for a different number, this is the full list: {{didUrl}}.",
|
||||
"inviteURLFirstPartGeneral": "You are invited to join a meeting.",
|
||||
"inviteURLFirstPartPersonal": "{{name}} is inviting you to a meeting.\n",
|
||||
"inviteURLSecondPart": "\nJoin the meeting:\n{{url}}\n",
|
||||
@@ -588,16 +593,21 @@
|
||||
"close": "Close",
|
||||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Meeting participants ({{count}})"
|
||||
"participantsList": "Meeting participants ({{count}})",
|
||||
"waitingLobby": "Waiting in lobby ({{count}})"
|
||||
},
|
||||
"actions": {
|
||||
"allow": "Allow attendees to:",
|
||||
"blockEveryoneMicCamera": "Block everyone's mic and camera",
|
||||
"invite": "Invite Someone",
|
||||
"askUnmute": "Ask to unmute",
|
||||
"mute": "Mute",
|
||||
"muteAll": "Mute all",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"startModeration": "Unmute themselves or start video",
|
||||
"stopEveryonesVideo": "Stop everyone's video",
|
||||
"stopVideo": "Stop video"
|
||||
"stopVideo": "Stop video",
|
||||
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera"
|
||||
}
|
||||
},
|
||||
"passwordSetRemotely": "Set by another participant",
|
||||
@@ -740,6 +750,7 @@
|
||||
"devices": "Devices",
|
||||
"followMe": "Everyone follows me",
|
||||
"framesPerSecond": "frames-per-second",
|
||||
"incomingMessage": "Incoming message",
|
||||
"language": "Language",
|
||||
"loggedIn": "Logged in as {{name}}",
|
||||
"microphones": "Microphones",
|
||||
@@ -747,13 +758,18 @@
|
||||
"more": "More",
|
||||
"name": "Name",
|
||||
"noDevice": "None",
|
||||
"participantJoined": "Participant Joined",
|
||||
"participantLeft": "Participant Left",
|
||||
"playSounds": "Play sound on",
|
||||
"sameAsSystem": "Same as system ({{label}})",
|
||||
"selectAudioOutput": "Audio output",
|
||||
"selectCamera": "Camera",
|
||||
"selectMic": "Microphone",
|
||||
"sounds": "Sounds",
|
||||
"speakers": "Speakers",
|
||||
"startAudioMuted": "Everyone starts muted",
|
||||
"startVideoMuted": "Everyone starts hidden",
|
||||
"talkWhileMuted": "Talk while muted",
|
||||
"title": "Settings"
|
||||
},
|
||||
"settingsView": {
|
||||
@@ -805,6 +821,7 @@
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Toggle audio only",
|
||||
"audioRoute": "Select the sound device",
|
||||
"boo": "Boo",
|
||||
"callQuality": "Manage video quality",
|
||||
"cc": "Toggle subtitles",
|
||||
"chat": "Open / Close chat",
|
||||
@@ -818,8 +835,8 @@
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"joy": "Laughing Crying",
|
||||
"kick": "Kick participant",
|
||||
"laugh": "Laugh",
|
||||
"like": "Thumbs Up",
|
||||
"lobbyButton": "Enable/disable lobby mode",
|
||||
"localRecording": "Toggle local recording controls",
|
||||
@@ -850,7 +867,6 @@
|
||||
"shareYourScreen": "Start / Stop sharing your screen",
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"show": "Show on stage",
|
||||
"smile": "Smile",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"surprised": "Surprised",
|
||||
"tileView": "Toggle tile view",
|
||||
@@ -869,6 +885,7 @@
|
||||
"audioOnlyOn": "Enable low bandwidth mode",
|
||||
"audioRoute": "Select the sound device",
|
||||
"authenticate": "Authenticate",
|
||||
"boo": "Boo",
|
||||
"callQuality": "Manage video quality",
|
||||
"chat": "Open / Close chat",
|
||||
"clap": "Clap",
|
||||
@@ -887,7 +904,7 @@
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"joy": "Joy",
|
||||
"laugh": "Laugh",
|
||||
"like": "Thumbs Up",
|
||||
"lobbyButtonDisable": "Disable lobby mode",
|
||||
"lobbyButtonEnable": "Enable lobby mode",
|
||||
@@ -915,11 +932,11 @@
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise / Lower your hand",
|
||||
"raiseYourHand": "Raise your hand",
|
||||
"reactionBoo": "Send boo reaction",
|
||||
"reactionClap": "Send clap reaction",
|
||||
"reactionJoy": "Send joy reaction",
|
||||
"reactionLaugh": "Send laugh reaction",
|
||||
"reactionLike": "Send thumbs up reaction",
|
||||
"reactionParty": "Send party popper reaction",
|
||||
"reactionSmile": "Send smile reaction",
|
||||
"reactionSurprised": "Send surprised reaction",
|
||||
"security": "Security options",
|
||||
"Settings": "Settings",
|
||||
@@ -927,7 +944,6 @@
|
||||
"sharedvideo": "Share video",
|
||||
"shareRoom": "Invite someone",
|
||||
"shortcuts": "View shortcuts",
|
||||
"smile": "Smile",
|
||||
"speakerStats": "Speaker stats",
|
||||
"startScreenSharing": "Start screen sharing",
|
||||
"startSubtitles": "Start subtitles",
|
||||
@@ -1098,6 +1114,7 @@
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
"rejectAll": "Reject all",
|
||||
"toggleLabel": "Enable lobby"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +574,12 @@ function initCommands() {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'get-custom-avatar-backgrounds' : {
|
||||
callback({
|
||||
avatarBackgrounds: APP.store.getState()['features/dynamic-branding'].avatarBackgrounds
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
11
modules/API/external/external_api.js
vendored
11
modules/API/external/external_api.js
vendored
@@ -769,6 +769,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return getCurrentDevices(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns any custom avatars backgrounds.
|
||||
*
|
||||
* @returns {Promise} - Resolves with the list of custom avatar backgrounds.
|
||||
*/
|
||||
getCustomAvatarBackgrounds() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'get-custom-avatar-backgrounds'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current livestream url.
|
||||
*
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -3102,6 +3102,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.1.5.tgz",
|
||||
"integrity": "sha512-lagdZr9UiVAccNXYfTEj+aUcPCx9ykbMe9puffeIyF3JsRuMmlu3BjHYx1klUHX7wNRmFNC8qVP0puxUt1sZ0A=="
|
||||
},
|
||||
"@react-native-community/slider": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-3.0.3.tgz",
|
||||
"integrity": "sha512-8IeHfDwJ9/CTUwFs6x90VlobV3BfuPgNLjTgC6dRZovfCWigaZwVNIFFJnHBakK3pW2xErAPwhdvNR4JeNoYbw=="
|
||||
},
|
||||
"@svgr/babel-plugin-add-jsx-attribute": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
|
||||
@@ -11071,8 +11076,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#89a7e2d9cdd24f48f95f6668ae4d8db1b635cf36",
|
||||
"from": "github:jitsi/lib-jitsi-meet#89a7e2d9cdd24f48f95f6668ae4d8db1b635cf36",
|
||||
"version": "github:jitsi/lib-jitsi-meet#b815157a22cec219a26457143b6d6cb2f430e01b",
|
||||
"from": "github:jitsi/lib-jitsi-meet#b815157a22cec219a26457143b6d6cb2f430e01b",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "github:jitsi/sdp-interop#5fc4af6dcf8a6e6af9fedbcd654412fd47b1b4ae",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@react-native-async-storage/async-storage": "1.13.2",
|
||||
"@react-native-community/google-signin": "3.0.1",
|
||||
"@react-native-community/netinfo": "4.1.5",
|
||||
"@react-native-community/slider": "3.0.3",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"amplitude-js": "8.2.1",
|
||||
"base64-js": "1.3.1",
|
||||
@@ -55,7 +56,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#89a7e2d9cdd24f48f95f6668ae4d8db1b635cf36",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#b815157a22cec219a26457143b6d6cb2f430e01b",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
|
||||
@@ -21,6 +21,7 @@ const TOOLBAR_TIMEOUT = 4000;
|
||||
*/
|
||||
type State = {
|
||||
avatarURL: string,
|
||||
customAvatarBackgrounds: Array<string>,
|
||||
displayName: string,
|
||||
formattedDisplayName: string,
|
||||
isVideoDisplayed: boolean,
|
||||
@@ -48,6 +49,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
||||
|
||||
this.state = {
|
||||
avatarURL: '',
|
||||
customAvatarBackgrounds: [],
|
||||
displayName: '',
|
||||
formattedDisplayName: '',
|
||||
isVideoDisplayed: true,
|
||||
@@ -178,7 +180,14 @@ export default class AlwaysOnTop extends Component<*, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderVideoNotAvailableScreen() {
|
||||
const { avatarURL, displayName, formattedDisplayName, isVideoDisplayed, userID } = this.state;
|
||||
const {
|
||||
avatarURL,
|
||||
customAvatarBackgrounds,
|
||||
displayName,
|
||||
formattedDisplayName,
|
||||
isVideoDisplayed,
|
||||
userID
|
||||
} = this.state;
|
||||
|
||||
if (isVideoDisplayed) {
|
||||
return null;
|
||||
@@ -188,7 +197,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
||||
<div id = 'videoNotAvailableScreen'>
|
||||
<div id = 'avatarContainer'>
|
||||
<StatelessAvatar
|
||||
color = { getAvatarColor(userID) }
|
||||
color = { getAvatarColor(userID, customAvatarBackgrounds) }
|
||||
id = 'avatar'
|
||||
initials = { getInitials(displayName) }
|
||||
url = { avatarURL } />)
|
||||
@@ -218,6 +227,12 @@ export default class AlwaysOnTop extends Component<*, State> {
|
||||
window.addEventListener('mousemove', this._mouseMove);
|
||||
|
||||
this._hideToolbarAfterTimeout();
|
||||
api.getCustomAvatarBackgrounds()
|
||||
.then(res =>
|
||||
this.setState({
|
||||
customAvatarBackgrounds: res.avatarBackgrounds || []
|
||||
}))
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -795,6 +795,23 @@ export function createToolbarEvent(buttonName, attributes = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event associated with a reaction button being clicked/pressed.
|
||||
*
|
||||
* @param {string} buttonName - The identifier of the reaction button which was
|
||||
* clicked/pressed.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createReactionMenuEvent(buttonName) {
|
||||
return {
|
||||
action: 'clicked',
|
||||
actionSubject: buttonName,
|
||||
source: 'reaction.button',
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that a local track was muted.
|
||||
*
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
parseURIString,
|
||||
toURLString
|
||||
} from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { clearNotifications, showNotification } from '../notifications';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { Platform } from '../../base/react';
|
||||
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native';
|
||||
import logger from '../logger';
|
||||
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
@@ -128,12 +127,10 @@ export class App extends AbstractApp {
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<JitsiThemePaperProvider>
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
{ super._createMainElement(component, props) }
|
||||
</DimensionsDetector>
|
||||
</JitsiThemePaperProvider>
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
{ super._createMainElement(component, props) }
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import '../base/sounds/middleware';
|
||||
import '../base/testing/middleware';
|
||||
import '../base/tracks/middleware';
|
||||
import '../base/user-interaction/middleware';
|
||||
import '../billing-counter/middleware';
|
||||
import '../calendar-sync/middleware';
|
||||
import '../chat/middleware';
|
||||
import '../conference/middleware';
|
||||
|
||||
@@ -25,7 +25,6 @@ import '../base/sounds/reducer';
|
||||
import '../base/testing/reducer';
|
||||
import '../base/tracks/reducer';
|
||||
import '../base/user-interaction/reducer';
|
||||
import '../billing-counter/reducer';
|
||||
import '../calendar-sync/reducer';
|
||||
import '../chat/reducer';
|
||||
import '../deep-linking/reducer';
|
||||
@@ -41,6 +40,7 @@ import '../large-video/reducer';
|
||||
import '../lobby/reducer';
|
||||
import '../notifications/reducer';
|
||||
import '../overlay/reducer';
|
||||
import '../participants-pane/reducer';
|
||||
import '../reactions/reducer';
|
||||
import '../recent-list/reducer';
|
||||
import '../recording/reducer';
|
||||
|
||||
@@ -10,6 +10,11 @@ import { StatelessAvatar } from '.';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Custom avatar backgrounds from branding.
|
||||
*/
|
||||
_customAvatarBackgrounds: Array<string>,
|
||||
|
||||
/**
|
||||
* The string we base the initials on (this is generated from a list of precedences).
|
||||
*/
|
||||
@@ -133,6 +138,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_customAvatarBackgrounds,
|
||||
_initialsBase,
|
||||
_loadableAvatarUrl,
|
||||
className,
|
||||
@@ -172,7 +178,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
|
||||
|
||||
if (initials) {
|
||||
if (dynamicColor) {
|
||||
avatarProps.color = getAvatarColor(colorBase || _initialsBase);
|
||||
avatarProps.color = getAvatarColor(colorBase || _initialsBase, _customAvatarBackgrounds);
|
||||
}
|
||||
|
||||
avatarProps.initials = initials;
|
||||
@@ -211,6 +217,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const _initialsBase = _participant?.name ?? displayName;
|
||||
|
||||
return {
|
||||
_customAvatarBackgrounds: state['features/dynamic-branding'].avatarBackgrounds,
|
||||
_initialsBase,
|
||||
_loadableAvatarUrl: _participant?.loadableAvatarUrl,
|
||||
colorBase: !colorBase && _participant ? _participant.id : colorBase
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { Icon } from '../../../icons';
|
||||
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
|
||||
|
||||
@@ -31,19 +30,14 @@ type Props = AbstractProps & {
|
||||
/**
|
||||
* TestId of the element, if any.
|
||||
*/
|
||||
testId?: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
testId?: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
|
||||
* props.
|
||||
*/
|
||||
class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
@@ -70,7 +64,7 @@ class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
return (
|
||||
<div className = { this._getBadgeClassName() }>
|
||||
<img
|
||||
alt = { this.props.t('profile.avatar') }
|
||||
alt = 'avatar'
|
||||
className = { this._getAvatarClassName() }
|
||||
data-testid = { this.props.testId }
|
||||
id = { this.props.id }
|
||||
@@ -111,7 +105,7 @@ class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
return (
|
||||
<div className = { this._getBadgeClassName() }>
|
||||
<img
|
||||
alt = { this.props.t('profile.avatar') }
|
||||
alt = 'avatar'
|
||||
className = { this._getAvatarClassName('defaultAvatar') }
|
||||
data-testid = { this.props.testId }
|
||||
id = { this.props.id }
|
||||
@@ -131,7 +125,7 @@ class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
const { size } = this.props;
|
||||
|
||||
return {
|
||||
backgroundColor: color || undefined,
|
||||
background: color || undefined,
|
||||
fontSize: size ? size * 0.5 : '180%',
|
||||
height: size || '100%',
|
||||
width: size || '100%'
|
||||
@@ -165,5 +159,3 @@ class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
|
||||
_isIcon: (?string | ?Object) => boolean
|
||||
}
|
||||
|
||||
export default translate(StatelessAvatar);
|
||||
|
||||
@@ -16,9 +16,13 @@ const AVATAR_OPACITY = 0.4;
|
||||
* Generates the background color of an initials based avatar.
|
||||
*
|
||||
* @param {string?} initials - The initials of the avatar.
|
||||
* @param {Array<strig>} customAvatarBackgrounds - Custom avatar background values.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getAvatarColor(initials: ?string) {
|
||||
export function getAvatarColor(initials: ?string, customAvatarBackgrounds: Array<string>) {
|
||||
const hasCustomAvatarBackgronds = customAvatarBackgrounds && customAvatarBackgrounds.length;
|
||||
const colorsBase = hasCustomAvatarBackgronds ? customAvatarBackgrounds : AVATAR_COLORS;
|
||||
|
||||
let colorIndex = 0;
|
||||
|
||||
if (initials) {
|
||||
@@ -28,10 +32,10 @@ export function getAvatarColor(initials: ?string) {
|
||||
nameHash += s.codePointAt(0);
|
||||
}
|
||||
|
||||
colorIndex = nameHash % AVATAR_COLORS.length;
|
||||
colorIndex = nameHash % colorsBase.length;
|
||||
}
|
||||
|
||||
return `rgba(${AVATAR_COLORS[colorIndex]}, ${AVATAR_OPACITY})`;
|
||||
return hasCustomAvatarBackgronds ? colorsBase[colorIndex] : `rgba(${colorsBase[colorIndex]}, ${AVATAR_OPACITY})`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,6 +87,7 @@ export default [
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableInviteFunctions',
|
||||
'disableIncomingMessageSound',
|
||||
'disableJoinLeaveSounds',
|
||||
'disableLocalVideoFlip',
|
||||
'disableNS',
|
||||
@@ -130,6 +131,7 @@ export default [
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hideConferenceSubject',
|
||||
'hideRecordingLabel',
|
||||
'hideParticipantsStats',
|
||||
'hideConferenceTimer',
|
||||
'hiddenDomain',
|
||||
|
||||
@@ -18,6 +18,6 @@ export const TOOLBAR_BUTTONS = [
|
||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'shareaudio', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'participants-pane', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone',
|
||||
'tileview', 'toggle-camera', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone',
|
||||
'security'
|
||||
];
|
||||
|
||||
@@ -60,8 +60,6 @@ export function getRecordingSharingUrl(state: Object) {
|
||||
return state['features/base/config'].recordingSharingUrl;
|
||||
}
|
||||
|
||||
/* eslint-disable max-params, no-shadow */
|
||||
|
||||
/**
|
||||
* Overrides JSON properties in {@code config} and
|
||||
* {@code interfaceConfig} Objects with the values from {@code newConfig}.
|
||||
|
||||
@@ -25,11 +25,21 @@ const GESTURE_SPEED_THRESHOLD = 0.2;
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The height of the screen.
|
||||
*/
|
||||
_height: number,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* Whether to add padding to scroll view.
|
||||
*/
|
||||
addScrollViewPadding?: boolean,
|
||||
|
||||
/**
|
||||
* The children to be displayed within this component.
|
||||
*/
|
||||
@@ -57,9 +67,14 @@ type Props = {
|
||||
renderFooter: ?Function,
|
||||
|
||||
/**
|
||||
* The height of the screen.
|
||||
*/
|
||||
_height: number
|
||||
* Whether to show sliding view or not.
|
||||
*/
|
||||
showSlidingView?: boolean,
|
||||
|
||||
/**
|
||||
* The component's external style
|
||||
*/
|
||||
style: Object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -68,6 +83,16 @@ type Props = {
|
||||
class BottomSheet extends PureComponent<Props> {
|
||||
panResponder: Object;
|
||||
|
||||
/**
|
||||
* Default values for {@code BottomSheet} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
addScrollViewPadding: true,
|
||||
showSlidingView: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
@@ -90,7 +115,15 @@ class BottomSheet extends PureComponent<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _styles, renderHeader, renderFooter, _height } = this.props;
|
||||
const {
|
||||
_height,
|
||||
_styles,
|
||||
addScrollViewPadding,
|
||||
renderHeader,
|
||||
renderFooter,
|
||||
showSlidingView,
|
||||
style
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
@@ -98,7 +131,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
accessibilityViewIsModal = { true }
|
||||
onHide = { this.props.onCancel }
|
||||
position = 'bottom'
|
||||
show = { true }>
|
||||
show = { showSlidingView }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.sheetContainer }>
|
||||
@@ -110,6 +143,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
style = { [
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet,
|
||||
style,
|
||||
{
|
||||
maxHeight: _height - 100
|
||||
}
|
||||
@@ -118,7 +152,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
showsVerticalScrollIndicator = { false }
|
||||
style = { styles.scrollView } >
|
||||
style = { addScrollViewPadding && styles.scrollView } >
|
||||
{ this.props.children }
|
||||
</ScrollView>
|
||||
{ renderFooter && renderFooter() }
|
||||
|
||||
@@ -214,3 +214,9 @@ export const VIDEO_SHARE_BUTTON_ENABLED = 'video-share.enabled';
|
||||
* Default: disabled (false).
|
||||
*/
|
||||
export const WELCOME_PAGE_ENABLED = 'welcomepage.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the reactions feature should be enabled.
|
||||
* Default: disabled (false).
|
||||
*/
|
||||
export const REACTIONS_ENABLED = 'reactions.enabled';
|
||||
|
||||
@@ -50,6 +50,11 @@ type Props = {
|
||||
*/
|
||||
headerProps: Object,
|
||||
|
||||
/**
|
||||
* True if the header with navigation should be hidden, false otherwise.
|
||||
*/
|
||||
hideHeaderWithNavigation?: boolean,
|
||||
|
||||
/**
|
||||
* The ID of the modal that is being rendered. This is used to show/hide the modal.
|
||||
*/
|
||||
@@ -78,7 +83,8 @@ type Props = {
|
||||
*/
|
||||
class JitsiModal extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
position: 'bottom'
|
||||
position: 'bottom',
|
||||
hideHeaderWithNavigation: false
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,7 +104,17 @@ class JitsiModal extends PureComponent<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _headerStyles, _show, _styles, children, footerComponent, headerProps, position, style } = this.props;
|
||||
const {
|
||||
_headerStyles,
|
||||
_show,
|
||||
_styles,
|
||||
children,
|
||||
footerComponent,
|
||||
headerProps,
|
||||
position,
|
||||
hideHeaderWithNavigation,
|
||||
style
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
@@ -119,6 +135,7 @@ class JitsiModal extends PureComponent<Props> {
|
||||
] }>
|
||||
<HeaderWithNavigation
|
||||
{ ...headerProps }
|
||||
hideHeaderWithNavigation = { hideHeaderWithNavigation }
|
||||
onPressBack = { this._onRequestClose } />
|
||||
<SafeAreaView style = { styles.safeArea }>
|
||||
{ children }
|
||||
|
||||
@@ -371,6 +371,7 @@ function _localParticipantLeft({ dispatch }, next, action) {
|
||||
function _maybePlaySounds({ getState, dispatch }, action) {
|
||||
const state = getState();
|
||||
const { startAudioMuted, disableJoinLeaveSounds } = state['features/base/config'];
|
||||
const { soundsParticipantJoined: joinSound, soundsParticipantLeft: leftSound } = state['features/base/settings'];
|
||||
|
||||
// If we have join/leave sounds disabled, don't play anything.
|
||||
if (disableJoinLeaveSounds) {
|
||||
@@ -387,13 +388,16 @@ function _maybePlaySounds({ getState, dispatch }, action) {
|
||||
const { isReplacing, isReplaced } = action.participant;
|
||||
|
||||
if (action.type === PARTICIPANT_JOINED) {
|
||||
if (!joinSound) {
|
||||
return;
|
||||
}
|
||||
const { presence } = action.participant;
|
||||
|
||||
// The sounds for the poltergeist are handled by features/invite.
|
||||
if (presence !== INVITED && presence !== CALLING && !isReplacing) {
|
||||
dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
|
||||
}
|
||||
} else if (action.type === PARTICIPANT_LEFT && !isReplaced) {
|
||||
} else if (action.type === PARTICIPANT_LEFT && !isReplaced && leftSound) {
|
||||
dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ type Props = {
|
||||
*/
|
||||
headerLabelKey: ?string,
|
||||
|
||||
/**
|
||||
* True if the header with navigation should be hidden, false otherwise.
|
||||
*/
|
||||
hideHeaderWithNavigation?: boolean,
|
||||
|
||||
/**
|
||||
* Callback to be invoked on pressing the back button.
|
||||
*/
|
||||
@@ -48,17 +53,18 @@ class HeaderWithNavigation extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { onPressBack, onPressForward } = this.props;
|
||||
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
|
||||
|
||||
return (
|
||||
<Header>
|
||||
{ onPressBack && <BackButton onPress = { onPressBack } /> }
|
||||
<HeaderLabel labelKey = { this.props.headerLabelKey } />
|
||||
{ onPressForward && <ForwardButton
|
||||
disabled = { this.props.forwardDisabled }
|
||||
labelKey = { this.props.forwardLabelKey }
|
||||
onPress = { onPressForward } /> }
|
||||
</Header>
|
||||
!hideHeaderWithNavigation
|
||||
&& <Header>
|
||||
{ onPressBack && <BackButton onPress = { onPressBack } /> }
|
||||
<HeaderLabel labelKey = { this.props.headerLabelKey } />
|
||||
{ onPressForward && <ForwardButton
|
||||
disabled = { this.props.forwardDisabled }
|
||||
labelKey = { this.props.forwardLabelKey }
|
||||
onPress = { onPressForward } /> }
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { isVpaasMeeting } from '../../../../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../../../../jaas/functions';
|
||||
import { translate } from '../../../i18n';
|
||||
import { connect } from '../../../redux';
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ const DEFAULT_STATE = {
|
||||
micDeviceId: undefined,
|
||||
serverURL: undefined,
|
||||
hideShareAudioHelper: false,
|
||||
soundsIncomingMessage: true,
|
||||
soundsParticipantJoined: true,
|
||||
soundsParticipantLeft: true,
|
||||
soundsTalkWhileMuted: true,
|
||||
startAudioOnly: false,
|
||||
startWithAudioMuted: false,
|
||||
startWithVideoMuted: false,
|
||||
|
||||
@@ -33,7 +33,8 @@ export const colors = {
|
||||
success04: '#189B55',
|
||||
success05: '#1EC26A',
|
||||
|
||||
warning05: '#F8AE1A'
|
||||
warning05: '#F8AE1A',
|
||||
warning06: '#ED9E1B'
|
||||
};
|
||||
|
||||
// Mapping between the token used and the color
|
||||
@@ -197,20 +198,24 @@ export const colorMap = {
|
||||
success02: 'success05',
|
||||
|
||||
// Color for warning messages applied to icons, borders & backgrounds
|
||||
warning01: 'warning05'
|
||||
warning01: 'warning05',
|
||||
|
||||
// Color for indicating a raised hand
|
||||
warning02: 'warning06'
|
||||
};
|
||||
|
||||
|
||||
export const font = {
|
||||
weightRegular: 400,
|
||||
weightSemiBold: 600
|
||||
weightRegular: '400',
|
||||
weightSemiBold: '600'
|
||||
};
|
||||
|
||||
export const shape = {
|
||||
borderRadius: 6
|
||||
borderRadius: 6,
|
||||
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)'
|
||||
};
|
||||
|
||||
export const spacing = [ 0, 4, 8, 16, 24, 32, 40, 48, 56 ];
|
||||
export const spacing = [ 0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80 ];
|
||||
|
||||
export const typography = {
|
||||
labelRegular: {
|
||||
|
||||
@@ -45,17 +45,18 @@ const _URI_PATH_PATTERN = '([^?#]*)';
|
||||
export const URI_PROTOCOL_PATTERN = '^([a-z][a-z0-9\\.\\+-]*:)';
|
||||
|
||||
/**
|
||||
* Excludes/removes certain characters from a specific room (name) which are
|
||||
* incompatible with Jitsi Meet on the client and/or server sides.
|
||||
* Excludes/removes certain characters from a specific path part which are
|
||||
* incompatible with Jitsi Meet on the client and/or server sides. The main
|
||||
* use case for this method is to clean up the room name and the tenant.
|
||||
*
|
||||
* @param {?string} room - The room (name) to fix.
|
||||
* @param {?string} pathPart - The path part to fix.
|
||||
* @private
|
||||
* @returns {?string}
|
||||
*/
|
||||
function _fixRoom(room: ?string) {
|
||||
return room
|
||||
? room.replace(new RegExp(_ROOM_EXCLUDE_PATTERN, 'g'), '')
|
||||
: room;
|
||||
function _fixPathPart(pathPart: ?string) {
|
||||
return pathPart
|
||||
? pathPart.replace(new RegExp(_ROOM_EXCLUDE_PATTERN, 'g'), '')
|
||||
: pathPart;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,6 +336,11 @@ export function parseURIString(uri: ?string) {
|
||||
|
||||
const obj = parseStandardURIString(_fixURIStringScheme(uri));
|
||||
|
||||
// XXX While the components/segments of pathname are URI encoded, Jitsi Meet
|
||||
// on the client and/or server sides still don't support certain characters.
|
||||
obj.pathname = obj.pathname.split('/').map(pathPart => _fixPathPart(pathPart))
|
||||
.join('/');
|
||||
|
||||
// Add the properties that are specific to a Jitsi Meet resource (location)
|
||||
// such as contextRoot, room:
|
||||
|
||||
@@ -344,24 +350,9 @@ export function parseURIString(uri: ?string) {
|
||||
// The room (name) is the last component/segment of pathname.
|
||||
const { pathname } = obj;
|
||||
|
||||
// XXX While the components/segments of pathname are URI encoded, Jitsi Meet
|
||||
// on the client and/or server sides still don't support certain characters.
|
||||
const contextRootEndIndex = pathname.lastIndexOf('/');
|
||||
let room = pathname.substring(contextRootEndIndex + 1) || undefined;
|
||||
|
||||
if (room) {
|
||||
const fixedRoom = _fixRoom(room);
|
||||
|
||||
if (fixedRoom !== room) {
|
||||
room = fixedRoom;
|
||||
|
||||
// XXX Drive fixedRoom into pathname (because room is derived from
|
||||
// pathname).
|
||||
obj.pathname
|
||||
= pathname.substring(0, contextRootEndIndex + 1) + (room || '');
|
||||
}
|
||||
}
|
||||
obj.room = room;
|
||||
obj.room = pathname.substring(contextRootEndIndex + 1) || undefined;
|
||||
|
||||
if (contextRootEndIndex > 1) {
|
||||
// The part of the pathname from the beginning to the room name is the tenant.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Action used to store the flag signaling the endpoint has been counted.
|
||||
*/
|
||||
export const SET_ENDPOINT_COUNTED = 'SET_ENDPOINT_COUNTED';
|
||||
@@ -1,42 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { SET_ENDPOINT_COUNTED } from './actionTypes';
|
||||
import { extractVpaasTenantFromPath, getBillingId, sendCountRequest } from './functions';
|
||||
|
||||
/**
|
||||
* Sends a billing count request when needed.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function countEndpoint() {
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
const state = getState();
|
||||
const baseUrl = state['features/base/config'].billingCounterUrl;
|
||||
const jwt = state['features/base/jwt'].jwt;
|
||||
const tenant = extractVpaasTenantFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
const shouldSendRequest = Boolean(baseUrl && jwt && tenant);
|
||||
|
||||
if (shouldSendRequest) {
|
||||
const billingId = getBillingId();
|
||||
|
||||
sendCountRequest({
|
||||
baseUrl,
|
||||
billingId,
|
||||
jwt,
|
||||
tenant
|
||||
});
|
||||
dispatch(setEndpointCounted());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to mark the endpoint as counted.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function setEndpointCounted() {
|
||||
return {
|
||||
type: SET_ENDPOINT_COUNTED
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* The key for the billing id stored in localStorage.
|
||||
*/
|
||||
export const BILLING_ID = 'jitsiMeetId';
|
||||
|
||||
/**
|
||||
* The prefix for the vpaas tenant.
|
||||
*/
|
||||
export const VPAAS_TENANT_PREFIX = 'vpaas-magic-cookie-';
|
||||
@@ -1,119 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { BILLING_ID, VPAAS_TENANT_PREFIX } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Returns the full vpaas tenant if available, given a path.
|
||||
*
|
||||
* @param {string} path - The meeting url path.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function extractVpaasTenantFromPath(path: string) {
|
||||
const [ , tenant ] = path.split('/');
|
||||
|
||||
if (tenant.startsWith(VPAAS_TENANT_PREFIX)) {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vpaas tenant.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getVpaasTenant(state: Object) {
|
||||
return extractVpaasTenantFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current meeting is a vpaas one.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @param {boolean} requiredJwt - Whether jwt is required or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isVpaasMeeting(state: Object, requiredJwt: boolean = true) {
|
||||
const { billingCounterUrl, iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
const jwtBoolean = requiredJwt ? Boolean(jwt) : true;
|
||||
|
||||
const isAllowed = iAmRecorder || iAmSipGateway || jwtBoolean;
|
||||
|
||||
return Boolean(
|
||||
billingCounterUrl
|
||||
&& extractVpaasTenantFromPath(
|
||||
state['features/base/connection'].locationURL.pathname)
|
||||
&& isAllowed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a billing counter request.
|
||||
*
|
||||
* @param {Object} reqData - The request info.
|
||||
* @param {string} reqData.baseUrl - The base url for the request.
|
||||
* @param {string} billingId - The unique id of the client.
|
||||
* @param {string} jwt - The JWT token.
|
||||
* @param {string} tenat - The client tenant.
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function sendCountRequest({ baseUrl, billingId, jwt, tenant }: {
|
||||
baseUrl: string,
|
||||
billingId: string,
|
||||
jwt: string,
|
||||
tenant: string
|
||||
}) {
|
||||
const fullUrl = `${baseUrl}/${encodeURIComponent(tenant)}/${billingId}`;
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${jwt}`
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(fullUrl, {
|
||||
method: 'GET',
|
||||
headers
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
logger.error('Status error:', res.status);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Could not send request', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored billing id (or generates a new one if none is present).
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getBillingId() {
|
||||
let billingId = jitsiLocalStorage.getItem(BILLING_ID);
|
||||
|
||||
if (!billingId) {
|
||||
billingId = uuid.v4();
|
||||
jitsiLocalStorage.setItem(BILLING_ID, billingId);
|
||||
}
|
||||
|
||||
return billingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the billing id for vpaas meetings.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export function getVpaasBillingId(state: Object) {
|
||||
if (isVpaasMeeting(state)) {
|
||||
return getBillingId();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
SET_ENDPOINT_COUNTED
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
endpointCounted: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that mutate the billing-counter state
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/billing-counter', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
|
||||
case SET_ENDPOINT_COUNTED: {
|
||||
return {
|
||||
...state,
|
||||
endpointCounted: true
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -22,11 +22,9 @@ import {
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { openDisplayNamePrompt } from '../display-name';
|
||||
import { ADD_REACTIONS_MESSAGE } from '../reactions/actionTypes';
|
||||
import {
|
||||
pushReaction
|
||||
} from '../reactions/actions.any';
|
||||
import { REACTIONS } from '../reactions/constants';
|
||||
import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
|
||||
import { pushReactions } from '../reactions/actions.any';
|
||||
import { getReactionMessageFromBuffer } from '../reactions/functions.any';
|
||||
import { endpointMessageReceived } from '../subtitles';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
import {
|
||||
@@ -158,13 +156,13 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case ADD_REACTIONS_MESSAGE: {
|
||||
case ADD_REACTION_MESSAGE: {
|
||||
_handleReceivedMessage(store, {
|
||||
id: localParticipant.id,
|
||||
message: action.message,
|
||||
privateMessage: false,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +210,6 @@ StateListenerRegistry.register(
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addChatMsgListener(conference, store) {
|
||||
const reactions = {};
|
||||
|
||||
if (store.getState()['features/base/config'].iAmRecorder) {
|
||||
// We don't register anything on web if we are in iAmRecorder mode
|
||||
return;
|
||||
@@ -252,30 +248,21 @@ function _addChatMsgListener(conference, store) {
|
||||
const [ { _id }, eventData ] = args;
|
||||
|
||||
if (eventData.name === ENDPOINT_REACTION_NAME) {
|
||||
reactions[_id] = reactions[_id] ?? {
|
||||
timeout: null,
|
||||
message: ''
|
||||
};
|
||||
batch(() => {
|
||||
store.dispatch(pushReaction(eventData.reaction));
|
||||
store.dispatch(setToolboxVisible(true));
|
||||
store.dispatch(setToolboxTimeout(
|
||||
() => store.dispatch(hideToolbox()),
|
||||
5000)
|
||||
);
|
||||
store.dispatch(pushReactions(eventData.reactions));
|
||||
});
|
||||
|
||||
clearTimeout(reactions[_id].timeout);
|
||||
reactions[_id].message = `${reactions[_id].message}${REACTIONS[eventData.reaction].message}`;
|
||||
reactions[_id].timeout = setTimeout(() => {
|
||||
_handleReceivedMessage(store, {
|
||||
id: _id,
|
||||
message: reactions[_id].message,
|
||||
privateMessage: false,
|
||||
timestamp: eventData.timestamp
|
||||
});
|
||||
delete reactions[_id];
|
||||
}, 500);
|
||||
_handleReceivedMessage(store, {
|
||||
id: _id,
|
||||
message: getReactionMessageFromBuffer(eventData.reactions),
|
||||
privateMessage: false,
|
||||
timestamp: eventData.timestamp
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -308,14 +295,20 @@ function _handleChatError({ dispatch }, error) {
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Object} message - The message object.
|
||||
* @param {boolean} shouldPlaySound - Whether or not to play the incoming message sound.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleReceivedMessage({ dispatch, getState }, { id, message, privateMessage, timestamp }) {
|
||||
function _handleReceivedMessage({ dispatch, getState },
|
||||
{ id, message, privateMessage, timestamp },
|
||||
shouldPlaySound = true
|
||||
) {
|
||||
// Logic for all platforms:
|
||||
const state = getState();
|
||||
const { isOpen: isChatOpen } = state['features/chat'];
|
||||
const { disableIncomingMessageSound } = state['features/base/config'];
|
||||
const { soundsIncomingMessage: soundEnabled } = state['features/base/settings'];
|
||||
|
||||
if (!isChatOpen) {
|
||||
if (!disableIncomingMessageSound && soundEnabled && shouldPlaySound && !isChatOpen) {
|
||||
dispatch(playSound(INCOMING_MSG_SOUND_ID));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { translate } from '../../base/i18n';
|
||||
import { Icon, IconClose } from '../../base/icons';
|
||||
import { browser } from '../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../base/redux';
|
||||
import { isVpaasMeeting } from '../../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../../jaas/functions';
|
||||
import logger from '../logger';
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList } from '../../../lobby';
|
||||
import { BackButtonRegistry } from '../../../mobile/back-button';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/native';
|
||||
import { Captions } from '../../../subtitles';
|
||||
import { setToolboxVisible } from '../../../toolbox/actions';
|
||||
import { Toolbox } from '../../../toolbox/components/native';
|
||||
@@ -71,6 +72,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_fullscreenEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines if the participants pane is open.
|
||||
*/
|
||||
_isParticipantsPaneOpen: boolean,
|
||||
|
||||
/**
|
||||
* The ID of the participant currently on stage (if any)
|
||||
*/
|
||||
@@ -237,6 +243,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_renderContent() {
|
||||
const {
|
||||
_connecting,
|
||||
_isParticipantsPaneOpen,
|
||||
_largeVideoParticipantId,
|
||||
_reducedUI,
|
||||
_shouldDisplayTileView
|
||||
@@ -296,11 +303,14 @@ class Conference extends AbstractConference<Props, *> {
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{ this._renderConferenceNotification() }
|
||||
|
||||
{ this._renderConferenceModals() }
|
||||
|
||||
{_shouldDisplayTileView && <Toolbox />}
|
||||
|
||||
{ _isParticipantsPaneOpen && <ParticipantsPane /> }
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -391,6 +401,7 @@ function _mapStateToProps(state) {
|
||||
membersOnly,
|
||||
leaving
|
||||
} = state['features/base/conference'];
|
||||
const { isOpen } = state['features/participants-pane'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
@@ -412,6 +423,7 @@ function _mapStateToProps(state) {
|
||||
_connecting: Boolean(connecting_),
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
|
||||
_isParticipantsPaneOpen: isOpen,
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
_reducedUI: reducedUI,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Filmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { KnockingParticipantList, LobbyScreen } from '../../../lobby';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/web';
|
||||
import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
|
||||
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
|
||||
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getParticipantCount } from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { E2EELabel } from '../../../e2ee';
|
||||
import { LocalRecordingLabel } from '../../../local-recording';
|
||||
import { RecordingLabel } from '../../../recording';
|
||||
import { getSessionStatusToShow, RecordingLabel } from '../../../recording';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { TranscribingLabel } from '../../../transcribing';
|
||||
import { VideoQualityLabel } from '../../../video-quality';
|
||||
@@ -38,6 +38,11 @@ type Props = {
|
||||
*/
|
||||
_hideConferenceTimer: boolean,
|
||||
|
||||
/**
|
||||
* Whether the recording label should be shown or not.
|
||||
*/
|
||||
_hideRecordingLabel: boolean,
|
||||
|
||||
/**
|
||||
* Whether the participant count should be shown or not.
|
||||
*/
|
||||
@@ -52,7 +57,20 @@ type Props = {
|
||||
/**
|
||||
* Indicates whether the component should be visible or not.
|
||||
*/
|
||||
_visible: boolean
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the recording label is visible.
|
||||
*/
|
||||
_recordingLabel: boolean
|
||||
};
|
||||
|
||||
const getLeftMargin = () => {
|
||||
const subjectContainerWidth = document.getElementById('subject-container')?.clientWidth ?? 0;
|
||||
const recContainerWidth = document.getElementById('rec-container')?.clientWidth ?? 0;
|
||||
const subjectDetailsContainer = document.getElementById('subject-details-container')?.clientWidth ?? 0;
|
||||
|
||||
return (subjectContainerWidth - recContainerWidth - subjectDetailsContainer) / 2;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -66,29 +84,46 @@ function ConferenceInfo(props: Props) {
|
||||
_hideConferenceNameAndTimer,
|
||||
_hideConferenceTimer,
|
||||
_showParticipantCount,
|
||||
_hideRecordingLabel,
|
||||
_subject,
|
||||
_fullWidth,
|
||||
_visible
|
||||
_visible,
|
||||
_recordingLabel
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className = { `subject ${_visible ? 'visible' : ''}` }>
|
||||
<div className = { `subject-info-container${_fullWidth ? ' subject-info-container--full-width' : ''}` }>
|
||||
{
|
||||
!_hideConferenceNameAndTimer
|
||||
&& <div className = 'subject-info'>
|
||||
{ _subject && <span className = 'subject-text'>{ _subject }</span>}
|
||||
{ !_hideConferenceTimer && <ConferenceTimer /> }
|
||||
</div>
|
||||
<div className = { `subject ${_recordingLabel ? 'recording' : ''} ${_visible ? 'visible' : ''}` }>
|
||||
<div
|
||||
className = { `subject-info-container${_fullWidth ? ' subject-info-container--full-width' : ''}` }
|
||||
id = 'subject-container'>
|
||||
{!_hideRecordingLabel && <div
|
||||
className = 'show-always'
|
||||
id = 'rec-container'
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
style = {{
|
||||
marginLeft: !_recordingLabel || _visible ? 0 : getLeftMargin()
|
||||
}}>
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
<LocalRecordingLabel />
|
||||
</div>
|
||||
}
|
||||
{ _showParticipantCount && <ParticipantsCount /> }
|
||||
<E2EELabel />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
<LocalRecordingLabel />
|
||||
<TranscribingLabel />
|
||||
<VideoQualityLabel />
|
||||
<InsecureRoomNameLabel />
|
||||
<div
|
||||
className = 'subject-details-container'
|
||||
id = 'subject-details-container'>
|
||||
{
|
||||
!_hideConferenceNameAndTimer
|
||||
&& <div className = 'subject-info'>
|
||||
{ _subject && <span className = 'subject-text'>{ _subject }</span>}
|
||||
{ !_hideConferenceTimer && <ConferenceTimer /> }
|
||||
</div>
|
||||
}
|
||||
{ _showParticipantCount && <ParticipantsCount /> }
|
||||
<E2EELabel />
|
||||
<TranscribingLabel />
|
||||
<VideoQualityLabel />
|
||||
<InsecureRoomNameLabel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -109,16 +144,30 @@ function ConferenceInfo(props: Props) {
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participantCount = getParticipantCount(state);
|
||||
const { hideConferenceTimer, hideConferenceSubject, hideParticipantsStats } = state['features/base/config'];
|
||||
const {
|
||||
hideConferenceTimer,
|
||||
hideConferenceSubject,
|
||||
hideParticipantsStats,
|
||||
hideRecordingLabel
|
||||
} = state['features/base/config'];
|
||||
const { clientWidth } = state['features/base/responsive-ui'];
|
||||
|
||||
const fileRecordingStatus = getSessionStatusToShow(state, JitsiRecordingConstants.mode.FILE);
|
||||
const streamRecordingStatus = getSessionStatusToShow(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const isFileRecording = fileRecordingStatus ? fileRecordingStatus !== JitsiRecordingConstants.status.OFF : false;
|
||||
const isStreamRecording = streamRecordingStatus
|
||||
? streamRecordingStatus !== JitsiRecordingConstants.status.OFF : false;
|
||||
const { isEngaged } = state['features/local-recording'];
|
||||
|
||||
return {
|
||||
_hideConferenceNameAndTimer: clientWidth < 300,
|
||||
_hideConferenceTimer: Boolean(hideConferenceTimer),
|
||||
_hideRecordingLabel: hideRecordingLabel,
|
||||
_fullWidth: state['features/video-layout'].tileViewEnabled,
|
||||
_showParticipantCount: participantCount > 2 && !hideParticipantsStats,
|
||||
_subject: hideConferenceSubject ? '' : getConferenceName(state),
|
||||
_visible: isToolboxVisible(state)
|
||||
_visible: isToolboxVisible(state),
|
||||
_recordingLabel: (isFileRecording || isStreamRecording || isEngaged) && !hideRecordingLabel
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { translateToHTML } from '../base/i18n';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import { getBackendSafePath, getJitsiMeetGlobalNS } from '../base/util';
|
||||
import { getVpaasBillingId } from '../billing-counter/functions';
|
||||
import { showWarningNotification } from '../notifications';
|
||||
import { createRnnoiseProcessor } from '../stream-effects/rnnoise';
|
||||
|
||||
@@ -85,7 +84,6 @@ export function getConferenceOptions(stateful) {
|
||||
options.applicationName = getName();
|
||||
options.getWiFiStatsMethod = getWiFiStatsMethod;
|
||||
options.createVADProcessor = createRnnoiseProcessor;
|
||||
options.billingId = getVpaasBillingId(state);
|
||||
|
||||
// Disable CallStats, if requessted.
|
||||
if (options.disableThirdPartyRequests) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { Platform } from '../base/react';
|
||||
import { URI_PROTOCOL_PATTERN } from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import {
|
||||
DeepLinkingDesktopPage,
|
||||
@@ -58,7 +58,7 @@ export function getDeepLinkingPage(state) {
|
||||
if (launchInWeb
|
||||
|| !room
|
||||
|| state['features/base/config'].disableDeepLinking
|
||||
|| (isVpaasMeeting(state) && !appScheme)) {
|
||||
|| (isVpaasMeeting(state) && (!appScheme || appScheme === 'com.8x8.meet'))) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name to be displayed within the badge.
|
||||
*/
|
||||
name: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
badge: {
|
||||
background: 'rgba(0, 0, 0, 0.6)',
|
||||
borderRadius: '3px',
|
||||
color: theme.palette.text01,
|
||||
maxWidth: '50%',
|
||||
overflow: 'hidden',
|
||||
padding: '2px 16px',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component that displays a name badge.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
const DisplayNameBadge = ({ name }: Props) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className = { classes.badge }>
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayNameBadge;
|
||||
@@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { getLargeVideoParticipant } from '../../../large-video/functions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { isLayoutTileView } from '../../../video-layout';
|
||||
|
||||
import DisplayNameBadge from './DisplayNameBadge';
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
badgeContainer: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
transition: 'margin-bottom 0.3s'
|
||||
},
|
||||
containerElevated: {
|
||||
marginBottom: theme.spacing(7)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Component that renders the dominant speaker's name as a badge above the toolbar in stage view.
|
||||
*
|
||||
* @returns {ReactElement|null}
|
||||
*/
|
||||
const DominantSpeakerName = () => {
|
||||
const classes = useStyles();
|
||||
const largeVideoParticipant = useSelector(getLargeVideoParticipant);
|
||||
const nameToDisplay = largeVideoParticipant?.name;
|
||||
const selectedId = largeVideoParticipant?.id;
|
||||
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const localId = localParticipant?.id;
|
||||
|
||||
const isTileView = useSelector(isLayoutTileView);
|
||||
const toolboxVisible = useSelector(isToolboxVisible);
|
||||
|
||||
if (nameToDisplay && selectedId !== localId && !isTileView) {
|
||||
return (
|
||||
<div
|
||||
className = { `${classes.badgeContainer}${toolboxVisible ? '' : ` ${classes.containerElevated}`}` }>
|
||||
<DisplayNameBadge name = { nameToDisplay } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default DominantSpeakerName;
|
||||
@@ -3,3 +3,4 @@
|
||||
export { default as DisplayName } from './DisplayName';
|
||||
export { default as DisplayNameLabel } from './DisplayNameLabel';
|
||||
export { default as DisplayNamePrompt } from './DisplayNamePrompt';
|
||||
export { default as DominantSpeakerName } from './DominantSpeakerName';
|
||||
|
||||
@@ -15,6 +15,15 @@ import {
|
||||
const STORE_NAME = 'features/dynamic-branding';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
|
||||
/**
|
||||
* The pool of avatar backgrounds.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
avatarBackgrounds: [],
|
||||
|
||||
/**
|
||||
* The custom background color for the LargeVideo.
|
||||
*
|
||||
@@ -112,10 +121,12 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl
|
||||
logoImageUrl,
|
||||
avatarBackgrounds
|
||||
} = action.value;
|
||||
|
||||
return {
|
||||
avatarBackgrounds,
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
defaultBranding,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../base/media';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
|
||||
import { appendSuffix } from '../display-name';
|
||||
import { shouldDisplayTileView } from '../video-layout';
|
||||
|
||||
@@ -45,12 +43,7 @@ StateListenerRegistry.register(
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/large-video'].participantId,
|
||||
/* listener */ (participantId, store) => {
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||
store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
|
||||
|
||||
if (videoTrack && videoTrack.videoType === VIDEO_TYPE.CAMERA) {
|
||||
APP.API.notifyOnStageParticipantChanged(participantId);
|
||||
}
|
||||
/* listener */ participantId => {
|
||||
APP.API.notifyOnStageParticipantChanged(participantId);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { Dispatch } from 'redux';
|
||||
|
||||
import { FEEDBACK_REQUEST_IN_PROGRESS } from '../../../modules/UI/UIErrors';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import {
|
||||
CANCEL_FEEDBACK,
|
||||
|
||||
@@ -137,7 +137,7 @@ export function clickOnVideo(n: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume for a thumnail's audio.
|
||||
* Sets the volume for a thumbnail's audio.
|
||||
*
|
||||
* @param {string} participantId - The participant ID asociated with the audio.
|
||||
* @param {string} volume - The volume level.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { getToolbarButtons } from '../../../base/config';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
@@ -17,7 +18,13 @@ import { showToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||
import { setFilmstripVisible, setVisibleRemoteParticipants } from '../../actions';
|
||||
import { TILE_HORIZONTAL_MARGIN, TILE_VERTICAL_MARGIN, TOOLBAR_HEIGHT } from '../../constants';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
TILE_HORIZONTAL_MARGIN,
|
||||
TILE_VERTICAL_MARGIN,
|
||||
TOOLBAR_HEIGHT,
|
||||
TOOLBAR_HEIGHT_MOBILE
|
||||
} from '../../constants';
|
||||
import { shouldRemoteVideosBeVisible } from '../../functions';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
@@ -485,10 +492,6 @@ function _mapStateToProps(state) {
|
||||
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
|
||||
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
|
||||
const { isOpen: shiftRight } = state['features/chat'];
|
||||
const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
|
||||
reduceHeight ? 'reduce-height' : ''
|
||||
} ${shiftRight ? 'shift-right' : ''}`.trim();
|
||||
const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}`;
|
||||
const {
|
||||
gridDimensions = {},
|
||||
filmstripHeight,
|
||||
@@ -496,12 +499,35 @@ function _mapStateToProps(state) {
|
||||
thumbnailSize: tileViewThumbnailSize
|
||||
} = state['features/filmstrip'].tileViewDimensions;
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
const availableSpace = clientHeight - filmstripHeight;
|
||||
let filmstripPadding = 0;
|
||||
|
||||
if (availableSpace > 0) {
|
||||
const paddingValue = TOOLBAR_HEIGHT_MOBILE - availableSpace;
|
||||
|
||||
if (paddingValue > 0) {
|
||||
filmstripPadding = paddingValue;
|
||||
}
|
||||
} else {
|
||||
filmstripPadding = TOOLBAR_HEIGHT_MOBILE;
|
||||
}
|
||||
|
||||
const collapseTileView = reduceHeight
|
||||
&& isMobileBrowser()
|
||||
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
|
||||
|
||||
const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
|
||||
reduceHeight ? 'reduce-height' : ''
|
||||
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''}`.trim();
|
||||
const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}`;
|
||||
let _thumbnailSize, remoteFilmstripHeight, remoteFilmstripWidth;
|
||||
|
||||
switch (_currentLayout) {
|
||||
case LAYOUTS.TILE_VIEW:
|
||||
_thumbnailSize = tileViewThumbnailSize;
|
||||
remoteFilmstripHeight = filmstripHeight;
|
||||
remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
|
||||
remoteFilmstripWidth = filmstripWidth;
|
||||
break;
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { isMobileBrowser } from '../../../../../react/features/base/environment/utils';
|
||||
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
|
||||
import { AudioLevelIndicator } from '../../../audio-level-indicator';
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
|
||||
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
|
||||
import {
|
||||
@@ -139,6 +139,11 @@ export type Props = {|
|
||||
*/
|
||||
_isCurrentlyOnLargeVideo: boolean,
|
||||
|
||||
/**
|
||||
* Whether we are currently running in a mobile browser.
|
||||
*/
|
||||
_isMobile: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether the participant is screen sharing.
|
||||
*/
|
||||
@@ -612,7 +617,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderFakeParticipant() {
|
||||
const { _participant: { avatarURL } } = this.props;
|
||||
const { _isMobile, _participant: { avatarURL } } = this.props;
|
||||
const styles = this._getStyles();
|
||||
const containerClassName = this._getContainerClassName();
|
||||
|
||||
@@ -621,8 +626,10 @@ class Thumbnail extends Component<Props, State> {
|
||||
className = { containerClassName }
|
||||
id = 'sharedVideoContainer'
|
||||
onClick = { this._onClick }
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
{ ...(_isMobile ? {} : {
|
||||
onMouseEnter: this._onMouseEnter,
|
||||
onMouseLeave: this._onMouseLeave
|
||||
}) }
|
||||
style = { styles.thumbnail }>
|
||||
{avatarURL ? (
|
||||
<img
|
||||
@@ -753,6 +760,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
const {
|
||||
_defaultLocalDisplayName,
|
||||
_disableLocalVideoFlip,
|
||||
_isMobile,
|
||||
_isScreenSharing,
|
||||
_localFlipX,
|
||||
_disableProfile,
|
||||
@@ -772,13 +780,17 @@ class Thumbnail extends Component<Props, State> {
|
||||
className = { containerClassName }
|
||||
id = 'localVideoContainer'
|
||||
onClick = { this._onClick }
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
{ ...(isMobileBrowser() ? {
|
||||
onTouchEnd: this._onTouchEnd,
|
||||
onTouchMove: this._onTouchMove,
|
||||
onTouchStart: this._onTouchStart
|
||||
} : {}) }
|
||||
{ ...(_isMobile
|
||||
? {
|
||||
onTouchEnd: this._onTouchEnd,
|
||||
onTouchMove: this._onTouchMove,
|
||||
onTouchStart: this._onTouchStart
|
||||
}
|
||||
: {
|
||||
onMouseEnter: this._onMouseEnter,
|
||||
onMouseLeave: this._onMouseLeave
|
||||
}
|
||||
) }
|
||||
style = { styles.thumbnail }>
|
||||
<div className = 'videocontainer__background' />
|
||||
<span id = 'localVideoWrapper'>
|
||||
@@ -875,6 +887,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
*/
|
||||
_renderRemoteParticipant() {
|
||||
const {
|
||||
_isMobile,
|
||||
_isTestModeEnabled,
|
||||
_participant,
|
||||
_startSilent,
|
||||
@@ -909,13 +922,17 @@ class Thumbnail extends Component<Props, State> {
|
||||
className = { containerClassName }
|
||||
id = { `participant_${id}` }
|
||||
onClick = { this._onClick }
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
{ ...(isMobileBrowser() ? {
|
||||
onTouchEnd: this._onTouchEnd,
|
||||
onTouchMove: this._onTouchMove,
|
||||
onTouchStart: this._onTouchStart
|
||||
} : {}) }
|
||||
{ ...(_isMobile
|
||||
? {
|
||||
onTouchEnd: this._onTouchEnd,
|
||||
onTouchMove: this._onTouchMove,
|
||||
onTouchStart: this._onTouchStart
|
||||
}
|
||||
: {
|
||||
onMouseEnter: this._onMouseEnter,
|
||||
onMouseLeave: this._onMouseLeave
|
||||
}
|
||||
) }
|
||||
style = { styles.thumbnail }>
|
||||
{
|
||||
_videoTrack && <VideoTrack
|
||||
@@ -1031,6 +1048,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
} = state['features/base/config'];
|
||||
const { NORMAL = 8 } = interfaceConfig.INDICATOR_FONT_SIZES || {};
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
const _isMobile = isMobileBrowser();
|
||||
|
||||
|
||||
switch (_currentLayout) {
|
||||
@@ -1072,7 +1090,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
return {
|
||||
_audioTrack,
|
||||
_connectionIndicatorAutoHideEnabled: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
|
||||
_connectionIndicatorDisabled: isMobileBrowser() || interfaceConfig.CONNECTION_INDICATOR_DISABLED,
|
||||
_connectionIndicatorDisabled: _isMobile || interfaceConfig.CONNECTION_INDICATOR_DISABLED,
|
||||
_currentLayout,
|
||||
_defaultLocalDisplayName: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
||||
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
||||
@@ -1081,6 +1099,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
||||
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
||||
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
|
||||
_isMobile,
|
||||
_isScreenSharing: _videoTrack?.videoType === 'desktop',
|
||||
_isTestModeEnabled: isTestModeEnabled(state),
|
||||
_isVideoPlayable: id && isVideoPlayable(state, id),
|
||||
|
||||
@@ -163,6 +163,11 @@ export const TILE_HORIZONTAL_MARGIN = 4;
|
||||
*/
|
||||
export const TOOLBAR_HEIGHT = 72;
|
||||
|
||||
/**
|
||||
* The height of the whole toolbar.
|
||||
*/
|
||||
export const TOOLBAR_HEIGHT_MOBILE = 60;
|
||||
|
||||
/**
|
||||
* The size of the horizontal border of a thumbnail.
|
||||
*
|
||||
|
||||
@@ -25,3 +25,4 @@ export function isFilmstripVisible(stateful: Object | Function) {
|
||||
|
||||
return getParticipantCountWithFake(state) > 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_STATE = {
|
||||
horizontalViewDimensions: {},
|
||||
|
||||
/**
|
||||
* The custom audio volume levels per perticipant.
|
||||
* The custom audio volume levels per participant.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
@@ -8,14 +8,15 @@ import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isVpaasMeeting } from '../../../../billing-counter/functions';
|
||||
import { isDynamicBrandingDataLoaded } from '../../../../dynamic-branding/functions';
|
||||
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
|
||||
import { isVpaasMeeting } from '../../../../jaas/functions';
|
||||
import { getActiveSession } from '../../../../recording';
|
||||
import { updateDialInNumbers } from '../../../actions';
|
||||
import {
|
||||
_getDefaultPhoneNumber,
|
||||
getInviteText,
|
||||
getInviteTextiOS,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled,
|
||||
sharingFeatures,
|
||||
@@ -62,6 +63,12 @@ type Props = {
|
||||
*/
|
||||
_invitationText: string,
|
||||
|
||||
/**
|
||||
* The custom no new-lines meeting invitation text for iOS default email.
|
||||
* Needed because of this mailto: iOS issue: https://developer.apple.com/forums/thread/681023
|
||||
*/
|
||||
_invitationTextiOS: string,
|
||||
|
||||
/**
|
||||
* An alternate app name to be displayed in the email subject.
|
||||
*/
|
||||
@@ -110,6 +117,7 @@ function AddPeopleDialog({
|
||||
_urlSharingVisible,
|
||||
_emailSharingVisible,
|
||||
_invitationText,
|
||||
_invitationTextiOS,
|
||||
_inviteAppName,
|
||||
_inviteContactsVisible,
|
||||
_inviteUrl,
|
||||
@@ -160,7 +168,8 @@ function AddPeopleDialog({
|
||||
_emailSharingVisible
|
||||
? <InviteByEmailSection
|
||||
inviteSubject = { inviteSubject }
|
||||
inviteText = { _invitationText } />
|
||||
inviteText = { _invitationText }
|
||||
inviteTextiOS = { _invitationTextiOS } />
|
||||
: null
|
||||
}
|
||||
{ _embedMeetingVisible && <EmbedMeetingTrigger /> }
|
||||
@@ -207,6 +216,9 @@ function mapStateToProps(state, ownProps) {
|
||||
_invitationText: getInviteText({ state,
|
||||
phoneNumber,
|
||||
t: ownProps.t }),
|
||||
_invitationTextiOS: getInviteTextiOS({ state,
|
||||
phoneNumber,
|
||||
t: ownProps.t }),
|
||||
_inviteAppName: inviteAppName,
|
||||
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
|
||||
_inviteUrl: getInviteURL(state),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { isIosMobileBrowser } from '../../../../base/environment/utils';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import {
|
||||
Icon,
|
||||
@@ -27,6 +28,11 @@ type Props = {
|
||||
*/
|
||||
inviteText: string,
|
||||
|
||||
/**
|
||||
* The encoded no new-lines iOS invitation text to be sent on default mail.
|
||||
*/
|
||||
inviteTextiOS: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -38,10 +44,13 @@ type Props = {
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
||||
function InviteByEmailSection({ inviteSubject, inviteText, inviteTextiOS, t }: Props) {
|
||||
const [ isActive, setIsActive ] = useState(false);
|
||||
const encodedInviteSubject = encodeURIComponent(inviteSubject);
|
||||
const encodedInviteText = encodeURIComponent(inviteText);
|
||||
const encodedInviteTextiOS = encodeURIComponent(inviteTextiOS);
|
||||
|
||||
const encodedDefaultEmailText = isIosMobileBrowser() ? encodedInviteTextiOS : encodedInviteText;
|
||||
|
||||
/**
|
||||
* Copies the conference invitation to the clipboard.
|
||||
@@ -100,7 +109,7 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
||||
{
|
||||
icon: IconEmail,
|
||||
tooltipKey: 'addPeople.defaultEmail',
|
||||
url: `mailto:?subject=${encodedInviteSubject}&body=${encodedInviteText}`
|
||||
url: `mailto:?subject=${encodedInviteSubject}&body=${encodedDefaultEmailText}`
|
||||
},
|
||||
{
|
||||
icon: IconGoogle,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Icon, IconPhone } from '../../../../base/icons';
|
||||
import { getLocalParticipant } from '../../../../base/participants';
|
||||
import { MultiSelectAutocomplete } from '../../../../base/react';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isVpaasMeeting } from '../../../../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../../../../jaas/functions';
|
||||
import { hideAddPeopleDialog } from '../../../actions';
|
||||
import { INVITE_TYPES } from '../../../constants';
|
||||
import AbstractAddPeopleDialog, {
|
||||
@@ -511,11 +511,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
||||
const supportString = t('inlineDialogFailure.supportMsg');
|
||||
const supportLink = interfaceConfig.SUPPORT_URL;
|
||||
|
||||
if (!supportLink) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const supportLinkContent = (
|
||||
const supportLinkContent = supportLink ? (
|
||||
<span>
|
||||
<span>
|
||||
{ supportString.padEnd(supportString.length + 1) }
|
||||
@@ -524,13 +520,14 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
||||
<a
|
||||
aria-label = { supportLink }
|
||||
href = { supportLink }
|
||||
rel = 'noopener noreferrer'>
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{ t('inlineDialogFailure.support') }
|
||||
</a>
|
||||
</span>
|
||||
<span>.</span>
|
||||
</span>
|
||||
);
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className = 'modal-dialog-form-error'>
|
||||
@@ -595,7 +592,7 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_footerTextEnabled: footerTextEnabled,
|
||||
_isVpaas: isVpaasMeeting(state, false)
|
||||
_isVpaas: isVpaasMeeting(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class DialInSummary extends Component<Props> {
|
||||
headerLabelKey: 'info.label'
|
||||
}}
|
||||
modalId = { DIAL_IN_SUMMARY_VIEW_ID }
|
||||
style = { styles.backDrop } >
|
||||
style = { styles.backDrop }>
|
||||
<WebView
|
||||
onError = { this._onError }
|
||||
onShouldStartLoadWithRequest = { this._onNavigate }
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { getActiveSession } from '../../features/recording/functions';
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import { doGetJSON, parseURIString } from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import { INVITE_TYPES, SIP_ADDRESS_REGEX } from './constants';
|
||||
import logger from './logger';
|
||||
@@ -251,6 +252,49 @@ export function getInviteResultsForQuery(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom no new lines message for iOS default mail describing how to dial in to the conference.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getInviteTextiOS({
|
||||
state,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Object) {
|
||||
if (!isIosMobileBrowser()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dialIn = state['features/invite'];
|
||||
const inviteUrl = getInviteURL(state);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localParticipantName = localParticipant?.name;
|
||||
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteTextiOSPersonal', { name: localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += ' ';
|
||||
|
||||
invite += t('info.inviteTextiOSInviteUrl', { inviteUrl });
|
||||
invite += ' ';
|
||||
|
||||
if (shouldDisplayDialIn(dialIn)) {
|
||||
invite += t('info.inviteTextiOSPhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID,
|
||||
didUrl: getDialInfoPageURL(state)
|
||||
});
|
||||
}
|
||||
invite += ' ';
|
||||
invite += t('info.inviteTextiOSJoinSilent', { silentUrl: `${inviteURL}#config.startSilent=true` });
|
||||
|
||||
return invite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message describing how to dial in to the conference.
|
||||
*
|
||||
@@ -271,7 +315,6 @@ export function getInviteText({
|
||||
const localParticipantName = localParticipant?.name;
|
||||
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { VPAAS_TENANT_PREFIX } from '../billing-counter/constants';
|
||||
import { getVpaasTenant } from '../billing-counter/functions';
|
||||
|
||||
import { SET_DETAILS } from './actionTypes';
|
||||
import { PremiumFeatureDialog } from './components';
|
||||
import { isFeatureDisabled, sendGetDetailsRequest } from './functions';
|
||||
import { VPAAS_TENANT_PREFIX } from './constants';
|
||||
import { getVpaasTenant, isFeatureDisabled, sendGetDetailsRequest } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,3 +23,8 @@ export const FEATURES = {
|
||||
* URL for displaying JaaS upgrade options
|
||||
*/
|
||||
export const JAAS_UPGRADE_URL = 'https://jaas.8x8.vc/#/plan/upgrade';
|
||||
|
||||
/**
|
||||
* The prefix for the vpaas tenant.
|
||||
*/
|
||||
export const VPAAS_TENANT_PREFIX = 'vpaas-magic-cookie-';
|
||||
|
||||
@@ -1,9 +1,53 @@
|
||||
// @flow
|
||||
|
||||
import { getVpaasTenant } from '../billing-counter/functions';
|
||||
|
||||
import { VPAAS_TENANT_PREFIX } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Returns the full vpaas tenant if available, given a path.
|
||||
*
|
||||
* @param {string} path - The meeting url path.
|
||||
* @returns {string}
|
||||
*/
|
||||
function extractVpaasTenantFromPath(path: string) {
|
||||
const [ , tenant ] = path.split('/');
|
||||
|
||||
if (tenant.startsWith(VPAAS_TENANT_PREFIX)) {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vpaas tenant.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getVpaasTenant(state: Object) {
|
||||
return extractVpaasTenantFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current meeting is a vpaas one.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isVpaasMeeting(state: Object) {
|
||||
const connection = state['features/base/connection'];
|
||||
|
||||
if (connection?.locationURL?.pathname) {
|
||||
return Boolean(
|
||||
extractVpaasTenantFromPath(connection?.locationURL?.pathname)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request for retrieving jaas customer details.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { sendAnalytics, createVpaasConferenceJoinedEvent } from '../analytics';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { PARTICIPANT_JOINED } from '../base/participants/actionTypes';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { countEndpoint } from './actions';
|
||||
import { isVpaasMeeting, extractVpaasTenantFromPath } from './functions';
|
||||
import { isVpaasMeeting, getVpaasTenant } from './functions';
|
||||
|
||||
/**
|
||||
* The redux middleware for billing counter.
|
||||
@@ -20,17 +18,6 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_JOINED: {
|
||||
const shouldCount = !store.getState()['features/billing-counter'].endpointCounted
|
||||
&& !action.participant.local;
|
||||
|
||||
if (shouldCount) {
|
||||
store.dispatch(countEndpoint());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -45,7 +32,6 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
function _maybeTrackVpaasConferenceJoin(state) {
|
||||
if (isVpaasMeeting(state)) {
|
||||
sendAnalytics(createVpaasConferenceJoinedEvent(
|
||||
extractVpaasTenantFromPath(
|
||||
state['features/base/connection'].locationURL.pathname)));
|
||||
getVpaasTenant(state)));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
import { redirectToStaticPage } from '../app/actions';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { CONNECTION_FAILED } from '../base/connection';
|
||||
import { JitsiConnectionErrors } from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
|
||||
import { SET_DETAILS } from './actionTypes';
|
||||
import { getCustomerDetails } from './actions';
|
||||
import { STATUSES } from './constants';
|
||||
import { isVpaasMeeting } from './functions';
|
||||
|
||||
/**
|
||||
* The redux middleware for billing counter.
|
||||
* The redux middleware for jaas.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
@@ -20,6 +23,22 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case CONNECTION_FAILED: {
|
||||
const { error } = action;
|
||||
|
||||
if (!isVpaasMeeting(store.getState()) || !error) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (error.name === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
if (error.message !== 'could not obtain public key') {
|
||||
break;
|
||||
}
|
||||
|
||||
store.dispatch(redirectToStaticPage('/static/planLimit.html'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SET_DETAILS: {
|
||||
const { status } = action.payload;
|
||||
|
||||
|
||||
15
react/features/large-video/functions.js
Normal file
15
react/features/large-video/functions.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// @flow
|
||||
|
||||
import { getParticipantById } from '../base/participants';
|
||||
|
||||
/**
|
||||
* Selector for the participant currently displaying on the large video.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getLargeVideoParticipant(state: Object) {
|
||||
const { participantId } = state['features/large-video'];
|
||||
|
||||
return getParticipantById(state, participantId);
|
||||
}
|
||||
@@ -1,28 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
|
||||
/**
|
||||
* Approves (lets in) or rejects a knocking participant.
|
||||
*
|
||||
* @param {Function} getState - Function to get the Redux state.
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approved - True if the participant is approved, false otherwise.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setKnockingParticipantApproval(getState: Function, id: string, approved: boolean) {
|
||||
const conference = getCurrentConference(getState());
|
||||
|
||||
if (conference) {
|
||||
if (approved) {
|
||||
conference.lobbyApproveAccess(id);
|
||||
} else {
|
||||
conference.lobbyDenyAccess(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Selector to return lobby state.
|
||||
*
|
||||
@@ -32,3 +9,16 @@ export function setKnockingParticipantApproval(getState: Function, id: string, a
|
||||
export function getLobbyState(state: any) {
|
||||
return state['features/lobby'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Selector to return array with knocking participant ids.
|
||||
*
|
||||
* @param {any} state - State object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getKnockingParticipantsById(state: any) {
|
||||
const { knockingParticipants } = state['features/lobby'];
|
||||
|
||||
return knockingParticipants.map(participant => participant.id);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
}
|
||||
];
|
||||
|
||||
if (!hideErrorSupportLink) {
|
||||
if (!hideErrorSupportLink && interfaceConfig.SUPPORT_URL) {
|
||||
buttons.push({
|
||||
content: this.props.t('dialog.contactSupport'),
|
||||
onClick: this._onOpenSupportLink
|
||||
|
||||
@@ -7,3 +7,8 @@ export const PARTICIPANTS_PANE_CLOSE = 'PARTICIPANTS_PANE_CLOSE';
|
||||
* Action type to signal the opening of the participants pane.
|
||||
*/
|
||||
export const PARTICIPANTS_PANE_OPEN = 'PARTICIPANTS_PANE_OPEN';
|
||||
|
||||
/**
|
||||
* Action type to set the volume of the participant.
|
||||
*/
|
||||
export const SET_VOLUME = 'SET_VOLUME';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
PARTICIPANTS_PANE_CLOSE,
|
||||
PARTICIPANTS_PANE_OPEN
|
||||
50
react/features/participants-pane/actions.native.js
Normal file
50
react/features/participants-pane/actions.native.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
|
||||
import { SET_VOLUME } from './actionTypes';
|
||||
import {
|
||||
ContextMenuMeetingParticipantDetails,
|
||||
ContextMenuLobbyParticipantReject
|
||||
} from './components/native';
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Displays the context menu for the selected lobby participant.
|
||||
*
|
||||
* @param {Object} participant - The selected lobby participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showContextMenuReject(participant: Object) {
|
||||
return openDialog(ContextMenuLobbyParticipantReject, { participant });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Displays the context menu for the selected meeting participant.
|
||||
*
|
||||
* @param {string} participantID - The selected meeting participant id.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showContextMenuDetails(participantID: String) {
|
||||
return openDialog(ContextMenuMeetingParticipantDetails, { participantID });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume.
|
||||
*
|
||||
* @param {string} participantId - The participant ID associated with the audio.
|
||||
* @param {string} volume - The volume level.
|
||||
* @returns {{
|
||||
* type: SET_VOLUME,
|
||||
* participantId: string,
|
||||
* volume: number
|
||||
* }}
|
||||
*/
|
||||
export function setVolume(participantId: string, volume: number) {
|
||||
return {
|
||||
type: SET_VOLUME,
|
||||
participantId,
|
||||
volume
|
||||
};
|
||||
}
|
||||
3
react/features/participants-pane/actions.web.js
Normal file
3
react/features/participants-pane/actions.web.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './actions.any';
|
||||
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { approveParticipant } from '../../av-moderation/actions';
|
||||
|
||||
import { QuickActionButton } from './styled';
|
||||
import { QuickActionButton } from './web/styled';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { MuteEveryonesVideoDialog } from '../../video-menu/components';
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuItem
|
||||
} from './styled';
|
||||
} from './web/styled';
|
||||
|
||||
const useStyles = makeStyles(() => {
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { QUICK_ACTION_BUTTON } from '../constants';
|
||||
|
||||
import AskToUnmuteButton from './AskToUnmuteButton';
|
||||
import { QuickActionButton } from './styled';
|
||||
import { QuickActionButton } from './web/styled';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { Divider, Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { hideDialog } from '../../../base/dialog';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import {
|
||||
Icon, IconClose
|
||||
} from '../../../base/icons';
|
||||
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
||||
import { getKnockingParticipantsById } from '../../../lobby/functions';
|
||||
|
||||
import styles from './styles';
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Participant reference
|
||||
*/
|
||||
participant: Object
|
||||
};
|
||||
|
||||
const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const knockParticipantsIDArr = useSelector(getKnockingParticipantsById);
|
||||
const knockParticipantIsAvailable = knockParticipantsIDArr.find(knockPartId => knockPartId === p.id);
|
||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||
const displayName = p.name;
|
||||
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
addScrollViewPadding = { false }
|
||||
onCancel = { cancel }
|
||||
showSlidingView = { Boolean(knockParticipantIsAvailable) }
|
||||
style = { styles.contextMenuMore }>
|
||||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { p.id }
|
||||
size = { 20 } />
|
||||
<View style = { styles.contextMenuItemAvatarText }>
|
||||
<Text style = { styles.contextMenuItemName }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style = { styles.divider } />
|
||||
<TouchableOpacity
|
||||
onPress = { reject }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconClose } />
|
||||
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
|
||||
</TouchableOpacity>
|
||||
</BottomSheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenuLobbyParticipantReject;
|
||||
@@ -0,0 +1,264 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { Divider, Text } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { hideDialog, openDialog } from '../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import {
|
||||
Icon, IconCloseCircle, IconMessage,
|
||||
IconMicrophoneEmptySlash,
|
||||
IconMuteEveryoneElse, IconVideoOff
|
||||
} from '../../../base/icons';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantByIdOrUndefined,
|
||||
getParticipantDisplayName, getRemoteParticipants,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantVideoMuted
|
||||
} from '../../../base/tracks/functions';
|
||||
import { openChat } from '../../../chat/actions.native';
|
||||
import {
|
||||
KickRemoteParticipantDialog,
|
||||
MuteEveryoneDialog,
|
||||
MuteRemoteParticipantDialog,
|
||||
MuteRemoteParticipantsVideoDialog
|
||||
} from '../../../video-menu';
|
||||
import VolumeSlider from '../../../video-menu/components/native/VolumeSlider';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The display name of the participant.
|
||||
*/
|
||||
_displayName: string,
|
||||
|
||||
/**
|
||||
* True if the local participant is moderator and false otherwise.
|
||||
*/
|
||||
_isLocalModerator: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is moderator and false otherwise.
|
||||
*/
|
||||
_isParticipantModerator: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is video muted and false otherwise.
|
||||
*/
|
||||
_isParticipantVideoMuted: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is audio muted and false otherwise.
|
||||
*/
|
||||
_isParticipantAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether the participant is present in the room or not.
|
||||
*/
|
||||
_isParticipantIDAvailable?: boolean,
|
||||
|
||||
/**
|
||||
* Participant reference
|
||||
*/
|
||||
_participant: Object,
|
||||
|
||||
/**
|
||||
* The ID of the participant.
|
||||
*/
|
||||
participantID: string,
|
||||
};
|
||||
|
||||
const ContextMenuMeetingParticipantDetails = (
|
||||
{
|
||||
_displayName,
|
||||
_isLocalModerator,
|
||||
_isParticipantVideoMuted,
|
||||
_isParticipantAudioMuted,
|
||||
_participant,
|
||||
_isParticipantIDAvailable,
|
||||
participantID
|
||||
}: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||
const kickRemoteParticipant = useCallback(() => {
|
||||
dispatch(openDialog(KickRemoteParticipantDialog, {
|
||||
participantID
|
||||
}));
|
||||
}, [ dispatch, participantID ]);
|
||||
const muteAudio = useCallback(() => {
|
||||
dispatch(openDialog(MuteRemoteParticipantDialog, {
|
||||
participantID
|
||||
}));
|
||||
}, [ dispatch, participantID ]);
|
||||
const muteEveryoneElse = useCallback(() => {
|
||||
dispatch(openDialog(MuteEveryoneDialog, {
|
||||
exclude: [ participantID ]
|
||||
}));
|
||||
}, [ dispatch, participantID ]);
|
||||
const muteVideo = useCallback(() => {
|
||||
dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
|
||||
participantID
|
||||
}));
|
||||
}, [ dispatch, participantID ]);
|
||||
|
||||
const sendPrivateMessage = useCallback(() => {
|
||||
dispatch(hideDialog());
|
||||
dispatch(openChat(_participant));
|
||||
}, [ dispatch, _participant ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
addScrollViewPadding = { false }
|
||||
onCancel = { cancel }
|
||||
showSlidingView = { _isParticipantIDAvailable }
|
||||
style = { styles.contextMenuMeetingParticipantDetails }>
|
||||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { participantID }
|
||||
size = { 20 } />
|
||||
<View style = { styles.contextMenuItemAvatarText }>
|
||||
<Text style = { styles.contextMenuItemName }>
|
||||
{ _displayName }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style = { styles.divider } />
|
||||
{
|
||||
_isLocalModerator && (
|
||||
<>
|
||||
{
|
||||
!_isParticipantAudioMuted
|
||||
&& <TouchableOpacity
|
||||
onPress = { muteAudio }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconMicrophoneEmptySlash } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{ t('participantsPane.actions.mute') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress = { muteEveryoneElse }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconMuteEveryoneElse } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{ t('participantsPane.actions.muteEveryoneElse') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Divider style = { styles.divider } />
|
||||
{
|
||||
_isLocalModerator && (
|
||||
<>
|
||||
{
|
||||
!_isParticipantVideoMuted
|
||||
&& <TouchableOpacity
|
||||
onPress = { muteVideo }
|
||||
style = { styles.contextMenuItemSection }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconVideoOff } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{ t('participantsPane.actions.stopVideo') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress = { kickRemoteParticipant }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconCloseCircle } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{ t('videothumbnail.kick') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<TouchableOpacity
|
||||
onPress = { sendPrivateMessage }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconMessage } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{ t('toolbar.accessibilityLabel.privateMessage') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
{/* We need design specs for this*/}
|
||||
{/* <TouchableOpacity*/}
|
||||
{/* style = { styles.contextMenuItemSection }>*/}
|
||||
{/* <Icon*/}
|
||||
{/* size = { 20 }*/}
|
||||
{/* src = { IconConnectionActive }*/}
|
||||
{/* style = { styles.contextMenuItemIcon } />*/}
|
||||
{/* <Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>*/}
|
||||
{/* </TouchableOpacity>*/}
|
||||
<Divider style = { styles.divider } />
|
||||
<VolumeSlider participantID = { participantID } />
|
||||
</BottomSheet>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The own props of the component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps): Object {
|
||||
const { participantID } = ownProps;
|
||||
const participantIDS = [];
|
||||
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
const _isLocalModerator = isLocalParticipantModerator(state);
|
||||
const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const remoteParticipants = getRemoteParticipants(state);
|
||||
|
||||
localParticipant && participantIDS.push(localParticipant?.id);
|
||||
|
||||
remoteParticipants.forEach(p => {
|
||||
participantIDS.push(p?.id);
|
||||
});
|
||||
|
||||
const isParticipantIDAvailable = participantIDS.find(partID => partID === participantID);
|
||||
|
||||
return {
|
||||
_displayName: getParticipantDisplayName(state, participantID),
|
||||
_isLocalModerator,
|
||||
_isParticipantAudioMuted,
|
||||
_isParticipantIDAvailable: Boolean(isParticipantIDAvailable),
|
||||
_isParticipantVideoMuted,
|
||||
_participant: participant
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(ContextMenuMeetingParticipantDetails);
|
||||
@@ -0,0 +1,65 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { openDialog, hideDialog } from '../../../base/dialog/actions';
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
import {
|
||||
Icon, IconMicDisabledHollow,
|
||||
IconVideoOff
|
||||
} from '../../../base/icons';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantCount
|
||||
} from '../../../base/participants';
|
||||
import { BlockAudioVideoDialog } from '../../../video-menu';
|
||||
import MuteEveryonesVideoDialog
|
||||
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
export const ContextMenuMore = () => {
|
||||
const dispatch = useDispatch();
|
||||
const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
|
||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||
const { id } = useSelector(getLocalParticipant);
|
||||
const participantsCount = useSelector(getParticipantCount);
|
||||
const showSlidingView = participantsCount > 2;
|
||||
const muteAllVideo = useCallback(() =>
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog,
|
||||
{ exclude: [ id ] })),
|
||||
[ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
addScrollViewPadding = { false }
|
||||
onCancel = { cancel }
|
||||
showSlidingView = { showSlidingView }
|
||||
style = { styles.contextMenuMore }>
|
||||
<TouchableOpacity
|
||||
onPress = { muteAllVideo }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconVideoOff } />
|
||||
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress = { blockAudioVideo }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconMicDisabledHollow }
|
||||
style = { styles.contextMenuIcon } />
|
||||
<Text style = { styles.contextMenuItemText }>
|
||||
{t('participantsPane.actions.blockEveryoneMicCamera')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</BottomSheet>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { approveKnockingParticipant } from '../../../lobby/actions.native';
|
||||
import { showContextMenuReject } from '../../actions.native';
|
||||
import { MEDIA_STATE } from '../../constants';
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Participant reference
|
||||
*/
|
||||
participant: Object
|
||||
};
|
||||
|
||||
export const LobbyParticipantItem = ({ participant: p }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const admit = useCallback(() => dispatch(approveKnockingParticipant(p.id), [ dispatch ]));
|
||||
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ParticipantItem
|
||||
audioMediaState = { MEDIA_STATE.NONE }
|
||||
displayName = { p.name }
|
||||
isKnockingParticipant = { true }
|
||||
local = { p.local }
|
||||
onPress = { openContextMenuReject }
|
||||
participant = { p }
|
||||
participantID = { p.id }
|
||||
raisedHand = { p.raisedHand }
|
||||
videoMediaState = { MEDIA_STATE.NONE }>
|
||||
<Button
|
||||
children = { t('lobby.admit') }
|
||||
contentStyle = { styles.participantActionsButtonContent }
|
||||
labelStyle = { styles.participantActionsButtonText }
|
||||
mode = 'contained'
|
||||
onPress = { admit }
|
||||
style = { styles.participantActionsButtonAdmit } />
|
||||
</ParticipantItem>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { admitMultiple } from '../../../lobby/actions.native';
|
||||
import { getLobbyState } from '../../../lobby/functions';
|
||||
|
||||
import { LobbyParticipantItem } from './LobbyParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
export const LobbyParticipantList = () => {
|
||||
const {
|
||||
lobbyEnabled,
|
||||
knockingParticipants: participants
|
||||
} = useSelector(getLobbyState);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const admitAll = useCallback(() =>
|
||||
dispatch(admitMultiple(participants)),
|
||||
[ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!lobbyEnabled || !participants.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.lobbyList }>
|
||||
<View style = { styles.lobbyListDetails } >
|
||||
<Text style = { styles.lobbyListDescription }>
|
||||
{t('participantsPane.headings.waitingLobby',
|
||||
{ count: participants.length })}
|
||||
</Text>
|
||||
<Button
|
||||
color = '#3D3D3D'
|
||||
labelStyle = { styles.admitAllParticipantsActionButtonLabel }
|
||||
mode = 'text'
|
||||
onPress = { admitAll }
|
||||
style = { styles.admitAllParticipantsActionButton }>
|
||||
{t('lobby.admitAll')}
|
||||
</Button>
|
||||
</View>
|
||||
{
|
||||
participants.map(p => (
|
||||
<LobbyParticipantItem
|
||||
key = { p.id }
|
||||
participant = { p } />)
|
||||
)
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
getParticipantByIdOrUndefined,
|
||||
getParticipantDisplayName
|
||||
} from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isParticipantAudioMuted,
|
||||
isParticipantVideoMuted
|
||||
} from '../../../base/tracks';
|
||||
import { MEDIA_STATE } from '../../constants';
|
||||
import type { MediaState } from '../../constants';
|
||||
import { getParticipantAudioMediaState } from '../../functions';
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Media state for audio.
|
||||
*/
|
||||
_audioMediaState: MediaState,
|
||||
|
||||
/**
|
||||
* The display name of the participant.
|
||||
*/
|
||||
_displayName: string,
|
||||
|
||||
/**
|
||||
* True if the participant is video muted.
|
||||
*/
|
||||
_isVideoMuted: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is the local participant.
|
||||
*/
|
||||
_local: boolean,
|
||||
|
||||
/**
|
||||
* The participant ID.
|
||||
*/
|
||||
_participantID: string,
|
||||
|
||||
/**
|
||||
* True if the participant have raised hand.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
|
||||
/**
|
||||
* Callback to invoke when item is pressed.
|
||||
*/
|
||||
onPress: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant.
|
||||
*/
|
||||
participantID: ?string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the MeetingParticipantItem component.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function MeetingParticipantItem({
|
||||
_audioMediaState,
|
||||
_displayName,
|
||||
_isVideoMuted,
|
||||
_local,
|
||||
_participantID,
|
||||
_raisedHand,
|
||||
onPress
|
||||
}: Props) {
|
||||
return (
|
||||
<ParticipantItem
|
||||
audioMediaState = { _audioMediaState }
|
||||
displayName = { _displayName }
|
||||
isKnockingParticipant = { false }
|
||||
local = { _local }
|
||||
onPress = { onPress }
|
||||
participantID = { _participantID }
|
||||
raisedHand = { _raisedHand }
|
||||
videoMediaState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props for this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The own props of the component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state, ownProps): Object {
|
||||
const { participantID } = ownProps;
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||
const isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||
const audioMediaState = getParticipantAudioMediaState(
|
||||
participant, _isAudioMuted, state
|
||||
);
|
||||
|
||||
return {
|
||||
_audioMediaState: audioMediaState,
|
||||
_displayName: getParticipantDisplayName(state, participant?.id),
|
||||
_isAudioMuted,
|
||||
_isVideoMuted: isVideoMuted,
|
||||
_local: Boolean(participant?.local),
|
||||
_participantID: participant?.id,
|
||||
_raisedHand: Boolean(participant?.raisedHand)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default translate(connect(mapStateToProps)(MeetingParticipantItem));
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantCountWithFake,
|
||||
getRemoteParticipants
|
||||
} from '../../../base/participants';
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
import { showContextMenuDetails } from '../../actions.native';
|
||||
import { shouldRenderInviteButton } from '../../functions';
|
||||
|
||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||
import styles from './styles';
|
||||
|
||||
export const MeetingParticipantList = () => {
|
||||
const dispatch = useDispatch();
|
||||
const items = [];
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
|
||||
const participants = useSelector(getRemoteParticipants);
|
||||
const participantsCount = useSelector(getParticipantCountWithFake);
|
||||
const showInviteButton = useSelector(shouldRenderInviteButton);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
const renderParticipant = id => (
|
||||
<MeetingParticipantItem
|
||||
key = { id }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
onPress = { () => dispatch(showContextMenuDetails(id)) }
|
||||
participantID = { id } />
|
||||
);
|
||||
|
||||
localParticipant && items.push(renderParticipant(localParticipant?.id));
|
||||
|
||||
participants.forEach(p => {
|
||||
items.push(renderParticipant(p?.id));
|
||||
});
|
||||
|
||||
return (
|
||||
<View style = { styles.meetingList }>
|
||||
<Text style = { styles.meetingListDescription }>
|
||||
{t('participantsPane.headings.participantsList',
|
||||
{ count: participantsCount })}
|
||||
</Text>
|
||||
{
|
||||
showInviteButton
|
||||
&& <Button
|
||||
children = { t('participantsPane.actions.invite') }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
icon = { () =>
|
||||
(<Icon
|
||||
size = { 20 }
|
||||
src = { IconInviteMore } />)
|
||||
}
|
||||
labelStyle = { styles.inviteLabel }
|
||||
mode = 'contained'
|
||||
onPress = { onInvite }
|
||||
style = { styles.inviteButton } />
|
||||
}
|
||||
{ items }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants';
|
||||
|
||||
import { RaisedHandIndicator } from './RaisedHandIndicator';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Media state for audio
|
||||
*/
|
||||
audioMediaState: MediaState,
|
||||
|
||||
/**
|
||||
* React children
|
||||
*/
|
||||
children?: Node,
|
||||
|
||||
/**
|
||||
* The name of the participant. Used for showing lobby names.
|
||||
*/
|
||||
displayName: string,
|
||||
|
||||
/**
|
||||
* Is the participant waiting?
|
||||
*/
|
||||
isKnockingParticipant: boolean,
|
||||
|
||||
/**
|
||||
* True if the participant is local.
|
||||
*/
|
||||
local: boolean,
|
||||
|
||||
/**
|
||||
* Callback to be invoked on pressing the participant item.
|
||||
*/
|
||||
onPress?: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* True if the participant have raised hand.
|
||||
*/
|
||||
raisedHand: boolean,
|
||||
|
||||
/**
|
||||
* Media state for video
|
||||
*/
|
||||
videoMediaState: MediaState
|
||||
}
|
||||
|
||||
/**
|
||||
* Participant item.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function ParticipantItem({
|
||||
children,
|
||||
displayName,
|
||||
isKnockingParticipant,
|
||||
local,
|
||||
onPress,
|
||||
participantID,
|
||||
raisedHand,
|
||||
audioMediaState = MEDIA_STATE.NONE,
|
||||
videoMediaState = MEDIA_STATE.NONE
|
||||
}: Props) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style = { styles.participantContainer } >
|
||||
<TouchableOpacity
|
||||
onPress = { onPress }
|
||||
style = { styles.participantContent }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { participantID }
|
||||
size = { 32 } />
|
||||
<View style = { styles.participantNameContainer }>
|
||||
<Text style = { styles.participantName }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
{ local ? <Text style = { styles.isLocal }>({t('chat.you')})</Text> : null }
|
||||
</View>
|
||||
{
|
||||
!isKnockingParticipant
|
||||
&& <>
|
||||
{
|
||||
raisedHand && <RaisedHandIndicator />
|
||||
}
|
||||
<View style = { styles.participantStatesContainer }>
|
||||
<View style = { styles.participantStateVideo }>{VideoStateIcons[videoMediaState]}</View>
|
||||
<View>{AudioStateIcons[audioMediaState]}</View>
|
||||
</View>
|
||||
</>
|
||||
}
|
||||
</TouchableOpacity>
|
||||
{ !local && children }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default ParticipantItem;
|
||||
@@ -0,0 +1,91 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
|
||||
import { JitsiModal } from '../../../base/modal';
|
||||
import {
|
||||
getParticipantCount,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import MuteEveryoneDialog
|
||||
from '../../../video-menu/components/native/MuteEveryoneDialog';
|
||||
import { close } from '../../actions.native';
|
||||
|
||||
import { ContextMenuMore } from './ContextMenuMore';
|
||||
import { LobbyParticipantList } from './LobbyParticipantList';
|
||||
import { MeetingParticipantList } from './MeetingParticipantList';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Participant pane.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
const ParticipantsPane = () => {
|
||||
const dispatch = useDispatch();
|
||||
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
|
||||
const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const participantsCount = useSelector(getParticipantCount);
|
||||
const showContextMenu = participantsCount > 2;
|
||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
||||
[ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<JitsiModal
|
||||
hideHeaderWithNavigation = { true }
|
||||
style = { styles.participantsPane }>
|
||||
<View style = { styles.header }>
|
||||
<Button
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
icon = { () =>
|
||||
(<Icon
|
||||
size = { 20 }
|
||||
src = { IconClose } />)
|
||||
}
|
||||
labelStyle = { styles.closeIcon }
|
||||
mode = 'contained'
|
||||
onPress = { closePane }
|
||||
style = { styles.closeButton } />
|
||||
</View>
|
||||
<ScrollView>
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList />
|
||||
</ScrollView>
|
||||
{
|
||||
isLocalModerator
|
||||
&& <View style = { styles.footer }>
|
||||
<Button
|
||||
children = { t('participantsPane.actions.muteAll') }
|
||||
labelStyle = { styles.muteAllLabel }
|
||||
mode = 'contained'
|
||||
onPress = { muteAll }
|
||||
style = { showContextMenu ? styles.muteAllMoreButton : styles.muteAllButton } />
|
||||
{
|
||||
showContextMenu
|
||||
&& <Button
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
icon = { () =>
|
||||
(<Icon
|
||||
size = { 20 }
|
||||
src = { IconHorizontalPoints } />)
|
||||
}
|
||||
labelStyle = { styles.moreIcon }
|
||||
mode = 'contained'
|
||||
onPress = { openMoreMenu }
|
||||
style = { styles.moreButton } />
|
||||
}
|
||||
</View>
|
||||
}
|
||||
</JitsiModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParticipantsPane;
|
||||
@@ -0,0 +1,41 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconParticipants } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { open } from '../../actions.native';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the participants panel.
|
||||
*/
|
||||
class ParticipantsPaneButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.participants';
|
||||
icon = IconParticipants;
|
||||
label = 'toolbar.participants';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the participants panel.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(open());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(ParticipantsPaneButton));
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { Icon, IconRaisedHandHollow } from '../../../base/icons';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
export const RaisedHandIndicator = () => (
|
||||
<View style = { styles.raisedHandIndicator }>
|
||||
<Icon
|
||||
size = { 15 }
|
||||
src = { IconRaisedHandHollow }
|
||||
style = { styles.raisedHandIcon } />
|
||||
</View>
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
// @flow
|
||||
|
||||
export { default as ParticipantsPane } from './ParticipantsPane';
|
||||
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
|
||||
export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';
|
||||
export { default as ContextMenuMeetingParticipantDetails } from './ContextMenuMeetingParticipantDetails';
|
||||
347
react/features/participants-pane/components/native/styles.js
Normal file
347
react/features/participants-pane/components/native/styles.js
Normal file
@@ -0,0 +1,347 @@
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* The style for participant list description.
|
||||
*/
|
||||
const participantListDescription = {
|
||||
...BaseTheme.typography.heading6,
|
||||
color: BaseTheme.palette.text02,
|
||||
paddingBottom: BaseTheme.spacing[3],
|
||||
paddingTop: BaseTheme.spacing[3],
|
||||
position: 'relative',
|
||||
width: '55%'
|
||||
};
|
||||
|
||||
/**
|
||||
* The style for content.
|
||||
*/
|
||||
const flexContent = {
|
||||
alignItems: 'center',
|
||||
color: BaseTheme.palette.icon01,
|
||||
display: 'flex',
|
||||
flex: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* The style for the context menu items text.
|
||||
*/
|
||||
const contextMenuItemText = {
|
||||
...BaseTheme.typography.bodyShortRegularLarge,
|
||||
color: BaseTheme.palette.text01
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of the participants pane buttons.
|
||||
*/
|
||||
export const button = {
|
||||
backgroundColor: BaseTheme.palette.action02,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
minWidth: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Small buttons.
|
||||
*/
|
||||
const smallButton = {
|
||||
...button,
|
||||
height: BaseTheme.spacing[7],
|
||||
width: BaseTheme.spacing[7]
|
||||
};
|
||||
|
||||
/**
|
||||
* Mute all button.
|
||||
*/
|
||||
const muteAllButton = {
|
||||
...button,
|
||||
marginLeft: 'auto'
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of the participants pane buttons description.
|
||||
*/
|
||||
const buttonContent = {
|
||||
...BaseTheme.typography.labelButton,
|
||||
alignContent: 'center',
|
||||
color: BaseTheme.palette.text01,
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of the context menu pane items.
|
||||
*/
|
||||
const contextMenuItem = {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[7],
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the native components of the feature {@code participants}.
|
||||
*/
|
||||
export default {
|
||||
|
||||
participantActionsButtonAdmit: {
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[6],
|
||||
marginRight: BaseTheme.spacing[3],
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
zIndex: 1
|
||||
},
|
||||
|
||||
participantActionsButtonContent: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: BaseTheme.spacing[5],
|
||||
top: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
participantActionsButtonText: {
|
||||
color: BaseTheme.palette.text01,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
|
||||
admitAllParticipantsActionButtonLabel: {
|
||||
...BaseTheme.typography.heading6,
|
||||
color: BaseTheme.palette.link01,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
|
||||
admitAllParticipantsActionButton: {
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
|
||||
participantContainer: {
|
||||
alignItems: 'center',
|
||||
borderBottomColor: BaseTheme.palette.field01Hover,
|
||||
borderBottomWidth: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[9],
|
||||
paddingLeft: BaseTheme.spacing[3],
|
||||
paddingRight: BaseTheme.spacing[3],
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
participantContent: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
participantNameContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
paddingLeft: BaseTheme.spacing[2],
|
||||
width: '63%'
|
||||
},
|
||||
|
||||
participantName: {
|
||||
overflow: 'hidden',
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
isLocal: {
|
||||
alignSelf: 'center',
|
||||
color: BaseTheme.palette.text01,
|
||||
marginLeft: 4
|
||||
},
|
||||
|
||||
participantsPane: {
|
||||
backgroundColor: BaseTheme.palette.ui01
|
||||
},
|
||||
|
||||
participantStatesContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginLeft: 'auto',
|
||||
width: '15%'
|
||||
},
|
||||
|
||||
participantStateVideo: {
|
||||
paddingRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
raisedHandIndicator: {
|
||||
backgroundColor: BaseTheme.palette.warning02,
|
||||
borderRadius: BaseTheme.shape.borderRadius / 2,
|
||||
height: BaseTheme.spacing[4],
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
width: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
raisedHandIcon: {
|
||||
...flexContent,
|
||||
top: BaseTheme.spacing[1]
|
||||
},
|
||||
lobbyList: {
|
||||
position: 'relative'
|
||||
},
|
||||
|
||||
meetingList: {
|
||||
position: 'relative',
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
lobbyListDetails: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
overflow: 'hidden',
|
||||
paddingLeft: BaseTheme.spacing[3],
|
||||
position: 'relative',
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
lobbyListDescription: {
|
||||
...participantListDescription
|
||||
},
|
||||
|
||||
meetingListDescription: {
|
||||
...participantListDescription,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
top: BaseTheme.spacing[0],
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[10],
|
||||
paddingRight: BaseTheme.spacing[3],
|
||||
position: 'relative',
|
||||
right: BaseTheme.spacing[0],
|
||||
left: BaseTheme.spacing[0]
|
||||
},
|
||||
|
||||
footer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
bottom: BaseTheme.spacing[0],
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[10],
|
||||
justifyContent: 'space-between',
|
||||
paddingRight: BaseTheme.spacing[3],
|
||||
position: 'relative',
|
||||
right: BaseTheme.spacing[0],
|
||||
left: BaseTheme.spacing[0]
|
||||
},
|
||||
|
||||
closeButton: {
|
||||
...smallButton,
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
...buttonContent,
|
||||
height: BaseTheme.spacing[5],
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
|
||||
inviteButton: {
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
marginTop: BaseTheme.spacing[2],
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
inviteLabel: {
|
||||
...BaseTheme.typography.labelButtonLarge,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
|
||||
moreButton: {
|
||||
...smallButton
|
||||
},
|
||||
|
||||
moreIcon: {
|
||||
...buttonContent,
|
||||
height: BaseTheme.spacing[5],
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
|
||||
contextMenuMore: {
|
||||
backgroundColor: BaseTheme.palette.action02,
|
||||
borderRadius: BaseTheme.shape.borderRadius
|
||||
},
|
||||
|
||||
contextMenuMeetingParticipantDetails: {
|
||||
backgroundColor: BaseTheme.palette.action02,
|
||||
borderRadius: BaseTheme.shape.borderRadius
|
||||
},
|
||||
|
||||
muteAllButton: {
|
||||
...muteAllButton
|
||||
},
|
||||
|
||||
muteAllMoreButton: {
|
||||
...muteAllButton,
|
||||
right: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
muteAllLabel: {
|
||||
...BaseTheme.typography.labelButtonLarge,
|
||||
color: BaseTheme.palette.text01,
|
||||
height: BaseTheme.spacing[7],
|
||||
marginVertical: BaseTheme.spacing[0],
|
||||
marginHorizontal: BaseTheme.spacing[0],
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
paddingRight: BaseTheme.spacing[3],
|
||||
paddingLeft: BaseTheme.spacing[3],
|
||||
textTransform: 'capitalize',
|
||||
width: 94
|
||||
},
|
||||
|
||||
contextMenuItem: {
|
||||
...contextMenuItem
|
||||
},
|
||||
|
||||
contextMenuItemSection: {
|
||||
...contextMenuItem
|
||||
},
|
||||
|
||||
contextMenuItemSectionAvatar: {
|
||||
...contextMenuItem,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
contextMenuItemAvatarText: {
|
||||
...contextMenuItemText,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
contextMenuItemText: {
|
||||
...contextMenuItemText,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
contextMenuItemName: {
|
||||
...BaseTheme.typography.bodyShortRegularLarge,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
contextMenuIcon: {
|
||||
color: BaseTheme.palette.actionDanger
|
||||
},
|
||||
|
||||
divider: {
|
||||
backgroundColor: BaseTheme.palette.section01
|
||||
}
|
||||
};
|
||||
@@ -4,9 +4,9 @@ import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { Icon, IconInviteMore } from '../../base/icons';
|
||||
import { beginAddPeople } from '../../invite';
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||
import { beginAddPeople } from '../../../invite';
|
||||
|
||||
import { ParticipantInviteButton } from './styled';
|
||||
|
||||
@@ -4,8 +4,8 @@ import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../../lobby/actions';
|
||||
import { ACTION_TRIGGER, MEDIA_STATE } from '../constants';
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../../../lobby/actions';
|
||||
import { ACTION_TRIGGER, MEDIA_STATE } from '../../constants';
|
||||
|
||||
import ParticipantItem from './ParticipantItem';
|
||||
import { ParticipantActionButton } from './styled';
|
||||
@@ -5,9 +5,9 @@ import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import { admitMultiple } from '../../lobby/actions.web';
|
||||
import { getLobbyState } from '../../lobby/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { admitMultiple } from '../../../lobby/actions.web';
|
||||
import { getLobbyState } from '../../../lobby/functions';
|
||||
|
||||
import { LobbyParticipantItem } from './LobbyParticipantItem';
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { isToolbarButtonEnabled } from '../../base/config/functions.web';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
IconCloseCircle,
|
||||
IconCrown,
|
||||
@@ -12,18 +12,18 @@ import {
|
||||
IconMicDisabled,
|
||||
IconMuteEveryoneElse,
|
||||
IconVideoOff
|
||||
} from '../../base/icons';
|
||||
} from '../../../base/icons';
|
||||
import {
|
||||
getParticipantByIdOrUndefined,
|
||||
isLocalParticipantModerator,
|
||||
isParticipantModerator
|
||||
} from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../base/tracks';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../video-menu';
|
||||
import MuteRemoteParticipantsVideoDialog from '../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
|
||||
import { getComputedOuterHeight } from '../functions';
|
||||
} from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
|
||||
import { openChat } from '../../../chat/actions';
|
||||
import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
|
||||
import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
|
||||
import { getComputedOuterHeight } from '../../functions';
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user