Compare commits

...

18 Commits

Author SHA1 Message Date
Tudor-Ovidiu Avram
a270e4300a fix(native) add missing function 2020-10-08 13:10:45 +02:00
Mihai Uscat
5e2ee3bdcd fix: Show focus indicator only when navigating via keyboard 2020-10-08 10:41:26 +02:00
Saúl Ibarra Corretgé
bdd2845917 deps,misc: fix package-lock conflicts 2020-10-08 10:17:53 +02:00
Saúl Ibarra Corretgé
f9888e5dbb rn,remote-video-menu: make UI consistent with other menus 2020-10-08 10:17:53 +02:00
Saúl Ibarra Corretgé
44d7828e9c rn,overflow-menu: improve drag icon 2020-10-08 10:17:53 +02:00
Saúl Ibarra Corretgé
82b14ba7f1 deps: update react-native-svg
Watch out when updating beyond this version:
https://github.com/react-native-community/react-native-svg/issues/1354
2020-10-08 10:17:53 +02:00
Saúl Ibarra Corretgé
63fe1de789 rn,recent-list: replace swipe options with long-press sheet
This change serves 2 purposes:

- (Hopefully) make the recent list entry options easier to discover
- Remove the (now unmaintained) swipeout dependency
2020-10-08 10:17:53 +02:00
Jaya Allamsetty
39af6f5943 fix(video-quality): Add the ability to request Ultra HD resolutions
Change the preferredVideoQuality and maxReceiverVideoQuality values to Ultra HD resolutions. The requested resolution can be as high as 4K to facilitate VPaaS customers to request 4K. The sender video resolution will always max out at the value specified in the video constraints from config.js settings.
2020-10-07 15:07:14 -04:00
Anand Parshuramka
f01869c21c Adding the flags to enable/disable Kick out option in RemoteVideoMenu 2020-10-07 10:54:13 -05:00
Tudor-Ovidiu Avram
6d2f8ae37d feat(prejoin) show connection status in exported prejoin screen 2020-10-07 17:23:49 +02:00
Saúl Ibarra Corretgé
35bea1a1d0 fix(misc) update update-ljm script commit message 2020-10-07 16:31:47 +02:00
Saúl Ibarra Corretgé
afa4306ae8 chore(deps) lib-jitsi-meet@latest 2020-10-07 16:31:47 +02:00
Saúl Ibarra Corretgé
1d9daa8da7 fix(config) drop useStunTurn
Always attempt to discover the configured STUN/TURN servers.
2020-10-07 16:31:47 +02:00
Tudor-Ovidiu Avram
478f1a731e feat(prejoin) improve ux 2020-10-07 14:53:49 +02:00
Titus-Andrei Moldovan
9f9e192c3c fix(android) - separates the invocation of the gradle tasks. It was noticed on some configurations that the publish task was executed before assembleRelease finished 2020-10-07 14:01:26 +02:00
Titus-Andrei Moldovan
943996e5b6 fix(android) - adds the import for the VersionName, since on some configurations it is not automatically imported 2020-10-07 14:01:26 +02:00
Hristo Terezov
bfde13cb15 chore(lib-jitsi-meet): Update. 2020-10-06 12:58:27 -05:00
George Politis
5939820271 fix: Makes the code more defensive to prevent an error. (#7837) 2020-10-05 16:56:46 +02:00
56 changed files with 848 additions and 578 deletions

View File

@@ -1,4 +1,5 @@
import groovy.json.JsonSlurper
import org.gradle.util.VersionNumber
// Top-level build file where you can add configuration options common to all
// sub-projects/modules.

View File

@@ -89,7 +89,9 @@ fi
# Now build and publish the Jitsi Meet SDK and its dependencies
echo "Building and publishing the Jitsi Meet SDK"
pushd ${THIS_DIR}/../
./gradlew clean assembleRelease publish
./gradlew clean
./gradlew assembleRelease
./gradlew publish
popd
if [[ $DO_GIT_TAG == 1 ]]; then

2
app.js
View File

@@ -6,6 +6,8 @@ import 'jQuery-Impromptu';
import 'olm';
import 'focus-visible';
// We need to setup the jitsi-local-storage as early as possible so that we can start using it.
// NOTE: If jitsi-local-storage is used before the initial setup is performed this will break the use case when we use
// the local storage from the parent page when the localStorage is disabled. Also the setup is relying that

View File

@@ -104,11 +104,8 @@ import {
trackAdded,
trackRemoved
} from './react/features/base/tracks';
import {
getBackendSafePath,
getJitsiMeetGlobalNS
} from './react/features/base/util';
import { downloadJSON } from './react/features/base/util/downloadJSON';
import { getConferenceOptions } from './react/features/conference/functions';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import {
@@ -124,7 +121,6 @@ import {
isPrejoinPageVisible,
makePrecallTest
} from './react/features/prejoin';
import { createRnnoiseProcessorPromise } from './react/features/rnnoise';
import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
import { setSharedVideoStatus } from './react/features/shared-video';
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
@@ -1319,53 +1315,7 @@ export default {
},
_getConferenceOptions() {
const options = config;
const { email, name: nick } = getLocalParticipant(APP.store.getState());
const state = APP.store.getState();
const { locationURL } = state['features/base/connection'];
const { tenant } = state['features/base/jwt'];
if (tenant) {
options.siteID = tenant;
}
if (options.enableDisplayNameInStats && nick) {
options.statisticsDisplayName = nick;
}
if (options.enableEmailInStats && email) {
options.statisticsId = email;
}
options.applicationName = interfaceConfig.APP_NAME;
options.getWiFiStatsMethod = this._getWiFiStatsMethod;
options.confID = `${locationURL.host}${getBackendSafePath(locationURL.pathname)}`;
options.createVADProcessor = createRnnoiseProcessorPromise;
// Disable CallStats, if requessted.
if (options.disableThirdPartyRequests) {
delete options.callStatsID;
delete options.callStatsSecret;
delete options.getWiFiStatsMethod;
}
return options;
},
/**
* Returns the result of getWiFiStats from the global NS or does nothing
* (returns empty result).
* Fixes a concurrency problem where we need to pass a function when creating
* JitsiConference, but that method is added to the context later.
*
* @returns {Promise}
* @private
*/
_getWiFiStatsMethod() {
const gloabalNS = getJitsiMeetGlobalNS();
return gloabalNS.getWiFiStats ? gloabalNS.getWiFiStats() : Promise.resolve('{}');
return getConferenceOptions(APP.store.getState());
},
/**

View File

@@ -323,10 +323,6 @@ var config = {
// is set in Jicofo and set to 2).
// minParticipants: 2,
// Use the TURN servers discovered via XEP-0215 for the jitsi-videobridge
// connection
// useStunTurn: true,
// Use TURN/UDP servers for the jitsi-videobridge connection (by default
// we filter out TURN/UDP because it is usually not needed since the
// bridge itself is reachable via UDP)
@@ -442,9 +438,6 @@ var config = {
// connection.
enabled: true,
// Use XEP-0215 to fetch STUN and TURN servers.
// useStunTurn: true,
// The STUN servers that will be used in the peer to peer connections
stunServers: [

View File

@@ -33,6 +33,14 @@ body {
}
}
/**
* This will hide the focus indicator if an element receives focus via the mouse,
* but it will still show up on keyboard focus, thus preserving accessibility.
*/
.js-focus-visible :focus:not(.focus-visible) {
outline: none;
}
/**
* AtlasKit sets a default margin on the rendered modals, so
* when the shift-right class is set when the chat opens, we

View File

@@ -1,30 +1,30 @@
.con-status {
position: absolute;
top: 40px;
top: 24px;
width: 100%;
z-index: $toolbarZ + 3;
&-container {
background: rgba(28, 32, 37, .5);
border-radius: 3px;
color: #fff;
font-size: 13px;
line-height: 20px;
line-height: 13px;
margin: 0 auto;
width: 304px;
width: 320px;
}
&-header {
background: rgba(28, 32, 37, .5);
align-items: center;
display: flex;
justify-content: space-between;
padding: 8px;
}
&-circle {
border-radius: 50%;
display: inline-block;
padding: 4px;
margin: 8px;
}
&--good {
@@ -40,6 +40,16 @@
}
&-arrow {
height: 36px;
width: 36px;
border-radius: 3px;
margin-left: 8px;
margin-right: 2px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.16s ease-out;
&--up {
transform: rotate(180deg);
}
@@ -47,6 +57,10 @@
&>svg {
cursor: pointer;
}
&:hover {
background-color: rgba(1,1,1, 0.1);
}
}
&-text {
@@ -54,7 +68,17 @@
}
&-details {
background: rgba(28, 32, 37, .5);
border-top: 1px solid #5E6D7A;
padding: 16px;
transition: opacity 0.16s ease-out;
&-visible {
opacity: 1;
}
&-hidden {
opacity: 0;
}
}
}

View File

@@ -14,19 +14,6 @@
margin: 10px;
}
}
.form {
align-items: stretch;
display: flex;
flex-direction: column;
min-width: 400px;
}
.participant-info {
align-items: center;
display: flex;
flex-direction: column;
}
}
}
@@ -100,19 +87,6 @@
}
}
input {
align-self: stretch;
background-color: transparent;
border: 1px solid #B8C7E0;
border-radius: 4px;
color: white;
padding: 12px 8px;
&:focus {
border-color: rgb(3, 118, 218);
}
}
button {
align-self: stretch;
margin: 8px 0;

View File

@@ -3,7 +3,6 @@
&-input-area {
margin: 0 auto;
text-align: center;
width: 320px;
}
&-title {
@@ -42,9 +41,11 @@
&-error {
color: white;
background-color: rgba(229, 75, 75, 0.5);
background-color: rgba(225, 45, 45, 0.6);
border-radius: 3px;
width: 100%;
padding: 3px;
padding: 2px;
box-sizing: border-box;
margin-top: 4px;
font-size: 13px;
text-align: center;
@@ -58,75 +59,6 @@
}
.prejoin-preview {
height: 100%;
position: absolute;
width: 100%;
&--no-video {
background: radial-gradient(50% 50% at 50% 50%, #5B6F80 0%, #365067 100%), #FFFFFF;
text-align: center;
}
&-video {
height: 100%;
object-fit: cover;
position: absolute;
width: 100%;
}
&-name {
color: #fff;
font-size: 19px;
line-height: 28px;
&--editable {
background: none;
border: 0;
border-bottom: 1px solid #D1DBE8;
margin: 24px 0 16px 0;
outline: none;
text-align: center;
width: 100%;
&::-webkit-input-placeholder {
@include name-placeholder;
}
&::-moz-placeholder {
@include name-placeholder;
}
&:-ms-input-placeholder {
@include name-placeholder;
}
}
&--text {
margin: 16px 0;
outline: none;
}
}
&-avatar.avatar {
background: #A4B8D1;
margin: 200px auto 0 auto;
}
&-overlay {
height: 100%;
position: absolute;
width: 100%;
z-index: 1;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3));
}
&-bottom-overlay {
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.9) 100%);
bottom: 0;
height: 50%;
position: absolute;
width: 100%;
z-index: 1;
}
&-status {
align-items: center;
align-self: stretch;

View File

@@ -12,12 +12,23 @@
.premeeting-screen {
align-items: stretch;
background: radial-gradient(50% 50% at 50% 50%, #5D95C7 0%, #376288 100%), #FFFFFF;
background: radial-gradient(50% 50% at 50% 50%, #2A3A4B 20.83%, #1E2A36 100%);
display: flex;
flex-direction: column;
font-size: 1.3em;
z-index: $toolbarZ + 1;
&-avatar {
background-color: #A4B8D1;
margin-bottom: 24px;
text {
fill: black;
font-size: 26px;
font-weight: 400;
}
}
.action-btn {
border-radius: 3px;
color: #fff;
@@ -59,22 +70,26 @@
fill: #AFB6BC;
}
}
.options {
border-left: 1px solid #AFB6BC;
}
}
.options {
border-radius: 3px;
align-items: center;
border-left: 1px solid #fff;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
right: 0;
top: 0;
width: 40px;
width: 36px;
&:hover {
background-color: #0262B6;
}
svg {
pointer-events: none;
}
}
}
@@ -111,12 +126,14 @@
margin-bottom: 16px;
.url {
background: rgba(28, 32, 37, 0.5);
border-radius: 4px;
display: flex;
padding: 8px 10px;
transition: background 0.16s ease-out;
&:hover {
background: #1C2025;
border-radius: 4px;
}
&.done {
@@ -149,20 +166,23 @@
}
input.field {
background-color: transparent;
border: 1px solid transparent;
color: white;
outline-width: 0;
background-color: white;
border: none;
outline: none;
border-radius: 3px;
font-size: 15px;
line-height: 24px;
color: #1C2025;
padding: 8px 0;
text-align: center;
width: 100%;
width: 320px;
&.focused {
border-bottom: 1px solid white;
&.error {
box-shadow: 0px 0px 4px 3px rgba(225, 45, 45, 0.4);
}
&.error::placeholder {
color: $defaultWarningColor;
&.focused {
box-shadow: 0px 0px 4px 3px #0376DA;
}
}
}
@@ -170,7 +190,7 @@
.media-btn-container {
display: flex;
justify-content: center;
margin: 32px 0;
margin: 24px 0 16px 0;
width: 100%;
&> div {
@@ -233,6 +253,7 @@
font-size: 13px;
height: 40px;
margin: 0 auto;
transition: background 0.16s ease-out;
width: 320px;
@include flex-centered();
@@ -242,7 +263,7 @@
}
&:hover {
background: #1C2025;
background: rgba(255, 255, 255, 0.1);
@include icon-container(#A4B8D1, #1C2025);
}
@@ -261,14 +282,6 @@
}
&--toggled {
background: #75757A;
&:hover {
background: #75757A;
@include icon-container(#A4B8D1, #75757A);
}
@include icon-container(#A4B8D1, #75757A);
@include icon-container(white, #1C2025);
}
}

View File

@@ -150,11 +150,6 @@ denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG
echo "------------------------------------------------"
fi
# Enable turn server in config.js
if [ -f $JITSI_MEET_CONFIG ] ; then
sed -i "s/\/\/ useStunTurn: true/useStunTurn: true/g" $JITSI_MEET_CONFIG
fi
# and we're done with debconf
db_stop
;;

View File

@@ -1,5 +1,5 @@
// flow-typed signature: d2ddacbbca9700881249a9435381e689
// flow-typed version: c6154227d1/react-redux_v7.x.x/flow_>=v0.89.x <=v0.103.x
// flow-typed signature: 8da1e134b3de1d6f6bf9ba1cc7e2dc7e
// flow-typed version: 387a235736/react-redux_v7.x.x/flow_>=v0.104.x
/**
The order of type arguments for connect() is as follows:
@@ -219,6 +219,7 @@ declare module "react-redux" {
declare export class Provider<Store> extends React$Component<{
store: Store,
children?: React$Node,
...
}> {}
declare export function createProvider(
@@ -237,6 +238,7 @@ declare module "react-redux" {
shouldHandleStateChanges?: boolean,
storeKey?: string,
forwardRef?: boolean,
...
};
declare type SelectorFactoryOptions<Com> = {
@@ -249,6 +251,7 @@ declare module "react-redux" {
displayName: string,
wrappedComponentName: string,
WrappedComponent: Com,
...
};
declare type MapStateToPropsEx<S: Object, SP: Object, RSP: Object> = (
@@ -275,12 +278,14 @@ declare module "react-redux" {
OP: Object,
CP: Object,
EFO: Object,
ST: { [_: $Keys<Com>]: any },
ST: { [_: $Keys<Com>]: any, ... },
>(
selectorFactory: SelectorFactory<Com, D, S, OP, EFO, CP>,
connectAdvancedOptions: ?(ConnectAdvancedOptions & EFO),
): (component: Com) => React$ComponentType<OP> & $Shape<ST>;
declare export function batch(() => void): void
declare export default {
Provider: typeof Provider,
createProvider: typeof createProvider,
@@ -289,5 +294,7 @@ declare module "react-redux" {
useDispatch: typeof useDispatch,
useSelector: typeof useSelector,
useStore: typeof useStore,
batch: typeof batch,
...
};
}

View File

@@ -361,7 +361,7 @@ PODS:
- RNSound/Core (= 0.11.0)
- RNSound/Core (0.11.0):
- React
- RNSVG (9.7.1):
- RNSVG (10.1.0):
- React
- RNWatch (0.4.3):
- React
@@ -572,7 +572,7 @@ SPEC CHECKSUMS:
RNDefaultPreference: 56a405ce61033ac77b95004dccd7ac54c2eb50d1
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
RNSVG: aac12785382e8fd4f28d072fe640612e34914631
RNSVG: 069864be08c9fe065a2cf7e63656a34c78653c99
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f

View File

@@ -366,7 +366,7 @@
"password": "$t(lockRoomPasswordUppercase):",
"title": "Share",
"tooltip": "Share link and dial-in info for this meeting",
"label": "Meeting info"
"label": "Dial-in info"
},
"inviteDialog": {
"alertText": "Failed to invite some participants.",
@@ -536,7 +536,7 @@
"dialInMeeting": "Dial into the meeting",
"dialInPin": "Dial into the meeting and enter PIN code:",
"dialing": "Dialing",
"doNotShow": "Don't show this again",
"doNotShow": "Don't show this screen again",
"errorDialOut": "Could not dial out",
"errorDialOutDisconnected": "Could not dial out. Disconnected",
"errorDialOutFailed": "Could not dial out. Call failed",
@@ -876,12 +876,12 @@
"getHelp": "Get help",
"go": "GO",
"goSmall": "GO",
"info": "Info",
"info": "Dial-in info",
"join": "CREATE / JOIN",
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
"privacy": "Privacy",
"recentList": "Recent",
"recentListDelete": "Delete",
"recentListDelete": "Delete entry",
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
"reducedUIText": "Welcome to {{app}}!",
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",

176
package-lock.json generated
View File

@@ -7150,6 +7150,17 @@
}
}
},
"css-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
"integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==",
"requires": {
"boolbase": "^1.0.0",
"css-what": "^3.2.1",
"domutils": "^1.7.0",
"nth-check": "^1.0.2"
}
},
"css-to-react-native": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.2.tgz",
@@ -7160,6 +7171,27 @@
"postcss-value-parser": "^3.3.0"
}
},
"css-tree": {
"version": "1.0.0-alpha.39",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz",
"integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==",
"requires": {
"mdn-data": "2.0.6",
"source-map": "^0.6.1"
},
"dependencies": {
"mdn-data": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz",
"integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"css-what": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
@@ -8145,6 +8177,11 @@
}
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
@@ -8735,6 +8772,11 @@
"resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.3.tgz",
"integrity": "sha512-EU6ePgEauhWrzJEN5RtG1d1ayrWXhEnfzTjnieHj+jG9tNHDEhKTAnCn1TN3gs9h6XWCDH6cpeX1VXY/lzLwZg=="
},
"focus-visible": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.1.0.tgz",
"integrity": "sha512-nPer0rjtzdZ7csVIu233P2cUm/ks/4aVSI+5KUkYrYpgA7ujgC3p6J7FtFU+AIMWwnwYQOB/yeiOITxFeYIXiw=="
},
"follow-redirects": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
@@ -10637,6 +10679,15 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsc-android": {
"version": "245459.0.0",
"resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-245459.0.0.tgz",
@@ -10778,8 +10829,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#0cffc064e644ad87ff381cc6c4df1c1a9f2c73ff",
"from": "github:jitsi/lib-jitsi-meet#0cffc064e644ad87ff381cc6c4df1c1a9f2c73ff",
"version": "github:jitsi/lib-jitsi-meet#f370cccdfba6f9190ecb4afc3d78552d9f3ad57c",
"from": "github:jitsi/lib-jitsi-meet#f370cccdfba6f9190ecb4afc3d78552d9f3ad57c",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -12883,8 +12934,7 @@
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
},
"path-exists": {
"version": "3.0.0",
@@ -12939,11 +12989,6 @@
"sha.js": "^2.4.8"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@@ -13458,14 +13503,6 @@
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
"dev": true
},
"raf": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz",
"integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==",
"requires": {
"performance-now": "^2.1.0"
}
},
"raf-schd": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
@@ -14147,19 +14184,86 @@
"from": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190"
},
"react-native-svg": {
"version": "9.7.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-9.7.1.tgz",
"integrity": "sha512-Yr54SyLPCdovLCJ08V7syJUe1iKrTYG9V5wB08z6lh/9FdC2R9CtBnMyz83GDLKfzUONqqH9nN1l+o61CgD3tg=="
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-10.1.0.tgz",
"integrity": "sha512-mgo6CshQIQrDDBVUPqJK/iOsJEdlagk7N4q8fyo1sqCiSUP2efpt+AQ1IRXZtHXut210/7TliAamvM59NV0Bzg==",
"requires": {
"css-select": "^2.1.0",
"css-tree": "^1.0.0-alpha.39"
}
},
"react-native-svg-transformer": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-0.13.0.tgz",
"integrity": "sha512-tfcnIDC2Q6FN8+g/BXPootZtb+sWLzKJde3o5jSuUJdXSmKwwSazAbk+V808n/Ez5kd2arzsuPTKONT66qx5Xw==",
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-0.14.3.tgz",
"integrity": "sha512-agDGdMeeBAsWEgg/u7mjtR2Z3c8smGCLep/n3svwifut9dpswZCP+bSIrU8ekg6RNtxAJL+eGJbWjJ38vWxw6g==",
"requires": {
"@svgr/core": "^4.1.0",
"@svgr/core": "^4.3.3",
"@svgr/plugin-svgo": "^4.3.1",
"path-dirname": "^1.0.2",
"semver": "^5.6.0"
},
"dependencies": {
"@svgr/babel-plugin-svg-dynamic-title": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz",
"integrity": "sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w=="
},
"@svgr/babel-preset": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.3.tgz",
"integrity": "sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A==",
"requires": {
"@svgr/babel-plugin-add-jsx-attribute": "^4.2.0",
"@svgr/babel-plugin-remove-jsx-attribute": "^4.2.0",
"@svgr/babel-plugin-remove-jsx-empty-expression": "^4.2.0",
"@svgr/babel-plugin-replace-jsx-attribute-value": "^4.2.0",
"@svgr/babel-plugin-svg-dynamic-title": "^4.3.3",
"@svgr/babel-plugin-svg-em-dimensions": "^4.2.0",
"@svgr/babel-plugin-transform-react-native-svg": "^4.2.0",
"@svgr/babel-plugin-transform-svg-component": "^4.2.0"
}
},
"@svgr/core": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.3.tgz",
"integrity": "sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w==",
"requires": {
"@svgr/plugin-jsx": "^4.3.3",
"camelcase": "^5.3.1",
"cosmiconfig": "^5.2.1"
}
},
"@svgr/plugin-jsx": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz",
"integrity": "sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w==",
"requires": {
"@babel/core": "^7.4.5",
"@svgr/babel-preset": "^4.3.3",
"@svgr/hast-util-to-babel-ast": "^4.3.2",
"svg-parser": "^2.0.0"
}
},
"cosmiconfig": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
"integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
"requires": {
"import-fresh": "^2.0.0",
"is-directory": "^0.3.1",
"js-yaml": "^3.13.1",
"parse-json": "^4.0.0"
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -14167,16 +14271,6 @@
}
}
},
"react-native-swipeout": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz",
"integrity": "sha512-t9suUCspzck4vp2pWggWe0frS/QOtX6yYCawHnEes75A7dZCEE74bxX2A1bQzGH9cUMjq6xsdfC94RbiDKIkJg==",
"requires": {
"create-react-class": "^15.6.0",
"prop-types": "^15.5.10",
"react-tween-state": "^0.1.5"
}
},
"react-native-watch-connectivity": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-0.4.3.tgz",
@@ -14351,15 +14445,6 @@
}
}
},
"react-tween-state": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/react-tween-state/-/react-tween-state-0.1.5.tgz",
"integrity": "sha1-6YsGZVHvuTy5LdG+FJlcLj3q4zk=",
"requires": {
"raf": "^3.1.0",
"tween-functions": "^1.0.1"
}
},
"react-uid": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-uid/-/react-uid-2.2.0.tgz",
@@ -16655,11 +16740,6 @@
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
"tween-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
"integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",

View File

@@ -45,6 +45,7 @@
"base64-js": "1.3.1",
"bc-css-flags": "3.0.0",
"dropbox": "4.0.9",
"focus-visible": "5.1.0",
"i18n-iso-countries": "3.7.8",
"i18next": "17.0.6",
"i18next-browser-languagedetector": "3.0.1",
@@ -56,7 +57,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0cffc064e644ad87ff381cc6c4df1c1a9f2c73ff",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f370cccdfba6f9190ecb4afc3d78552d9f3ad57c",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
@@ -79,9 +80,8 @@
"react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-sound": "github:jitsi/react-native-sound#3fe5480fce935e888d5089d94a191c7c7e3aa190",
"react-native-svg": "9.7.1",
"react-native-svg-transformer": "0.13.0",
"react-native-swipeout": "2.3.6",
"react-native-svg": "10.1.0",
"react-native-svg-transformer": "0.14.3",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "1.84.0",
"react-native-webview": "10.9.0",

View File

@@ -37,6 +37,11 @@ export type Props = {
*/
displayName?: string,
/**
* Whether or not to update the background color of the avatar
*/
dynamicColor?: Boolean,
/**
* ID of the element, if any.
*/
@@ -78,6 +83,15 @@ export const DEFAULT_SIZE = 65;
* Implements a class to render avatars in the app.
*/
class Avatar<P: Props> extends PureComponent<P, State> {
/**
* Default values for {@code Avatar} component's properties.
*
* @static
*/
static defaultProps = {
dynamicColor: true
};
/**
* Instantiates a new {@code Component}.
*
@@ -123,6 +137,7 @@ class Avatar<P: Props> extends PureComponent<P, State> {
_loadableAvatarUrl,
className,
colorBase,
dynamicColor,
id,
size,
status,
@@ -156,7 +171,10 @@ class Avatar<P: Props> extends PureComponent<P, State> {
const initials = getInitials(_initialsBase);
if (initials) {
avatarProps.color = getAvatarColor(colorBase || _initialsBase);
if (dynamicColor) {
avatarProps.color = getAvatarColor(colorBase || _initialsBase);
}
avatarProps.initials = initials;
}

View File

@@ -3,7 +3,7 @@ import extraConfigWhitelist from './extraConfigWhitelist';
/**
* The config keys to whitelist, the keys that can be overridden.
* Currently we can only whitelist the first part of the properties, like
* 'p2p.useStunTurn' and 'p2p.enabled' we whitelist all p2p options.
* 'p2p.enabled' we whitelist all p2p options.
* The whitelist is used only for config.js.
*
* @type Array
@@ -149,7 +149,6 @@ export default [
'stereo',
'subject',
'testing',
'useStunTurn',
'useTurnUdp',
'videoQuality.persist',
'webrtcIceTcpDisable',

View File

@@ -172,9 +172,7 @@ ColorSchemeRegistry.register('BottomSheet', {
},
expandIcon: {
color: schemeColor('icon'),
fontSize: 48,
opacity: 0.8
color: schemeColor('icon')
},
/**

View File

@@ -49,6 +49,12 @@ export const INVITE_ENABLED = 'invite.enabled';
*/
export const IOS_RECORDING_ENABLED = 'ios.recording.enabled';
/**
* Flag indicating if kickout is enabled.
* Default: enabled (true).
*/
export const KICK_OUT_ENABLED = 'kick-out.enabled';
/**
* Flag indicating if live-streaming should be enabled.
* Default: auto-detected.

View File

@@ -0,0 +1,3 @@
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.41115 6.05746C8.71903 6.39955 9.24594 6.42729 9.58803 6.1194C9.93012 5.81152 9.95786 5.28461 9.64997 4.94252L5.72917 0.562752C5.39813 0.194935 4.82138 0.194935 4.49034 0.562752L0.63061 4.94252C0.322728 5.28461 0.35046 5.81152 0.692552 6.1194C1.03464 6.42729 1.56155 6.39955 1.86943 6.05746L5.10975 2.36593L8.41115 6.05746Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="24"
height="24"
viewBox="0 0 24 24">
<path
d="m 5.6875,10.59375 h 12.625 c 0.779062,0 1.40625,0.627187 1.40625,1.40625 0,0.779062 -0.627188,1.40625 -1.40625,1.40625 H 5.6875 c -0.7790625,0 -1.40625,-0.627188 -1.40625,-1.40625 0,-0.779063 0.6271875,-1.40625 1.40625,-1.40625 z"
id="rect3711" />
width="128"
height="32"
viewBox="0 0 128 32">
<path
d="m 19.431133,13.973662 h 90.198887 c 4.85877,0 4.20737,0.903746 4.20737,2.026338 0,1.122591 0.33306,2.026338 -4.52571,2.026338 H 19.218901 c -4.858774,0 -4.844061,-0.903747 -4.844061,-2.026338 0,-1.122592 0.197519,-2.026338 5.056293,-2.026338 z" />
</svg>

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 424 B

View File

@@ -5,6 +5,7 @@ export { default as IconAddPeople } from './link.svg';
export { default as IconArrowBack } from './arrow_back.svg';
export { default as IconArrowDown } from './arrow_down.svg';
export { default as IconArrowDownSmall } from './arrow-down-small.svg';
export { default as IconArrowUp } from './arrow_up.svg';
export { default as IconArrowLeft } from './arrow-left.svg';
export { default as IconAudioOnly } from './visibility.svg';
export { default as IconAudioOnlyOff } from './visibility-off.svg';
@@ -90,6 +91,7 @@ export { default as IconShareVideo } from './shared-video.svg';
export { default as IconSwitchCamera } from './switch-camera.svg';
export { default as IconTileView } from './tiles-many.svg';
export { default as IconToggleRecording } from './camera-take-picture.svg';
export { default as IconTrash } from './trash.svg';
export { default as IconVideoQualityAudioOnly } from './AUD.svg';
export { default as IconVideoQualityHD } from './HD.svg';
export { default as IconVideoQualityLD } from './LD.svg';

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trash-alt" class="svg-inline--fa fa-trash-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"></path></svg>

After

Width:  |  Height:  |  Size: 577 B

View File

@@ -26,6 +26,11 @@ type Props = {
*/
hasOptions?: boolean,
/**
* Icon to display in the options section.
*/
OptionsIcon?: React$Node,
/**
* TestId of the button. Can be used to locate element when testing UI.
*/
@@ -57,6 +62,7 @@ function ActionButton({
className = '',
disabled,
hasOptions,
OptionsIcon = IconArrowDown,
testId,
type = 'primary',
onClick,
@@ -75,7 +81,7 @@ function ActionButton({
<Icon
className = 'icon'
size = { 14 }
src = { IconArrowDown } />
src = { OptionsIcon } />
</div>
}
</div>

View File

@@ -1,61 +0,0 @@
// @flow
import React from 'react';
import { Avatar } from '../../../avatar';
import { connect } from '../../../redux';
import { calculateAvatarDimensions } from '../../functions';
type Props = {
/**
* The height of the window.
*/
height: number,
/**
* The name of the participant (if any).
*/
name: string
}
/**
* Component displaying the avatar for the premeeting screen.
*
* @param {Props} props - The props of the component.
* @returns {ReactElement}
*/
function PremeetingAvatar({ height, name }: Props) {
const { marginTop, size } = calculateAvatarDimensions(height);
if (size <= 5) {
return null;
}
return (
<div style = {{ marginTop }}>
<Avatar
className = 'preview-avatar'
displayName = { name }
participantId = 'local'
size = { size } />
</div>
);
}
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @returns {{
* height: number
* }}
*/
function mapStateToProps(state) {
return {
height: state['features/base/responsive-ui'].clientHeight
};
}
export default connect(mapStateToProps)(PremeetingAvatar);

View File

@@ -61,6 +61,9 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
? 'con-status-arrow con-status-arrow--up'
: 'con-status-arrow';
const detailsText = connectionDetails.map(t).join(' ');
const detailsClassName = showDetails
? 'con-status-details-visible'
: 'con-status-details-hidden';
return (
<div className = 'con-status'>
@@ -79,8 +82,7 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
size = { 24 }
src = { IconArrowDownSmall } />
</div>
{ showDetails
&& <div className = 'con-status-details'>{detailsText}</div> }
<div className = { `con-status-details ${detailsClassName}` }>{detailsText}</div>
</div>
</div>
);

View File

@@ -3,6 +3,7 @@
import React, { PureComponent } from 'react';
import { AudioSettingsButton, VideoSettingsButton } from '../../../../toolbox/components/web';
import { Avatar } from '../../../avatar';
import ConnectionStatus from './ConnectionStatus';
import CopyMeetingUrl from './CopyMeetingUrl';
@@ -85,12 +86,18 @@ export default class PreMeetingScreen extends PureComponent<Props> {
id = 'lobby-screen'>
<ConnectionStatus />
<Preview
name = { name }
showAvatar = { showAvatar }
videoMuted = { videoMuted }
videoTrack = { videoTrack } />
{!videoMuted && <div className = 'preview-overlay' />}
<div className = 'content'>
{showAvatar && videoMuted && (
<Avatar
className = 'premeeting-screen-avatar'
displayName = { name }
dynamicColor = { false }
participantId = 'local'
size = { 80 } />
)}
{showConferenceInfo && (
<>
<div className = 'title'>

View File

@@ -6,20 +6,8 @@ import { Video } from '../../../media';
import { connect } from '../../../redux';
import { getLocalVideoTrack } from '../../../tracks';
import PreviewAvatar from './Avatar';
export type Props = {
/**
* The name of the user that is about to join.
*/
name: string,
/**
* Indicates whether the avatar should be shown when video is off
*/
showAvatar: boolean,
/**
* Flag signaling the visibility of camera preview.
*/
@@ -38,7 +26,7 @@ export type Props = {
* @returns {ReactElement}
*/
function Preview(props: Props) {
const { name, showAvatar, videoMuted, videoTrack } = props;
const { videoMuted, videoTrack } = props;
if (!videoMuted && videoTrack) {
return (
@@ -50,23 +38,9 @@ function Preview(props: Props) {
);
}
if (showAvatar) {
return (
<div
className = 'no-video'
id = 'preview'>
<PreviewAvatar name = { name } />
</div>
);
}
return null;
}
Preview.defaultProps = {
showAvatar: true
};
/**
* Maps part of the Redux state to the props of this component.
*

View File

@@ -20,6 +20,11 @@ type Props = {
*/
disabled: boolean,
/**
* Function to be invoked when an item is long pressed. The item is passed.
*/
onLongPress: Function,
/**
* Function to be invoked when an item is pressed. The item's URL is passed.
*/
@@ -44,13 +49,7 @@ type Props = {
/**
* An array of sections
*/
sections: Array<Section>,
/**
* Optional array of on-slide actions this list should support. For details
* see https://github.com/dancormier/react-native-swipeout.
*/
slideActions?: Array<Object>
sections: Array<Section>
};
/**
@@ -83,11 +82,11 @@ class NavigateSectionList extends Component<Props> {
constructor(props: Props) {
super(props);
this._getItemKey = this._getItemKey.bind(this);
this._onLongPress = this._onLongPress.bind(this);
this._onPress = this._onPress.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._renderItem = this._renderItem.bind(this);
this._renderListEmptyComponent
= this._renderListEmptyComponent.bind(this);
this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
this._renderSectionHeader = this._renderSectionHeader.bind(this);
}
@@ -131,6 +130,25 @@ class NavigateSectionList extends Component<Props> {
return `${index}-${item.key}`;
}
_onLongPress: string => Function;
/**
* Returns a function that is used in the onLongPress callback of the items.
*
* @param {Object} item - The item that was long-pressed.
* @private
* @returns {Function}
*/
_onLongPress(item) {
const { disabled, onLongPress } = this.props;
if (!disabled && typeof onLongPress === 'function') {
return () => onLongPress(item);
}
return null;
}
_onPress: string => Function;
/**
@@ -210,10 +228,10 @@ class NavigateSectionList extends Component<Props> {
<NavigateSectionListItem
item = { item }
key = { key }
onLongPress = { url ? this._onLongPress(item) : undefined }
onPress = { url ? this._onPress(url) : undefined }
secondaryAction = {
url ? undefined : this._onSecondaryAction(id) }
slideActions = { this.props.slideActions } />
url ? undefined : this._onSecondaryAction(id) } />
);
}

View File

@@ -52,6 +52,11 @@ type Props = {
*/
linesStyle?: StyleType,
/**
* Function to invoke on long press.
*/
onLongPress: ?Function,
/**
* Function to invoke on press.
*/
@@ -88,13 +93,16 @@ export default class AvatarListItem extends Component<Props> {
avatarOnly,
avatarSize = AVATAR_SIZE,
avatarStatus,
avatarStyle
avatarStyle,
onLongPress,
onPress
} = this.props;
const { avatar, colorBase, lines, title } = this.props.item;
return (
<Container
onClick = { this.props.onPress }
onClick = { onPress }
onLongPress = { onLongPress }
style = { styles.listItem }
underlayColor = { UNDERLAY_COLOR }>
<Avatar

View File

@@ -1,9 +1,7 @@
// @flow
import React, { Component } from 'react';
import Swipeout from 'react-native-swipeout';
import { ColorPalette } from '../../../styles';
import type { Item } from '../../Types';
import AvatarListItem from './AvatarListItem';
@@ -18,6 +16,11 @@ type Props = {
*/
item: Item,
/**
* Function to be invoked when an item is long pressed. The item is passed.
*/
onLongPress: ?Function,
/**
* Function to be invoked when an Item is pressed. The Item's URL is passed.
*/
@@ -26,13 +29,7 @@ type Props = {
/**
* Function to be invoked when secondary action was performed on an Item.
*/
secondaryAction: ?Function,
/**
* Optional array of on-slide actions this list should support. For details
* see https://github.com/dancormier/react-native-swipeout.
*/
slideActions?: Array<Object>
secondaryAction: ?Function
}
/**
@@ -116,37 +113,15 @@ export default class NavigateSectionListItem extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { item, slideActions } = this.props;
const { id } = item;
let right;
// NOTE: The {@code Swipeout} component has an onPress prop encapsulated
// in the {@code right} array, but we need to bind it to the ID of the
// item too.
if (slideActions) {
right = [];
for (const slideAction of slideActions) {
right.push({
backgroundColor: slideAction.backgroundColor,
onPress: slideAction.onPress.bind(undefined, id),
text: slideAction.text
});
}
}
const { item, onLongPress, onPress, secondaryAction } = this.props;
return (
<Swipeout
autoClose = { true }
backgroundColor = { ColorPalette.transparent }
right = { right }>
<AvatarListItem
item = { item }
onPress = { this.props.onPress } >
{ this.props.secondaryAction
&& this._renderSecondaryAction() }
</AvatarListItem>
</Swipeout>
<AvatarListItem
item = { item }
onLongPress = { onLongPress }
onPress = { onPress } >
{ secondaryAction && this._renderSecondaryAction() }
</AvatarListItem>
);
}
}

View File

@@ -106,7 +106,7 @@ class CalendarListContent extends Component<Props> {
);
}
_onPress: (string, ?string) => Function;
_onPress: (string, ?string) => void;
/**
* Handles the list's navigate action.
@@ -259,9 +259,7 @@ class CalendarListContent extends Component<Props> {
* Maps redux state to component props.
*
* @param {Object} state - The redux state.
* @returns {{
* _eventList: Array<Object>
* }}
* @returns {Props}
*/
function _mapStateToProps(state: Object) {
return {

View File

@@ -0,0 +1,20 @@
import { toState } from '../base/redux';
import { areThereNotifications } from '../notifications';
import { getOverlayToRender } from '../overlay';
/**
* Tells whether or not the notifications should be displayed within
* the conference feature based on the current Redux state.
*
* @param {Object|Function} stateful - The redux store state.
* @returns {boolean}
*/
export function shouldDisplayNotifications(stateful) {
const state = toState(stateful);
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
const { calleeInfoVisible } = state['features/invite'];
return areThereNotifications(state)
&& !isAnyOverlayVisible
&& !calleeInfoVisible;
}

View File

@@ -1,51 +0,0 @@
import { isSuboptimalBrowser } from '../base/environment';
import { translateToHTML } from '../base/i18n';
import { toState } from '../base/redux';
import {
areThereNotifications,
showWarningNotification
} from '../notifications';
import { getOverlayToRender } from '../overlay';
/**
* Shows the suboptimal experience notification if needed.
*
* @param {Function} dispatch - The dispatch method.
* @param {Function} t - The translation function.
* @returns {void}
*/
export function maybeShowSuboptimalExperienceNotification(dispatch, t) {
if (isSuboptimalBrowser()) {
dispatch(
showWarningNotification(
{
titleKey: 'notify.suboptimalExperienceTitle',
description: translateToHTML(
t,
'notify.suboptimalBrowserWarning',
{
recommendedBrowserPageLink: `${window.location.origin}/static/recommendedBrowsers.html`
}
)
}
)
);
}
}
/**
* Tells whether or not the notifications should be displayed within
* the conference feature based on the current Redux state.
*
* @param {Object|Function} stateful - The redux store state.
* @returns {boolean}
*/
export function shouldDisplayNotifications(stateful) {
const state = toState(stateful);
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
const { calleeInfoVisible } = state['features/invite'];
return areThereNotifications(state)
&& !isAnyOverlayVisible
&& !calleeInfoVisible;
}

View File

@@ -0,0 +1 @@
export * from './functions.any';

View File

@@ -0,0 +1,93 @@
import { getName } from '../app/functions.web';
import { isSuboptimalBrowser } from '../base/environment';
import { translateToHTML } from '../base/i18n';
import { getLocalParticipant } from '../base/participants';
import { toState } from '../base/redux';
import { getBackendSafePath, getJitsiMeetGlobalNS } from '../base/util';
import { showWarningNotification } from '../notifications';
import { createRnnoiseProcessorPromise } from '../rnnoise';
export * from './functions.any';
/**
* Returns the result of getWiFiStats from the global NS or does nothing
(returns empty result).
* Fixes a concurrency problem where we need to pass a function when creating
* a JitsiConference, but that method is added to the context later.
*
* @returns {Promise}
* @private
*/
const getWiFiStatsMethod = () => {
const gloabalNS = getJitsiMeetGlobalNS();
return gloabalNS.getWiFiStats ? gloabalNS.getWiFiStats() : Promise.resolve('{}');
};
/**
* Shows the suboptimal experience notification if needed.
*
* @param {Function} dispatch - The dispatch method.
* @param {Function} t - The translation function.
* @returns {void}
*/
export function maybeShowSuboptimalExperienceNotification(dispatch, t) {
if (isSuboptimalBrowser()) {
dispatch(
showWarningNotification(
{
titleKey: 'notify.suboptimalExperienceTitle',
description: translateToHTML(
t,
'notify.suboptimalBrowserWarning',
{
recommendedBrowserPageLink: `${window.location.origin}/static/recommendedBrowsers.html`
}
)
}
)
);
}
}
/**
* Returns an object aggregating the conference options.
*
* @param {Object|Function} stateful - The redux store state.
* @returns {Object} - Options object.
*/
export function getConferenceOptions(stateful) {
const state = toState(stateful);
const options = state['features/base/config'];
const { locationURL } = state['features/base/connection'];
const { tenant } = state['features/base/jwt'];
const { email, name: nick } = getLocalParticipant(state);
if (tenant) {
options.siteID = tenant;
}
if (options.enableDisplayNameInStats && nick) {
options.statisticsDisplayName = nick;
}
if (options.enableEmailInStats && email) {
options.statisticsId = email;
}
options.applicationName = getName();
options.getWiFiStatsMethod = getWiFiStatsMethod;
options.confID = `${locationURL.host}${getBackendSafePath(locationURL.pathname)}`;
options.createVADProcessor = createRnnoiseProcessorPromise;
// Disable CallStats, if requessted.
if (options.disableThirdPartyRequests) {
delete options.callStatsID;
delete options.callStatsSecret;
delete options.getWiFiStatsMethod;
}
return options;
}

View File

@@ -435,16 +435,20 @@ export function _mapDispatchToProps(dispatch: Dispatch<any>) {
*/
export function _mapStateToProps(state: Object, ownProps: Props) {
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.VIDEO, ownProps.participantId);
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.AUDIO, ownProps.participantId);
const conference = state['features/base/conference'].conference;
return {
audioSsrc: firstAudioTrack
? state['features/base/conference'].conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
videoSsrc: firstVideoTrack
? state['features/base/conference'].conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
};
if (conference) {
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.VIDEO, ownProps.participantId);
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.AUDIO, ownProps.participantId);
return {
audioSsrc: firstAudioTrack ? conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
videoSsrc: firstVideoTrack ? conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
};
}
return {};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicator));

View File

@@ -92,15 +92,11 @@ class LobbyScreen extends AbstractLobbyScreen {
const { t } = this.props;
return (
<div className = 'participant-info'>
<div className = 'form'>
<InputField
onChange = { this._onChangeDisplayName }
placeHolder = { t('lobby.nameField') }
testId = 'lobby.nameField'
value = { displayName } />
</div>
</div>
<InputField
onChange = { this._onChangeDisplayName }
placeHolder = { t('lobby.nameField') }
testId = 'lobby.nameField'
value = { displayName } />
);
}
@@ -113,15 +109,13 @@ class LobbyScreen extends AbstractLobbyScreen {
const { _passwordJoinFailed, t } = this.props;
return (
<div className = 'form'>
<InputField
className = { _passwordJoinFailed ? 'error' : '' }
onChange = { this._onChangePassword }
placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
testId = 'lobby.password'
type = 'password'
value = { this.state.password } />
</div>
<InputField
className = { _passwordJoinFailed ? 'error' : '' }
onChange = { this._onChangePassword }
placeHolder = { _passwordJoinFailed ? t('lobby.invalidPassword') : t('lobby.passwordField') }
testId = 'lobby.password'
type = 'password'
value = { this.state.password } />
);
}

View File

@@ -5,7 +5,7 @@ import React, { Component } from 'react';
import { getRoomName } from '../../base/conference';
import { translate } from '../../base/i18n';
import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
import { Icon, IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
import { isVideoMutedByUser } from '../../base/media';
import { ActionButton, InputField, PreMeetingScreen, ToggleButton } from '../../base/premeeting';
import { connect } from '../../base/redux';
@@ -316,6 +316,8 @@ class Prejoin extends Component<Props, State> {
<div className = 'prejoin-input-area'>
<InputField
autoFocus = { true }
className = { showError ? 'error' : '' }
hasError = { showError }
onChange = { _setName }
onSubmit = { joinConference }
placeHolder = { t('dialog.enterDisplayName') }
@@ -352,6 +354,7 @@ class Prejoin extends Component<Props, State> {
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
<ActionButton
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
hasOptions = { true }
onClick = { _onJoinButtonClick }
onOptionsClick = { _onOptionsClick }

View File

@@ -2,11 +2,13 @@
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import { batch } from 'react-redux';
import { BaseApp } from '../../../features/base/app';
import { setConfig } from '../../base/config';
import { createPrejoinTracks } from '../../base/tracks';
import { initPrejoin } from '../actions';
import { getConferenceOptions } from '../../conference/functions';
import { initPrejoin, makePrecallTest } from '../actions';
import Prejoin from './Prejoin';
@@ -70,7 +72,10 @@ export default class PrejoinApp extends BaseApp<Props> {
const tracks = await tryCreateLocalTracks;
dispatch(initPrejoin(tracks, errors));
batch(() => {
dispatch(initPrejoin(tracks, errors));
dispatch(makePrecallTest(getConferenceOptions(store.getState())));
});
});
}

View File

@@ -0,0 +1,48 @@
// @flow
import { translate } from '../../base/i18n';
import { IconTrash } from '../../base/icons';
import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { deleteRecentListEntry } from '../actions';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The ID of the entry to be deleted.
*/
itemId: Object,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
/**
* A recent list menu button which deletes the selected entry.
*/
class DeleteItemButton extends AbstractButton<Props, *> {
accessibilityLabel = 'welcomepage.recentListDelete';
icon = IconTrash;
label = 'welcomepage.recentListDelete';
/**
* Handles clicking / pressing the button.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, itemId } = this.props;
dispatch(deleteRecentListEntry(itemId));
}
}
export default translate(connect()(DeleteItemButton));

View File

@@ -4,16 +4,14 @@ import React from 'react';
import type { Dispatch } from 'redux';
import { getDefaultURL } from '../../app/functions';
import { openDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n';
import { setActiveModalId } from '../../base/modal';
import { NavigateSectionList, type Section } from '../../base/react';
import { connect } from '../../base/redux';
import { ColorPalette } from '../../base/styles';
import { DIAL_IN_SUMMARY_VIEW_ID } from '../../invite/constants';
import { deleteRecentListEntry } from '../actions';
import { isRecentListEnabled, toDisplayableList } from '../functions';
import AbstractRecentList from './AbstractRecentList';
import RecentListItemMenu from './RecentListItemMenu.native';
/**
* The type of the React {@code Component} props of {@link RecentList}
@@ -62,8 +60,8 @@ class RecentList extends AbstractRecentList<Props> {
constructor(props: Props) {
super(props);
this._onDelete = this._onDelete.bind(this);
this._onShowDialInInfo = this._onShowDialInInfo.bind(this);
// Bind event handlers so they are only bound once per instance.
this._onLongPress = this._onLongPress.bind(this);
}
/**
@@ -82,50 +80,29 @@ class RecentList extends AbstractRecentList<Props> {
_recentList
} = this.props;
const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
const slideActions = [ {
backgroundColor: ColorPalette.blue,
onPress: this._onShowDialInInfo,
text: t('welcomepage.info')
}, {
backgroundColor: 'red',
onPress: this._onDelete,
text: t('welcomepage.recentListDelete')
} ];
return (
<NavigateSectionList
disabled = { disabled }
onLongPress = { this._onLongPress }
onPress = { this._onPress }
renderListEmptyComponent
= { this._getRenderListEmptyComponent() }
sections = { recentList }
slideActions = { slideActions } />
sections = { recentList } />
);
}
_onDelete: Object => void
_onLongPress: (Object) => void;
/**
* Callback for the delete action of the list.
* Handles the list's navigate action.
*
* @param {Object} itemId - The ID of the entry thats deletion is
* requested.
* @private
* @param {Object} item - The item which was long pressed.
* @returns {void}
*/
_onDelete(itemId) {
this.props.dispatch(deleteRecentListEntry(itemId));
}
_onShowDialInInfo: Object => void
/**
* Callback for the dial-in info action of the list.
*
* @param {Object} itemId - The ID of the entry for which we'd like to show the dial in numbers.
* @returns {void}
*/
_onShowDialInInfo(itemId) {
this.props.dispatch(setActiveModalId(DIAL_IN_SUMMARY_VIEW_ID, { summaryUrl: itemId.url }));
_onLongPress(item) {
this.props.dispatch(openDialog(RecentListItemMenu, { item }));
}
}
@@ -133,10 +110,7 @@ class RecentList extends AbstractRecentList<Props> {
* Maps redux state to component props.
*
* @param {Object} state - The redux state.
* @returns {{
* _defaultServerURL: string,
* _recentList: Array
* }}
* @returns {Props}
*/
export function _mapStateToProps(state: Object) {
return {

View File

@@ -0,0 +1,143 @@
// @flow
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { BottomSheet, hideDialog, isDialogOpen } from '../../base/dialog';
import { type Item } from '../../base/react/Types';
import { connect } from '../../base/redux';
import { StyleType } from '../../base/styles';
import DeleteItemButton from './DeleteItemButton.native';
import ShowDialInInfoButton from './ShowDialInInfoButton.native';
import styles from './styles';
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* Item being rendered in this menu.
*/
item: Item,
/**
* The color-schemed stylesheet of the BottomSheet.
*/
_bottomSheetStyles: StyleType,
/**
* True if the menu is currently open, false otherwise.
*/
_isOpen: boolean
}
// eslint-disable-next-line prefer-const
let RecentListItemMenu_;
/**
* Class to implement a popup menu that opens upon long pressing a recent list item.
*/
class RecentListItemMenu extends PureComponent<Props> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onCancel = this._onCancel.bind(this);
this._renderMenuHeader = this._renderMenuHeader.bind(this);
}
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const { _bottomSheetStyles, item } = this.props;
const buttonProps = {
afterClick: this._onCancel,
itemId: item.id,
showLabel: true,
styles: _bottomSheetStyles.buttons
};
return (
<BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuHeader }>
<DeleteItemButton { ...buttonProps } />
<ShowDialInInfoButton { ...buttonProps } />
</BottomSheet>
);
}
_onCancel: () => boolean;
/**
* Callback to hide this menu.
*
* @private
* @returns {boolean}
*/
_onCancel() {
if (this.props._isOpen) {
this.props.dispatch(hideDialog(RecentListItemMenu_));
return true;
}
return false;
}
_renderMenuHeader: () => React$Element<any>;
/**
* Function to render the menu's header.
*
* @returns {React$Element}
*/
_renderMenuHeader() {
const { _bottomSheetStyles, item } = this.props;
return (
<View
style = { [
_bottomSheetStyles.sheet,
styles.entryNameContainer
] }>
<Text
ellipsizeMode = { 'middle' }
numberOfLines = { 1 }
style = { styles.entryNameLabel }>
{ item.title }
</Text>
</View>
);
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
_isOpen: isDialogOpen(state, RecentListItemMenu_)
};
}
RecentListItemMenu_ = connect(_mapStateToProps)(RecentListItemMenu);
export default RecentListItemMenu_;

View File

@@ -0,0 +1,49 @@
// @flow
import { translate } from '../../base/i18n';
import { IconInfo } from '../../base/icons';
import { setActiveModalId } from '../../base/modal';
import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { DIAL_IN_SUMMARY_VIEW_ID } from '../../invite/constants';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The ID of the entry to be deleted.
*/
itemId: Object,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
/**
* A recent list menu button which opens the dial-in info dialog.
*/
class ShowDialInInfoButton extends AbstractButton<Props, *> {
accessibilityLabel = 'welcomepage.info';
icon = IconInfo;
label = 'welcomepage.info';
/**
* Handles clicking / pressing the button.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, itemId } = this.props;
dispatch(setActiveModalId(DIAL_IN_SUMMARY_VIEW_ID, { summaryUrl: itemId.url }));
}
}
export default translate(connect()(ShowDialInInfoButton));

View File

@@ -1,4 +1,4 @@
import { createStyleSheet } from '../../base/styles';
import { ColorPalette, createStyleSheet } from '../../base/styles';
/**
* The styles of the React {@code Component}s of the feature recent-list i.e.
@@ -22,5 +22,23 @@ export default createStyleSheet({
alignItems: 'center',
justifyContent: 'center',
padding: 20
},
entryNameContainer: {
alignItems: 'center',
borderBottomColor: ColorPalette.lightGrey,
borderBottomWidth: 1,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
flexDirection: 'row',
justifyContent: 'center',
height: 48
},
entryNameLabel: {
color: ColorPalette.lightGrey,
flexShrink: 1,
fontSize: 16,
opacity: 0.90
}
});

View File

@@ -1,11 +1,12 @@
// @flow
import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Avatar } from '../../../base/avatar';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, isDialogOpen } from '../../../base/dialog';
import { KICK_OUT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { getParticipantDisplayName } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
@@ -67,7 +68,7 @@ let RemoteVideoMenu_;
/**
* Class to implement a popup menu that opens upon long pressing a thumbnail.
*/
class RemoteVideoMenu extends Component<Props> {
class RemoteVideoMenu extends PureComponent<Props> {
/**
* Constructor of the component.
*
@@ -77,6 +78,7 @@ class RemoteVideoMenu extends Component<Props> {
super(props);
this._onCancel = this._onCancel.bind(this);
this._renderMenuHeader = this._renderMenuHeader.bind(this);
}
/**
@@ -93,32 +95,15 @@ class RemoteVideoMenu extends Component<Props> {
styles: this.props._bottomSheetStyles.buttons
};
const buttons = [];
if (!_disableRemoteMute) {
buttons.push(<MuteButton { ...buttonProps } />);
}
buttons.push(<GrantModeratorButton { ...buttonProps } />);
if (!_disableKick) {
buttons.push(<KickButton { ...buttonProps } />);
}
buttons.push(<PinButton { ...buttonProps } />);
buttons.push(<PrivateMessageButton { ...buttonProps } />);
return (
<BottomSheet onCancel = { this._onCancel }>
<View style = { styles.participantNameContainer }>
<Avatar
participantId = { participant.id }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
{ this.props._participantDisplayName }
</Text>
</View>
{ buttons }
<BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuHeader }>
{ !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
{ !_disableKick && <KickButton { ...buttonProps } /> }
<GrantModeratorButton { ...buttonProps } />
<PinButton { ...buttonProps } />
<PrivateMessageButton { ...buttonProps } />
</BottomSheet>
);
}
@@ -140,6 +125,31 @@ class RemoteVideoMenu extends Component<Props> {
return false;
}
_renderMenuHeader: () => React$Element<any>;
/**
* Function to render the menu's header.
*
* @returns {React$Element}
*/
_renderMenuHeader() {
const { _bottomSheetStyles, participant } = this.props;
return (
<View
style = { [
_bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
<Avatar
participantId = { participant.id }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
{ this.props._participantDisplayName }
</Text>
</View>
);
}
}
/**
@@ -151,9 +161,12 @@ class RemoteVideoMenu extends Component<Props> {
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
const { participant } = ownProps;
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
const { disableKick } = remoteVideoMenu;
let { disableKick } = remoteVideoMenu;
disableKick = disableKick || !kickOutEnabled;
return {
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),

View File

@@ -10,10 +10,13 @@ import { ColorPalette, createStyleSheet } from '../../../base/styles';
export default createStyleSheet({
participantNameContainer: {
alignItems: 'center',
borderBottomColor: ColorPalette.darkGrey,
borderBottomColor: ColorPalette.lightGrey,
borderBottomWidth: 1,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
flexDirection: 'row',
height: MD_ITEM_HEIGHT
height: MD_ITEM_HEIGHT,
paddingLeft: MD_ITEM_MARGIN_PADDING
},
participantNameLabel: {

View File

@@ -21,6 +21,7 @@ import { findNearestQualityLevel } from '../functions';
* @type {Object}
*/
const VIDEO_QUALITY_TO_ICON = {
[VIDEO_QUALITY_LEVELS.ULTRA]: IconVideoQualityHD,
[VIDEO_QUALITY_LEVELS.HIGH]: IconVideoQualityHD,
[VIDEO_QUALITY_LEVELS.STANDARD]: IconVideoQualitySD,
[VIDEO_QUALITY_LEVELS.LOW]: IconVideoQualityLD

View File

@@ -14,6 +14,7 @@ import { VIDEO_QUALITY_LEVELS } from '../constants';
import logger from '../logger';
const {
ULTRA,
HIGH,
STANDARD,
LOW
@@ -97,6 +98,7 @@ class VideoQualitySlider extends Component<Props> {
this._enableLowDefinition = this._enableLowDefinition.bind(this);
this._enableStandardDefinition
= this._enableStandardDefinition.bind(this);
this._enableUltraHighDefinition = this._enableUltraHighDefinition.bind(this);
this._onSliderChange = this._onSliderChange.bind(this);
/**
@@ -125,9 +127,9 @@ class VideoQualitySlider extends Component<Props> {
videoQuality: STANDARD
},
{
onSelect: this._enableHighDefinition,
onSelect: this._enableUltraHighDefinition,
textKey: 'videoStatus.highDefinition',
videoQuality: HIGH
videoQuality: ULTRA
}
];
}
@@ -298,6 +300,21 @@ class VideoQualitySlider extends Component<Props> {
this._setPreferredVideoQuality(STANDARD);
}
_enableUltraHighDefinition: () => void;
/**
* Dispatches an action to receive ultra HD quality video from remote
* participants.
*
* @private
* @returns {void}
*/
_enableUltraHighDefinition() {
sendAnalytics(createEvent('ultra high'));
logger.log('Video quality: ultra high enabled');
this._setPreferredVideoQuality(ULTRA);
}
/**
* Matches the current video quality state with corresponding index of the
* component's slider options.

View File

@@ -5,6 +5,7 @@
* @type {object}
*/
export const VIDEO_QUALITY_LEVELS = {
ULTRA: 2160,
HIGH: 720,
STANDARD: 360,
LOW: 180

View File

@@ -2,8 +2,8 @@
import { CFG_LVL_TO_APP_QUALITY_LVL, VIDEO_QUALITY_LEVELS } from './constants';
const { LOW, STANDARD, HIGH } = VIDEO_QUALITY_LEVELS;
const videoQualityLevels = [ LOW, STANDARD, HIGH ];
const { LOW, STANDARD, HIGH, ULTRA } = VIDEO_QUALITY_LEVELS;
const videoQualityLevels = [ LOW, STANDARD, HIGH, ULTRA ];
/**
* Finds the nearest video quality level to the passed video quality.

View File

@@ -81,7 +81,7 @@ StateListenerRegistry.register(
const { maxReceiverVideoQuality } = state['features/video-quality'];
const { maxFullResolutionParticipants = 2 } = state['features/base/config'];
let newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.HIGH;
let newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.ULTRA;
if (reducedUI) {
newMaxRecvVideoQuality = VIDEO_QUALITY_LEVELS.LOW;

View File

@@ -7,9 +7,9 @@ import { validateMinHeightForQualityLvl } from './functions';
import logger from './logger';
const DEFAULT_STATE = {
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.HIGH,
maxReceiverVideoQuality: VIDEO_QUALITY_LEVELS.ULTRA,
minHeightForQualityLvl: new Map(),
preferredVideoQuality: VIDEO_QUALITY_LEVELS.HIGH
preferredVideoQuality: VIDEO_QUALITY_LEVELS.ULTRA
};
DEFAULT_STATE.minHeightForQualityLvl.set(360, VIDEO_QUALITY_LEVELS.STANDARD);

View File

@@ -9,7 +9,7 @@ pushd ${THIS_DIR}/..
npm install github:jitsi/lib-jitsi-meet#${LATEST_LJM_COMMIT}
git add package.json package-lock.json
git commit -m "deps: lib-jitsi-meet@latest"
git commit -m "chore(deps) lib-jitsi-meet@latest"
popd

View File

@@ -171,6 +171,7 @@ const config = {
].filter(Boolean),
resolve: {
alias: {
'focus-visible': 'focus-visible/dist/focus-visible.min.js',
jquery: `jquery/dist/jquery${minimize ? '.min' : ''}.js`
},
aliasFields: [