mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-07-17 05:17:47 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45aa8606e6 | ||
|
|
8be02f9ca1 | ||
|
|
3c25a4c08c | ||
|
|
5ade0cad8b | ||
|
|
0fa6ffc439 | ||
|
|
c2ed296178 | ||
|
|
febd12b871 | ||
|
|
0a06e256b7 | ||
|
|
f295f60bea | ||
|
|
4a8f787519 | ||
|
|
d85b869934 | ||
|
|
35130f0736 | ||
|
|
1feff9709c | ||
|
|
1010f53a84 | ||
|
|
f7a526f488 | ||
|
|
245eb89b85 | ||
|
|
99de9d0bfa | ||
|
|
98698ba89a | ||
|
|
19d1e3829d | ||
|
|
612586ed1f | ||
|
|
2609e43f29 | ||
|
|
c5cd4f534c | ||
|
|
6e10ca5dd2 | ||
|
|
0fff1c3534 |
@@ -1,4 +1,4 @@
|
||||
osx_image: xcode10.2
|
||||
osx_image: xcode11.1
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
|
||||
@@ -45,6 +45,7 @@ dependencies {
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.amplitude:android-sdk:2.14.1'
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.squareup.duktape.Duktape;
|
||||
|
||||
@ReactModule(name = JavaScriptSandboxModule.NAME)
|
||||
class JavaScriptSandboxModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "JavaScriptSandbox";
|
||||
|
||||
public JavaScriptSandboxModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given code in a Duktape VM.
|
||||
* @param code - The code that needs to evaluated.
|
||||
* @param promise - Resolved with the output in case of success or rejected with an exception
|
||||
* in case of failure.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void evaluate(String code, Promise promise) {
|
||||
Duktape vm = Duktape.create();
|
||||
try {
|
||||
Object res = vm.evaluate(code);
|
||||
promise.resolve(res.toString());
|
||||
} catch (Throwable tr) {
|
||||
promise.reject(tr);
|
||||
} finally {
|
||||
vm.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
final String action = intent.getAction();
|
||||
if (action.equals(Actions.START)) {
|
||||
if (Actions.START.equals(action)) {
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
@@ -96,7 +96,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
} else if (action.equals(Actions.HANGUP)) {
|
||||
} else if (Actions.HANGUP.equals(action)) {
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
// Abort all ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
|
||||
@@ -67,6 +67,7 @@ class ReactInstanceManagerHolder {
|
||||
new AudioModeModule(reactContext),
|
||||
new DropboxModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new JavaScriptSandboxModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new LogBridgeModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
|
||||
@@ -10,7 +10,7 @@ project(':react-native-community-async-storage').projectDir = new File(rootProje
|
||||
include ':react-native-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
|
||||
@@ -104,7 +104,6 @@ import {
|
||||
trackRemoved
|
||||
} from './react/features/base/tracks';
|
||||
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
||||
import { addMessage } from './react/features/chat';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import {
|
||||
@@ -114,7 +113,6 @@ import {
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { isButtonEnabled } from './react/features/toolbox';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
@@ -244,8 +242,6 @@ class ConferenceConnector {
|
||||
this._handleConferenceJoined.bind(this));
|
||||
room.on(JitsiConferenceEvents.CONFERENCE_FAILED,
|
||||
this._onConferenceFailed.bind(this));
|
||||
room.on(JitsiConferenceEvents.CONFERENCE_ERROR,
|
||||
this._onConferenceError.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,31 +344,6 @@ class ConferenceConnector {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_onConferenceError(err, ...params) {
|
||||
logger.error('CONFERENCE Error:', err, params);
|
||||
switch (err) {
|
||||
case JitsiConferenceErrors.CHAT_ERROR:
|
||||
logger.error('Chat error.', err);
|
||||
if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
|
||||
const [ code, msg ] = params;
|
||||
|
||||
APP.store.dispatch(addMessage({
|
||||
hasRead: true,
|
||||
error: code,
|
||||
message: msg,
|
||||
messageType: 'error',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.error('Unknown error.', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
14
config.js
14
config.js
@@ -1,16 +1,6 @@
|
||||
/* eslint-disable no-unused-vars, no-var */
|
||||
|
||||
var config = {
|
||||
// Configuration
|
||||
//
|
||||
|
||||
// Alternative location for the configuration.
|
||||
// configLocation: './config.json',
|
||||
|
||||
// Custom function which given the URL path should return a room name.
|
||||
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
|
||||
|
||||
|
||||
// Connection
|
||||
//
|
||||
|
||||
@@ -429,6 +419,10 @@ var config = {
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html'
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
|
||||
@@ -164,9 +164,45 @@ $watermarkHeight: 74px;
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
$welcomePageHeaderBackground: none;
|
||||
$welcomePageHeaderBackgroundSmall: none;
|
||||
$welcomePageHeaderBackgroundPosition: none;
|
||||
$welcomePageHeaderBackgroundRepeat: none;
|
||||
$welcomePageHeaderBackgroundSize: none;
|
||||
$welcomePageHeaderPaddingBottom: 0px;
|
||||
|
||||
$welcomePageHeaderTextMarginTop: 35px;
|
||||
$welcomePageHeaderTextMarginBottom: 35px;
|
||||
|
||||
$welcomePageHeaderTextTitleMarginBottom: 16px;
|
||||
|
||||
$welcomePageHeaderTextDescriptionDisplay: inherit;
|
||||
|
||||
$welcomePageEnterRoomWidth: 680px;
|
||||
$welcomePageEnterRoomPadding: 25px 30px;
|
||||
$welcomePageEnterRoomBorderRadius: 0px;
|
||||
|
||||
$welcomePageEnterRoomInputContainerPadding: 0 8px 5px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderWidth: 0px 0px 2px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderStyle: solid;
|
||||
$welcomePageEnterRoomInputContainerBorderImage: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
|
||||
$welcomePageEnterRoomTitleDisplay: inherit;
|
||||
|
||||
$welcomePageTabContainerDisplay: flex;
|
||||
$welcomePageTabContentDisplay: inherit;
|
||||
$welcomePageTabButtonsDisplay: flex;
|
||||
$welcomePageTabDisplay: block;
|
||||
|
||||
$welcomePageButtonWidth: 51px;
|
||||
$welcomePageButtonHeight: 35px;
|
||||
$welcomePageButtonFontWeight: inherit;
|
||||
$welcomePageButtonBorderRadius: 4px;
|
||||
$welcomePageButtonLineHeight: 35px;
|
||||
|
||||
/**
|
||||
* Deep-linking page variables.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
.welcome {
|
||||
background-image: $welcomePageHeaderBackground;
|
||||
background-image: $welcomePageBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
@@ -13,6 +13,11 @@ body.welcome-page {
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
background-image: $welcomePageHeaderBackground;
|
||||
background-position: $welcomePageHeaderBackgroundPosition;
|
||||
background-repeat: $welcomePageHeaderBackgroundRepeat;
|
||||
background-size: $welcomePageHeaderBackgroundSize;
|
||||
padding-bottom: $welcomePageHeaderPaddingBottom;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -24,8 +29,8 @@ body.welcome-page {
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: $watermarkHeight + 35;
|
||||
margin-bottom: 35px;
|
||||
margin-top: $watermarkHeight + $welcomePageHeaderTextMarginTop;
|
||||
margin-bottom: $welcomePageHeaderTextMarginBottom;
|
||||
max-width: calc(100% - 40px);
|
||||
width: 650px;
|
||||
z-index: $zindex2;
|
||||
@@ -36,10 +41,11 @@ body.welcome-page {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.18;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: $welcomePageHeaderTextTitleMarginBottom;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
display: $welcomePageHeaderTextDescriptionDisplay;
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
@@ -51,23 +57,24 @@ body.welcome-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: calc(100% - 40px);
|
||||
width: 680px;
|
||||
width: $welcomePageEnterRoomWidth;
|
||||
z-index: $zindex2;
|
||||
background-color: #fff;
|
||||
padding: 25px 30px;
|
||||
padding: $welcomePageEnterRoomPadding;
|
||||
border-radius: $welcomePageEnterRoomBorderRadius;
|
||||
|
||||
.enter-room-input-container {
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 5px;
|
||||
padding: $welcomePageEnterRoomInputContainerPadding;
|
||||
text-align: left;
|
||||
color: #253858;
|
||||
height: fit-content;
|
||||
border-width: 0px 0px 2px 0px;
|
||||
border-style: solid;
|
||||
border-image: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
|
||||
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
|
||||
border-image: $welcomePageEnterRoomInputContainerBorderImage;
|
||||
|
||||
.enter-room-title {
|
||||
display: $welcomePageEnterRoomTitleDisplay;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
@@ -94,10 +101,11 @@ body.welcome-page {
|
||||
min-height: 354px;
|
||||
width: 710px;
|
||||
background: #75A7E7;
|
||||
display: flex;
|
||||
display: $welcomePageTabContainerDisplay;
|
||||
flex-direction: column;
|
||||
|
||||
.tab-content{
|
||||
display: $welcomePageTabContentDisplay;
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
@@ -111,13 +119,14 @@ body.welcome-page {
|
||||
.tab-buttons {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
flex-grow: 0;
|
||||
flex-direction: row;
|
||||
min-height: 54px;
|
||||
width: 100%;
|
||||
|
||||
.tab {
|
||||
display: $welcomePageTabDisplay;
|
||||
text-align: center;
|
||||
background: rgba(9,30,66,0.37);
|
||||
height: 55px;
|
||||
@@ -138,15 +147,16 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
width: 51px;
|
||||
height: 35px;
|
||||
width: $welcomePageButtonWidth;
|
||||
height: $welcomePageButtonHeight;
|
||||
font-size: 14px;
|
||||
font-weight: $welcomePageButtonFontWeight;
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
border-radius: $welcomePageButtonBorderRadius;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
line-height: $welcomePageButtonLineHeight;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
1
css/_welcome_page_settings_toolbar.scss
Normal file
1
css/_welcome_page_settings_toolbar.scss
Normal file
@@ -0,0 +1 @@
|
||||
/** Insert custom CSS for any additional content in the welcome page settings toolbar **/
|
||||
@@ -51,6 +51,7 @@ $flagsImagePath: "../images/";
|
||||
@import 'ringing/ringing';
|
||||
@import 'welcome_page';
|
||||
@import 'welcome_page_content';
|
||||
@import 'welcome_page_settings_toolbar';
|
||||
@import 'toolbars';
|
||||
@import 'jquery.contextMenu';
|
||||
@import 'keyboard-shortcuts';
|
||||
|
||||
@@ -148,6 +148,7 @@
|
||||
<!--#include virtual="title.html" -->
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
<!--#include virtual="static/welcomePageAdditionalContent.html" -->
|
||||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="react"></div>
|
||||
|
||||
@@ -27,6 +27,7 @@ var interfaceConfig = {
|
||||
SHOW_DEEP_LINKING_IMAGE: false,
|
||||
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: true,
|
||||
DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,
|
||||
APP_NAME: 'Jitsi Meet',
|
||||
NATIVE_APP_NAME: 'Jitsi Meet',
|
||||
PROVIDER_NAME: 'Jitsi',
|
||||
@@ -221,6 +222,13 @@ var interfaceConfig = {
|
||||
* milliseconds, those notifications should remain displayed.
|
||||
*/
|
||||
// ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT: 15000,
|
||||
|
||||
// List of undocumented settings
|
||||
/**
|
||||
INDICATOR_FONT_SIZES
|
||||
MOBILE_DYNAMIC_LINK
|
||||
PHONE_NUMBER_REGEX
|
||||
*/
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars, no-var, max-len */
|
||||
|
||||
@@ -62,7 +62,7 @@ target 'JitsiMeet' do
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'BVLinearGradient', :path => '../node_modules/react-native-linear-gradient'
|
||||
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-community/async-storage'
|
||||
pod 'RNGoogleSignin', :path => '../node_modules/react-native-google-signin'
|
||||
pod 'RNGoogleSignin', :path => '../node_modules/@react-native-community/google-signin'
|
||||
pod 'RNSound', :path => '../node_modules/react-native-sound'
|
||||
pod 'RNSVG', :path => '../node_modules/react-native-svg'
|
||||
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
PODS:
|
||||
- Amplitude-iOS (4.0.4)
|
||||
- AppAuth (1.2.0):
|
||||
- AppAuth/Core (= 1.2.0)
|
||||
- AppAuth/ExternalUserAgent (= 1.2.0)
|
||||
- AppAuth/Core (1.2.0)
|
||||
- AppAuth/ExternalUserAgent (1.2.0)
|
||||
- boost-for-react-native (1.63.0)
|
||||
- BVLinearGradient (2.5.6):
|
||||
- React
|
||||
@@ -62,18 +67,10 @@ PODS:
|
||||
- GoogleUtilities/Network (~> 5.2)
|
||||
- "GoogleUtilities/NSData+zlib (~> 5.2)"
|
||||
- nanopb (~> 0.3)
|
||||
- GoogleSignIn (4.4.0):
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
|
||||
- GoogleSignIn (5.0.1):
|
||||
- AppAuth (~> 1.2)
|
||||
- GTMAppAuth (~> 1.0)
|
||||
- GTMSessionFetcher/Core (~> 1.1)
|
||||
- GoogleToolboxForMac/DebugUtils (2.2.0):
|
||||
- GoogleToolboxForMac/Defines (= 2.2.0)
|
||||
- GoogleToolboxForMac/Defines (2.2.0)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.2.0)":
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.2.0)
|
||||
- GoogleToolboxForMac/Defines (= 2.2.0)
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.2.0)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (2.2.0)"
|
||||
- GoogleUtilities/AppDelegateSwizzler (5.4.1):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
@@ -92,7 +89,14 @@ PODS:
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (5.4.1):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMSessionFetcher/Core (1.2.1)
|
||||
- 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)
|
||||
- nanopb (0.3.901):
|
||||
- nanopb/decode (= 0.3.901)
|
||||
- nanopb/encode (= 0.3.901)
|
||||
@@ -272,7 +276,7 @@ PODS:
|
||||
- React
|
||||
- react-native-webrtc (1.75.0):
|
||||
- React
|
||||
- react-native-webview (5.8.1):
|
||||
- react-native-webview (7.4.1):
|
||||
- React
|
||||
- React-RCTActionSheet (0.61.1):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.61.1)
|
||||
@@ -330,8 +334,8 @@ PODS:
|
||||
- ReactCommon/turbomodule/core (= 0.61.1)
|
||||
- RNCAsyncStorage (1.3.4):
|
||||
- React
|
||||
- RNGoogleSignin (2.0.0):
|
||||
- GoogleSignIn (~> 4.4.0)
|
||||
- RNGoogleSignin (3.0.1):
|
||||
- GoogleSignIn (~> 5.0.0)
|
||||
- React
|
||||
- RNSound (0.11.0):
|
||||
- React
|
||||
@@ -386,7 +390,7 @@ DEPENDENCIES:
|
||||
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
|
||||
- ReactCommon/turbomodule (from `../node_modules/react-native/ReactCommon`)
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
|
||||
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
|
||||
- "RNGoogleSignin (from `../node_modules/@react-native-community/google-signin`)"
|
||||
- RNSound (from `../node_modules/react-native-sound`)
|
||||
- RNSVG (from `../node_modules/react-native-svg`)
|
||||
- RNWatch (from `../node_modules/react-native-watch-connectivity`)
|
||||
@@ -406,12 +410,14 @@ SPEC REPOS:
|
||||
- FirebaseDynamicLinks
|
||||
- FirebaseInstanceID
|
||||
- GoogleAppMeasurement
|
||||
- GoogleSignIn
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GTMSessionFetcher
|
||||
- nanopb
|
||||
- ObjectiveDropboxOfficial
|
||||
trunk:
|
||||
- AppAuth
|
||||
- GoogleSignIn
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
BVLinearGradient:
|
||||
@@ -479,7 +485,7 @@ EXTERNAL SOURCES:
|
||||
RNCAsyncStorage:
|
||||
:path: "../node_modules/@react-native-community/async-storage"
|
||||
RNGoogleSignin:
|
||||
:path: "../node_modules/react-native-google-signin"
|
||||
:path: "../node_modules/@react-native-community/google-signin"
|
||||
RNSound:
|
||||
:path: "../node_modules/react-native-sound"
|
||||
RNSVG:
|
||||
@@ -491,6 +497,7 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Amplitude-iOS: 2ad4d7270c99186236c1272a3a9425463b1ae1a7
|
||||
AppAuth: bce82c76043657c99d91e7882e8a9e1a93650cd4
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
|
||||
@@ -508,10 +515,10 @@ SPEC CHECKSUMS:
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
GoogleAppMeasurement: 6cf307834da065863f9faf4c0de0a936d81dd832
|
||||
GoogleSignIn: 7ff245e1a7b26d379099d3243a562f5747e23d39
|
||||
GoogleToolboxForMac: ff31605b7d66400dcec09bed5861689aebadda4d
|
||||
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
|
||||
GoogleUtilities: 1e25823cbf46540b4284f6ef8e17b3a68ee12bbc
|
||||
GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca
|
||||
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
|
||||
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
|
||||
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
|
||||
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
|
||||
RCTRequired: 53825815218847d3e9c7b6d92ad2d197a926d51e
|
||||
@@ -528,7 +535,7 @@ SPEC CHECKSUMS:
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-webrtc: c5e3d631179a933548a8e49bddbd8fad02586095
|
||||
react-native-webview: a95842e3f351a6d2c8bc8bcc9eab689c7e7e5ad4
|
||||
react-native-webview: 4dbc1d2a4a6b9c5e9e723c62651917aa2b5e579e
|
||||
React-RCTActionSheet: af4d951113b1e068bb30611f91b984a7a73597ff
|
||||
React-RCTAnimation: 4f518d70bb6890b7c3d9d732f84786d6693ca297
|
||||
React-RCTBlob: 072a4888c08de0eef6d04eaa727d25e577e6ff26
|
||||
@@ -540,12 +547,12 @@ SPEC CHECKSUMS:
|
||||
React-RCTVibration: 8be61459e3749d1fb02cf414edd05b3007622882
|
||||
ReactCommon: 4fba5be89efdf0b5720e0adb3d8d7edf6e532db0
|
||||
RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323
|
||||
RNGoogleSignin: d030c6c6591db24c3cee649f64c7babf0a1699a0
|
||||
RNGoogleSignin: 39336070b35fc4cea6a98cf111e00480317be0ae
|
||||
RNSound: c980916b596cc15c8dcd2f6ecd3b13c4881dbe20
|
||||
RNSVG: aac12785382e8fd4f28d072fe640612e34914631
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
Yoga: d8c572ddec8d05b7dba08e4e5f1924004a177078
|
||||
|
||||
PODFILE CHECKSUM: 0ac392c47cc78d9db03eea7b58950184336d6cf4
|
||||
PODFILE CHECKSUM: cb84b325b724c6ef7c8b24aa52ca7b6f681a095c
|
||||
|
||||
COCOAPODS: 1.8.1
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -52,8 +50,8 @@
|
||||
ReferencedContainer = "container:app.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -75,8 +73,6 @@
|
||||
ReferencedContainer = "container:app.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; };
|
||||
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AAC92317FFCD00290BEC /* LogUtils.h */; };
|
||||
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */; };
|
||||
DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -104,6 +105,7 @@
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = "<group>"; };
|
||||
DE65AAC92317FFCD00290BEC /* LogUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogUtils.h; sourceTree = "<group>"; };
|
||||
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetBaseLogHandler+Private.h"; sourceTree = "<group>"; };
|
||||
DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetUserInfo.h; sourceTree = "<group>"; };
|
||||
@@ -190,6 +192,7 @@
|
||||
A4A934E7212F3AB8001E9388 /* dropbox */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
DE438CD82350934700DD541D /* JavaScriptSandbox.m */,
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
|
||||
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
|
||||
DEFE535321FB1BF800011A3A /* JitsiMeet.m */,
|
||||
@@ -493,6 +496,7 @@
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
|
||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
|
||||
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */,
|
||||
DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -635,11 +639,7 @@
|
||||
INFOPLIST_FILE = src/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -670,11 +670,7 @@
|
||||
INFOPLIST_FILE = src/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
55
ios/sdk/src/JavaScriptSandbox.m
Normal file
55
ios/sdk/src/JavaScriptSandbox.m
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@import JavaScriptCore;
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
|
||||
@interface JavaScriptSandbox : NSObject<RCTBridgeModule>
|
||||
@end
|
||||
|
||||
@implementation JavaScriptSandbox
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Exported methods
|
||||
|
||||
RCT_EXPORT_METHOD(evaluate:(NSString *)code
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
__block BOOL hasError = NO;
|
||||
JSContext *ctx = [[JSContext alloc] init];
|
||||
ctx.exceptionHandler = ^(JSContext *context, JSValue *exception) {
|
||||
hasError = YES;
|
||||
reject(@"evaluate", [exception toString], nil);
|
||||
};
|
||||
JSValue *ret = [ctx evaluateScript:code];
|
||||
if (!hasError) {
|
||||
NSString *result = [ret toString];
|
||||
if (result == nil) {
|
||||
reject(@"evaluate", @"Error in string coercion", nil);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -90,8 +90,7 @@
|
||||
|
||||
if ([RNGoogleSignin application:app
|
||||
openURL:url
|
||||
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
|
||||
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]) {
|
||||
options:options]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"today": "Today"
|
||||
},
|
||||
"chat": {
|
||||
"error": "Error: your message \"{{originalText}}\" was not sent. Reason: {{error}}",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"messagebox": "Type a message",
|
||||
"messageTo": "Private message to {{recipient}}",
|
||||
"nickname": {
|
||||
@@ -275,6 +275,9 @@
|
||||
"dialOut": {
|
||||
"statusMessage": "is now {{status}}"
|
||||
},
|
||||
"documentSharing" : {
|
||||
"title": "Shared Document"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Average",
|
||||
"bad": "Bad",
|
||||
@@ -570,6 +573,7 @@
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hangup": "Leave the call",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"kick": "Kick participant",
|
||||
"localRecording": "Toggle local recording controls",
|
||||
@@ -611,6 +615,7 @@
|
||||
"exitTileView": "Exit tile view",
|
||||
"feedback": "Leave feedback",
|
||||
"hangup": "Leave",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
|
||||
@@ -15,7 +15,7 @@ import Filmstrip from './videolayout/Filmstrip';
|
||||
|
||||
import { getLocalParticipant } from '../../react/features/base/participants';
|
||||
import { toggleChat } from '../../react/features/chat';
|
||||
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
|
||||
import { setDocumentUrl } from '../../react/features/etherpad';
|
||||
import { setFilmstripVisible } from '../../react/features/filmstrip';
|
||||
import { setNotificationsEnabled } from '../../react/features/notifications';
|
||||
import {
|
||||
@@ -240,10 +240,12 @@ UI.initEtherpad = name => {
|
||||
return;
|
||||
}
|
||||
logger.log('Etherpad is enabled');
|
||||
etherpadManager
|
||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||
|
||||
APP.store.dispatch(setEtherpadHasInitialzied());
|
||||
etherpadManager = new EtherpadManager(eventEmitter);
|
||||
|
||||
const url = new URL(name, config.etherpad_base);
|
||||
|
||||
APP.store.dispatch(setDocumentUrl(url.toString()));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
|
||||
import { setDocumentEditingState } from '../../../react/features/etherpad';
|
||||
import { getSharedDocumentUrl, setDocumentEditingState } from '../../../react/features/etherpad';
|
||||
import { getToolboxHeight } from '../../../react/features/toolbox';
|
||||
|
||||
import VideoLayout from '../videolayout/VideoLayout';
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import Filmstrip from '../videolayout/Filmstrip';
|
||||
|
||||
/**
|
||||
* Etherpad options.
|
||||
*/
|
||||
const options = $.param({
|
||||
showControls: true,
|
||||
showChat: false,
|
||||
showLineNumbers: true,
|
||||
useMonospaceFont: false
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -70,13 +60,13 @@ class Etherpad extends LargeContainer {
|
||||
/**
|
||||
* Creates new Etherpad object
|
||||
*/
|
||||
constructor(domain, name) {
|
||||
constructor(url) {
|
||||
super();
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
|
||||
iframe.id = 'etherpadIFrame';
|
||||
iframe.src = `${domain + name}?${options}`;
|
||||
iframe.src = url;
|
||||
iframe.frameBorder = 0;
|
||||
iframe.scrolling = 'no';
|
||||
iframe.width = DEFAULT_WIDTH;
|
||||
@@ -199,13 +189,7 @@ export default class EtherpadManager {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(domain, name, eventEmitter) {
|
||||
if (!domain || !name) {
|
||||
throw new Error('missing domain or name');
|
||||
}
|
||||
|
||||
this.domain = domain;
|
||||
this.name = name;
|
||||
constructor(eventEmitter) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.etherpad = null;
|
||||
}
|
||||
@@ -228,7 +212,7 @@ export default class EtherpadManager {
|
||||
* Create new Etherpad frame.
|
||||
*/
|
||||
openEtherpad() {
|
||||
this.etherpad = new Etherpad(this.domain, this.name);
|
||||
this.etherpad = new Etherpad(getSharedDocumentUrl(APP.store.getState));
|
||||
VideoLayout.addLargeVideoContainer(
|
||||
ETHERPAD_CONTAINER_TYPE,
|
||||
this.etherpad
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Provider } from 'react-redux';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
|
||||
import { createThumbnailOffsetParentIsNullEvent, sendAnalytics } from '../../../react/features/analytics';
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
@@ -512,11 +513,31 @@ RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
|
||||
|
||||
$(streamElement).hide();
|
||||
|
||||
// If the container is currently visible
|
||||
// we attach the stream to the element.
|
||||
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
|
||||
this.waitForPlayback(streamElement, stream);
|
||||
stream.attach(streamElement);
|
||||
this.waitForPlayback(streamElement, stream);
|
||||
stream.attach(streamElement);
|
||||
|
||||
// TODO: Remove once we verify that this.container.offsetParent === null was the reason for not attached video
|
||||
// streams to the thumbnail.
|
||||
if (isVideo && this.container.offsetParent === null) {
|
||||
sendAnalytics(createThumbnailOffsetParentIsNullEvent(this.id));
|
||||
const parentNodesDisplayProps = [
|
||||
'#filmstripRemoteVideosContainer',
|
||||
'#filmstripRemoteVideos',
|
||||
'#remoteVideos',
|
||||
'.filmstrip',
|
||||
'#videospace',
|
||||
'#videoconference_page',
|
||||
'#react'
|
||||
].map(selector => `${selector} - ${$(selector).css('display')}`);
|
||||
const videoConferencePageParent = $('#videoconference_page').parent();
|
||||
const reactDiv = document.getElementById('react');
|
||||
|
||||
parentNodesDisplayProps.push(
|
||||
`${videoConferencePageParent.attr('class')} - ${videoConferencePageParent.css('display')}`);
|
||||
parentNodesDisplayProps.push(`this.container - ${this.$container.css('display')}`);
|
||||
logger.debug(`this.container.offsetParent is null [user: ${this.id}, ${
|
||||
parentNodesDisplayProps.join(', ')}, #react.offsetParent - ${
|
||||
reactDiv && reactDiv.offsetParent !== null ? 'not null' : 'null'}]`);
|
||||
}
|
||||
|
||||
if (!isVideo) {
|
||||
|
||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -3062,6 +3062,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-native-community/google-signin": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/google-signin/-/google-signin-3.0.1.tgz",
|
||||
"integrity": "sha512-RC9c7ATGdq5IKFqw/h4d8eVTDve8FZxMtsarBHKfP09SrQfEgvOebzVr7YNC+4qs7dFqR+0E2vD7nle1s/dQ3A=="
|
||||
},
|
||||
"@react-native-community/netinfo": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.1.5.tgz",
|
||||
@@ -11681,8 +11686,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#16e08408aa3fa8303e236b9ffdb0dfce65e79bb7",
|
||||
"from": "github:jitsi/lib-jitsi-meet#16e08408aa3fa8303e236b9ffdb0dfce65e79bb7",
|
||||
"version": "github:jitsi/lib-jitsi-meet#f9808adb8eb523bae3318f9f8ef49b544651485f",
|
||||
"from": "github:jitsi/lib-jitsi-meet#f9808adb8eb523bae3318f9f8ef49b544651485f",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.2",
|
||||
@@ -15441,11 +15446,6 @@
|
||||
"jssha": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"react-native-google-signin": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-2.0.0.tgz",
|
||||
"integrity": "sha512-9loM4lcCIdbco5BnmNio7yGaXQKCpCaY1VRmYiTSvC5NjuSf6Ui6jZRee46p/YdaU4yRnS3u5Vct6Psrvr0HNg=="
|
||||
},
|
||||
"react-native-immersive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz",
|
||||
@@ -15520,14 +15520,19 @@
|
||||
}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-5.8.1.tgz",
|
||||
"integrity": "sha512-b6pSvmjoiWtcz6YspggW02X+BRXJWuquHwkh37BRx1NMW1iwMZA31SnFQvTpPzWYYIb9WF/mRsy2nGtt9C6NIg==",
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-7.4.1.tgz",
|
||||
"integrity": "sha512-AVT5HIEEWc/NZdNwXRVev0cAs1Si0O3BA4Crqyor8JbwuxUUCllLv+NK7TO3eOw/ENl+QyIPHu9dizkoycmgJQ==",
|
||||
"requires": {
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"escape-string-regexp": "2.0.0",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -16417,35 +16422,6 @@
|
||||
"version": "github:jitsi/rnnoise-wasm#db96d11f175a22ef56c7db1ba9550835b716e615",
|
||||
"from": "github:jitsi/rnnoise-wasm#db96d11f175a22ef56c7db1ba9550835b716e615"
|
||||
},
|
||||
"rollup-plugin-visualizer": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-1.1.1.tgz",
|
||||
"integrity": "sha512-7xkSKp+dyJmSC7jg2LXqViaHuOnF1VvIFCnsZEKjrgT5ZVyiLLSbeszxFcQSfNJILphqgAEmWAUz0Z4xYScrRw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"opn": "^5.4.0",
|
||||
"source-map": "^0.7.3",
|
||||
"typeface-oswald": "0.0.54"
|
||||
},
|
||||
"dependencies": {
|
||||
"opn": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
|
||||
"integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"rsvp": {
|
||||
"version": "4.8.5",
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@atlaskit/tooltip": "12.1.13",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
"@react-native-community/async-storage": "1.3.4",
|
||||
"@react-native-community/google-signin": "3.0.1",
|
||||
"@react-native-community/netinfo": "4.1.5",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow-models/body-pix": "1.1.2",
|
||||
@@ -56,7 +57,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#16e08408aa3fa8303e236b9ffdb0dfce65e79bb7",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f9808adb8eb523bae3318f9f8ef49b544651485f",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -71,7 +72,6 @@
|
||||
"react-native-background-timer": "2.1.1",
|
||||
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2",
|
||||
"react-native-callstats": "3.61.0",
|
||||
"react-native-google-signin": "2.0.0",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
@@ -81,7 +81,7 @@
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#047b019a7ce1ec93ab4a2f6796e997d7a02e8e5d",
|
||||
"react-native-webview": "5.8.1",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
"react-transition-group": "2.4.0",
|
||||
|
||||
@@ -692,6 +692,21 @@ export function createSyncTrackStateEvent(mediaType, muted) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event that indicates the thumbnail offset parent is null.
|
||||
*
|
||||
* @param {string} id - The id of the user related to the thumbnail.
|
||||
* @returns {Object} The event in a format suitable for sending via sendAnalytics.
|
||||
*/
|
||||
export function createThumbnailOffsetParentIsNullEvent(id) {
|
||||
return {
|
||||
action: 'OffsetParentIsNull',
|
||||
attributes: {
|
||||
id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event associated with a toolbar button being clicked/pressed. By
|
||||
* convention, where appropriate an attribute named 'enable' should be used to
|
||||
|
||||
@@ -77,6 +77,11 @@ declare var APP: Object;
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addConferenceListeners(conference, dispatch) {
|
||||
// A simple logger for conference errors received through
|
||||
// the listener. These errors are not handled now, but logged.
|
||||
conference.on(JitsiConferenceEvents.CONFERENCE_ERROR,
|
||||
error => logger.error('Conference error.', error));
|
||||
|
||||
// Dispatches into features/base/conference follow:
|
||||
|
||||
conference.on(
|
||||
|
||||
137
react/features/base/config/configWhitelist.js
Normal file
137
react/features/base/config/configWhitelist.js
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.
|
||||
* The whitelist is used only for config.js.
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
export default [
|
||||
'_desktopSharingSourceDevice',
|
||||
'_peerConnStatusOutOfLastNTimeout',
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'abTesting',
|
||||
'analytics.disabled',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
'callFlowsEnabled',
|
||||
'callStatsConfIDNamespace',
|
||||
'callStatsID',
|
||||
'callStatsSecret',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js including while the call is ongoing in the
|
||||
* UI presented by CallKit and in the system-wide call history. The property
|
||||
* is meant for use cases in which the room name is not desirable as a
|
||||
* display name for CallKit purposes and the desired display name is not
|
||||
* provided in the form of a JWT callee. As the value is associated with a
|
||||
* conference/meeting, the value makes sense not as a deployment-wide
|
||||
* configuration, only as a runtime configuration override/overwrite
|
||||
* provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callDisplayName',
|
||||
|
||||
/**
|
||||
* The handle
|
||||
* ({@link https://developer.apple.com/documentation/callkit/cxhandle}) of
|
||||
* the CallKit call representing the conference/meeting associated with this
|
||||
* config.js. The property is meant for use cases in which the room URL is
|
||||
* not desirable as the handle for CallKit purposes. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callHandle',
|
||||
|
||||
/**
|
||||
* The UUID of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js. The property is meant for use cases in
|
||||
* which Jitsi Meet is to work with a CallKit call created outside of Jitsi
|
||||
* Meet and to be adopted by Jitsi Meet such as, for example, an incoming
|
||||
* and/or outgoing CallKit call created by Jitsi Meet SDK for iOS
|
||||
* clients/consumers prior to giving control to Jitsi Meet. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callUUID',
|
||||
|
||||
'channelLastN',
|
||||
'constraints',
|
||||
'debug',
|
||||
'debugAudioLevels',
|
||||
'defaultLanguage',
|
||||
'desktopSharingChromeDisabled',
|
||||
'desktopSharingChromeExtId',
|
||||
'desktopSharingChromeMinExtVersion',
|
||||
'desktopSharingChromeSources',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingFirefoxDisabled',
|
||||
'desktopSharingSources',
|
||||
'disable1On1Mode',
|
||||
'disableAEC',
|
||||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAudioLevels',
|
||||
'disableDeepLinking',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableNS',
|
||||
'disableRemoteControl',
|
||||
'disableRtx',
|
||||
'disableSuspendVideo',
|
||||
'displayJids',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'etherpad_base',
|
||||
'failICE',
|
||||
'fileRecordingsEnabled',
|
||||
'firefox_fake_device',
|
||||
'forceJVB121Ratio',
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hiddenDomain',
|
||||
'hosts',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
'iceTransportPolicy',
|
||||
'ignoreStartMuted',
|
||||
'liveStreamingEnabled',
|
||||
'localRecording',
|
||||
'minParticipants',
|
||||
'nick',
|
||||
'openBridgeChannel',
|
||||
'p2p',
|
||||
'preferH264',
|
||||
'requireDisplayName',
|
||||
'resolution',
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startVideoMuted',
|
||||
'startWithVideoMuted',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
'useNicks',
|
||||
'useStunTurn',
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable'
|
||||
];
|
||||
@@ -2,152 +2,12 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import CONFIG_WHITELIST from './configWhitelist';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
||||
import parseURLParams from './parseURLParams';
|
||||
import logger from './logger';
|
||||
|
||||
declare var $: Object;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The whitelist is used only for config.js.
|
||||
*
|
||||
* @private
|
||||
* @type Array
|
||||
*/
|
||||
const WHITELISTED_KEYS = [
|
||||
'_desktopSharingSourceDevice',
|
||||
'_peerConnStatusOutOfLastNTimeout',
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'abTesting',
|
||||
'analytics.disabled',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
'callFlowsEnabled',
|
||||
'callStatsConfIDNamespace',
|
||||
'callStatsID',
|
||||
'callStatsSecret',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js including while the call is ongoing in the
|
||||
* UI presented by CallKit and in the system-wide call history. The property
|
||||
* is meant for use cases in which the room name is not desirable as a
|
||||
* display name for CallKit purposes and the desired display name is not
|
||||
* provided in the form of a JWT callee. As the value is associated with a
|
||||
* conference/meeting, the value makes sense not as a deployment-wide
|
||||
* configuration, only as a runtime configuration override/overwrite
|
||||
* provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callDisplayName',
|
||||
|
||||
/**
|
||||
* The handle
|
||||
* ({@link https://developer.apple.com/documentation/callkit/cxhandle}) of
|
||||
* the CallKit call representing the conference/meeting associated with this
|
||||
* config.js. The property is meant for use cases in which the room URL is
|
||||
* not desirable as the handle for CallKit purposes. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callHandle',
|
||||
|
||||
/**
|
||||
* The UUID of the CallKit call representing the conference/meeting
|
||||
* associated with this config.js. The property is meant for use cases in
|
||||
* which Jitsi Meet is to work with a CallKit call created outside of Jitsi
|
||||
* Meet and to be adopted by Jitsi Meet such as, for example, an incoming
|
||||
* and/or outgoing CallKit call created by Jitsi Meet SDK for iOS
|
||||
* clients/consumers prior to giving control to Jitsi Meet. As the value is
|
||||
* associated with a conference/meeting, the value makes sense not as a
|
||||
* deployment-wide configuration, only as a runtime configuration
|
||||
* override/overwrite provided by, for example, Jitsi Meet SDK for iOS.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
'callUUID',
|
||||
|
||||
'channelLastN',
|
||||
'constraints',
|
||||
'debug',
|
||||
'debugAudioLevels',
|
||||
'defaultLanguage',
|
||||
'desktopSharingChromeDisabled',
|
||||
'desktopSharingChromeExtId',
|
||||
'desktopSharingChromeMinExtVersion',
|
||||
'desktopSharingChromeSources',
|
||||
'desktopSharingFrameRate',
|
||||
'desktopSharingFirefoxDisabled',
|
||||
'desktopSharingSources',
|
||||
'disable1On1Mode',
|
||||
'disableAEC',
|
||||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAudioLevels',
|
||||
'disableDeepLinking',
|
||||
'disableH264',
|
||||
'disableHPF',
|
||||
'disableNS',
|
||||
'disableRemoteControl',
|
||||
'disableRtx',
|
||||
'disableSuspendVideo',
|
||||
'displayJids',
|
||||
'e2eping',
|
||||
'enableDisplayNameInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
'enableTcc',
|
||||
'etherpad_base',
|
||||
'failICE',
|
||||
'fileRecordingsEnabled',
|
||||
'firefox_fake_device',
|
||||
'forceJVB121Ratio',
|
||||
'gatherStats',
|
||||
'googleApiApplicationClientID',
|
||||
'hiddenDomain',
|
||||
'hosts',
|
||||
'iAmRecorder',
|
||||
'iAmSipGateway',
|
||||
'iceTransportPolicy',
|
||||
'ignoreStartMuted',
|
||||
'liveStreamingEnabled',
|
||||
'localRecording',
|
||||
'minParticipants',
|
||||
'nick',
|
||||
'openBridgeChannel',
|
||||
'p2p',
|
||||
'preferH264',
|
||||
'requireDisplayName',
|
||||
'resolution',
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
'useNicks',
|
||||
'useStunTurn',
|
||||
'webrtcIceTcpDisable',
|
||||
'webrtcIceUdpDisable'
|
||||
];
|
||||
|
||||
// XXX The functions getRoomName and parseURLParams are split out of
|
||||
// functions.js because they are bundled in both app.bundle and
|
||||
// do_external_connect, webpack 1 does not support tree shaking, and we don't
|
||||
@@ -178,69 +38,6 @@ export function createFakeConfig(baseURL: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise wrapper on obtain config method. When HttpConfigFetch will be moved
|
||||
* to React app it's better to use load config instead.
|
||||
*
|
||||
* @param {string} location - URL of the domain from which the config is to be
|
||||
* obtained.
|
||||
* @param {string} room - Room name.
|
||||
* @private
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function obtainConfig(location: string, room: string): Promise<void> {
|
||||
return new Promise((resolve, reject) =>
|
||||
_obtainConfig(location, room, (success, error) => {
|
||||
success ? resolve() : reject(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends HTTP POST request to specified {@code endpoint}. In request the name
|
||||
* of the room is included in JSON format:
|
||||
* {
|
||||
* "rooomName": "someroom12345"
|
||||
* }.
|
||||
*
|
||||
* @param {string} endpoint - The name of HTTP endpoint to which to send
|
||||
* the HTTP POST request.
|
||||
* @param {string} roomName - The name of the conference room for which config
|
||||
* is requested.
|
||||
* @param {Function} complete - The callback to invoke upon success or failure.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _obtainConfig(endpoint: string, roomName: string, complete: Function) {
|
||||
logger.info(`Send config request to ${endpoint} for room: ${roomName}`);
|
||||
$.ajax(
|
||||
endpoint,
|
||||
{
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ roomName }),
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
|
||||
error(jqXHR, textStatus, errorThrown) {
|
||||
logger.error('Get config error: ', jqXHR, errorThrown);
|
||||
complete(false, `Get config response status: ${textStatus}`);
|
||||
},
|
||||
success(data) {
|
||||
const { config, interfaceConfig, loggingConfig } = window;
|
||||
|
||||
try {
|
||||
overrideConfigJSON(
|
||||
config, interfaceConfig, loggingConfig,
|
||||
data);
|
||||
complete(true);
|
||||
} catch (e) {
|
||||
logger.error('Parse config error: ', e);
|
||||
complete(false, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-disable max-params, no-shadow */
|
||||
|
||||
/**
|
||||
@@ -306,8 +103,8 @@ export function overrideConfigJSON(
|
||||
/* eslint-enable max-params, no-shadow */
|
||||
|
||||
/**
|
||||
* Whitelist only config.js, skips this for others configs
|
||||
* (interfaceConfig, loggingConfig).
|
||||
* Apply whitelist filtering for configs with whitelists, skips this for others
|
||||
* configs (loggingConfig).
|
||||
* Only extracts overridden values for keys we allow to be overridden.
|
||||
*
|
||||
* @param {string} configName - The config name, one of config,
|
||||
@@ -318,11 +115,13 @@ export function overrideConfigJSON(
|
||||
* that are whitelisted.
|
||||
*/
|
||||
function _getWhitelistedJSON(configName, configJSON) {
|
||||
if (configName !== 'config') {
|
||||
return configJSON;
|
||||
if (configName === 'interfaceConfig') {
|
||||
return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST);
|
||||
} else if (configName === 'config') {
|
||||
return _.pick(configJSON, CONFIG_WHITELIST);
|
||||
}
|
||||
|
||||
return _.pick(configJSON, WHITELISTED_KEYS);
|
||||
return configJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,8 +11,8 @@ export * from './functions.any';
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _cleanupConfig(config: Object) {
|
||||
config.analytics.scriptURLs = [];
|
||||
if (NativeModules.AppInfo.LIBRE_BUILD) {
|
||||
config.analytics.scriptURLs = [];
|
||||
delete config.analytics.amplitudeAPPKey;
|
||||
delete config.analytics.googleAnalyticsTrackingId;
|
||||
delete config.callStatsID;
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import { getBackendSafeRoomName } from '../util';
|
||||
|
||||
declare var config: Object;
|
||||
|
||||
/**
|
||||
* Builds and returns the room name.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function getRoomName(): ?string {
|
||||
const { getroomnode } = config;
|
||||
const path = window.location.pathname;
|
||||
let roomName;
|
||||
|
||||
// Determine the room node from the URL.
|
||||
if (getroomnode && typeof getroomnode === 'function') {
|
||||
roomName = getroomnode.call(config, path);
|
||||
} else {
|
||||
// Fall back to the default strategy of making assumptions about how the
|
||||
// URL maps to the room (name). It currently assumes a deployment in
|
||||
// which the last non-directory component of the path (name) is the
|
||||
// room.
|
||||
roomName = path.substring(path.lastIndexOf('/') + 1) || undefined;
|
||||
}
|
||||
// The last non-directory component of the path (name) is the room.
|
||||
const roomName = path.substring(path.lastIndexOf('/') + 1) || undefined;
|
||||
|
||||
return getBackendSafeRoomName(roomName);
|
||||
}
|
||||
|
||||
70
react/features/base/config/interfaceConfigWhitelist.js
Normal file
70
react/features/base/config/interfaceConfigWhitelist.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* The interface config keys to whitelist, the keys that can be overridden.
|
||||
*
|
||||
* @private
|
||||
* @type Array
|
||||
*/
|
||||
export default [
|
||||
'ANDROID_APP_PACKAGE',
|
||||
'APP_NAME',
|
||||
'APP_SCHEME',
|
||||
'AUDIO_LEVEL_PRIMARY_COLOR',
|
||||
'AUDIO_LEVEL_SECONDARY_COLOR',
|
||||
'AUTHENTICATION_ENABLE',
|
||||
'AUTO_PIN_LATEST_SCREEN_SHARE',
|
||||
'BRAND_WATERMARK_LINK',
|
||||
'CLOSE_PAGE_GUEST_HINT',
|
||||
'CONNECTION_INDICATOR_AUTO_HIDE_ENABLED',
|
||||
'CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT',
|
||||
'CONNECTION_INDICATOR_DISABLED',
|
||||
'DEFAULT_BACKGROUND',
|
||||
'DEFAULT_LOCAL_DISPLAY_NAME',
|
||||
'DEFAULT_REMOTE_DISPLAY_NAME',
|
||||
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
|
||||
'DISABLE_FOCUS_INDICATOR',
|
||||
'DISABLE_RINGING',
|
||||
'DISABLE_TRANSCRIPTION_SUBTITLES',
|
||||
'DISABLE_VIDEO_BACKGROUND',
|
||||
'DISPLAY_WELCOME_PAGE_CONTENT',
|
||||
'ENABLE_FEEDBACK_ANIMATION',
|
||||
'ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT',
|
||||
'FILM_STRIP_MAX_HEIGHT',
|
||||
'GENERATE_ROOMNAMES_ON_WELCOME_PAGE',
|
||||
'INDICATOR_FONT_SIZES',
|
||||
'INITIAL_TOOLBAR_TIMEOUT',
|
||||
'INVITATION_POWERED_BY',
|
||||
'JITSI_WATERMARK_LINK',
|
||||
'LANG_DETECTION',
|
||||
'LIVE_STREAMING_HELP_LINK',
|
||||
'LOCAL_THUMBNAIL_RATIO',
|
||||
'MAXIMUM_ZOOMING_COEFFICIENT',
|
||||
'MOBILE_APP_PROMO',
|
||||
'MOBILE_DOWNLOAD_LINK_ANDROID',
|
||||
'MOBILE_DOWNLOAD_LINK_IOS',
|
||||
'MOBILE_DYNAMIC_LINK',
|
||||
'NATIVE_APP_NAME',
|
||||
'OPTIMAL_BROWSERS',
|
||||
'PHONE_NUMBER_REGEX',
|
||||
'POLICY_LOGO',
|
||||
'PROVIDER_NAME',
|
||||
'RANDOM_AVATAR_URL_PREFIX',
|
||||
'RANDOM_AVATAR_URL_SUFFIX',
|
||||
'RECENT_LIST_ENABLED',
|
||||
'REMOTE_THUMBNAIL_RATIO',
|
||||
'SETTINGS_SECTIONS',
|
||||
'SHOW_BRAND_WATERMARK',
|
||||
'SHOW_DEEP_LINKING_IMAGE',
|
||||
'SHOW_JITSI_WATERMARK',
|
||||
'SHOW_POWERED_BY',
|
||||
'SHOW_WATERMARK_FOR_GUESTS',
|
||||
'SUPPORT_URL',
|
||||
'TILE_VIEW_MAX_COLUMNS',
|
||||
'TOOLBAR_ALWAYS_VISIBLE',
|
||||
'TOOLBAR_BUTTONS',
|
||||
'TOOLBAR_TIMEOUT',
|
||||
'UNSUPPORTED_BROWSERS',
|
||||
'VERTICAL_FILMSTRIP',
|
||||
'VIDEO_LAYOUT_FIT',
|
||||
'VIDEO_QUALITY_LABEL_DISABLED',
|
||||
'filmStripOnly'
|
||||
];
|
||||
@@ -61,11 +61,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet
|
||||
] }>
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
showsVerticalScrollIndicator = { false }>
|
||||
{ this._getWrappedContent() }
|
||||
</ScrollView>
|
||||
{ this._getWrappedContent() }
|
||||
</View>
|
||||
</View>
|
||||
</SlidingView>
|
||||
@@ -73,24 +69,32 @@ class BottomSheet extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the content when needed (iOS 11 and above), or just returns the original children.
|
||||
* Wraps the content when needed (iOS 11 and above), or just returns the original content.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_getWrappedContent() {
|
||||
const content = (
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
showsVerticalScrollIndicator = { false } >
|
||||
{ this.props.children }
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
const majorVersionIOS = parseInt(Platform.Version, 10);
|
||||
|
||||
if (majorVersionIOS > 10) {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
{ this.props.children }
|
||||
{ content }
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
react/features/base/icons/svg/help.svg
Normal file
1
react/features/base/icons/svg/help.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -28,6 +28,7 @@ export { default as IconExitFullScreen } from './exit-full-screen.svg';
|
||||
export { default as IconFeedback } from './feedback.svg';
|
||||
export { default as IconFullScreen } from './full-screen.svg';
|
||||
export { default as IconHangup } from './hangup.svg';
|
||||
export { default as IconHelp } from './help.svg';
|
||||
export { default as IconInfo } from './info.svg';
|
||||
export { default as IconInvite } from './invite.svg';
|
||||
export { default as IconKick } from './kick.svg';
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../redux';
|
||||
import { loadScript } from '../util';
|
||||
|
||||
import JitsiMeetJS from './_';
|
||||
import logger from './logger';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
@@ -97,42 +92,3 @@ export function isFatalJitsiConnectionError(error: Object | string) {
|
||||
|| error === JitsiConnectionErrors.OTHER_ERROR
|
||||
|| error === JitsiConnectionErrors.SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config.js from a specific remote server.
|
||||
*
|
||||
* @param {string} url - The URL to load.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function loadConfig(url: string): Promise<Object> {
|
||||
let promise;
|
||||
|
||||
if (typeof APP === 'undefined') {
|
||||
promise
|
||||
= loadScript(url, 2.5 * 1000 /* Timeout in ms */)
|
||||
.then(() => {
|
||||
const { config } = window;
|
||||
|
||||
// We don't want to pollute the global scope.
|
||||
window.config = undefined;
|
||||
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('window.config is not an object');
|
||||
}
|
||||
|
||||
return config;
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(`Failed to load config from ${url}`, err);
|
||||
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
// Return "the config.js file" from the global scope - that is how the
|
||||
// Web app on both the client and the server was implemented before the
|
||||
// React Native app was even conceived.
|
||||
promise = Promise.resolve(window.config);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
36
react/features/base/lib-jitsi-meet/functions.native.js
Normal file
36
react/features/base/lib-jitsi-meet/functions.native.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { loadScript } from '../util';
|
||||
import logger from './logger';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
const { JavaScriptSandbox } = NativeModules;
|
||||
|
||||
/**
|
||||
* Loads config.js from a specific remote server.
|
||||
*
|
||||
* @param {string} url - The URL to load.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function loadConfig(url: string): Promise<Object> {
|
||||
try {
|
||||
const configTxt = await loadScript(url, 2.5 * 1000 /* Timeout in ms */, true /* skipeval */);
|
||||
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
|
||||
const config = JSON.parse(configJson);
|
||||
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('config is not an object');
|
||||
}
|
||||
|
||||
logger.info(`Config loaded from ${url}`);
|
||||
|
||||
return config;
|
||||
} catch (err) {
|
||||
logger.error(`Failed to load config from ${url}`, err);
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
16
react/features/base/lib-jitsi-meet/functions.web.js
Normal file
16
react/features/base/lib-jitsi-meet/functions.web.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// @flow
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Loads config.js from a specific remote server.
|
||||
*
|
||||
* @param {string} url - The URL to load.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function loadConfig(url: string): Promise<Object> { // eslint-disable-line no-unused-vars
|
||||
// Return "the config.js file" from the global scope - that is how the
|
||||
// Web app on both the client and the server was implemented before the
|
||||
// React Native app was even conceived.
|
||||
return window.config;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './helpers';
|
||||
export * from './httpUtils';
|
||||
export * from './loadScript';
|
||||
export * from './openURLInBrowser';
|
||||
export * from './uri';
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { timeoutPromise } from './timeoutPromise';
|
||||
/**
|
||||
* Default timeout for loading scripts.
|
||||
*/
|
||||
const DEFAULT_TIMEOUT = 5000;
|
||||
|
||||
/**
|
||||
* Loads a script from a specific URL. React Native cannot load a JS
|
||||
@@ -13,63 +16,49 @@ import { timeoutPromise } from './timeoutPromise';
|
||||
* @param {number} [timeout] - The timeout in millisecnods after which the
|
||||
* loading of the specified {@code url} is to be aborted/rejected (if not
|
||||
* settled yet).
|
||||
* @param {boolean} skipEval - Wether we want to skip evaluating the loaded content or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function loadScript(url: string, timeout: ?number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// XXX The implementation of fetch on Android will throw an Exception on
|
||||
// the Java side which will break the app if the URL is invalid (which
|
||||
// the implementation of fetch on Android calls 'unexpected url'). In
|
||||
// order to try to prevent the breakage of the app, try to fail on an
|
||||
// invalid URL as soon as possible.
|
||||
const { hostname, pathname, protocol } = new URL(url);
|
||||
export async function loadScript(
|
||||
url: string, timeout: number = DEFAULT_TIMEOUT, skipEval: boolean = false): Promise<any> {
|
||||
// XXX The implementation of fetch on Android will throw an Exception on
|
||||
// the Java side which will break the app if the URL is invalid (which
|
||||
// the implementation of fetch on Android calls 'unexpected url'). In
|
||||
// order to try to prevent the breakage of the app, try to fail on an
|
||||
// invalid URL as soon as possible.
|
||||
const { hostname, pathname, protocol } = new URL(url);
|
||||
|
||||
// XXX The standard URL implementation should throw an Error if the
|
||||
// specified URL is relative. Unfortunately, the polyfill used on
|
||||
// react-native does not.
|
||||
if (!hostname || !pathname || !protocol) {
|
||||
reject(`unexpected url: ${url}`);
|
||||
// XXX The standard URL implementation should throw an Error if the
|
||||
// specified URL is relative. Unfortunately, the polyfill used on
|
||||
// react-native does not.
|
||||
if (!hostname || !pathname || !protocol) {
|
||||
throw new Error(`unexpected url: ${url}`);
|
||||
}
|
||||
|
||||
return;
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
|
||||
const response = await fetch(url, { signal });
|
||||
|
||||
// If the timeout hits the above will raise AbortError.
|
||||
|
||||
clearTimeout(timer);
|
||||
|
||||
switch (response.status) {
|
||||
case 200: {
|
||||
const txt = await response.text();
|
||||
|
||||
if (skipEval) {
|
||||
return txt;
|
||||
}
|
||||
|
||||
let fetch_ = fetch(url, { method: 'GET' });
|
||||
|
||||
// The implementation of fetch provided by react-native is based on
|
||||
// XMLHttpRequest. Which defines timeout as an unsigned long with
|
||||
// default value 0, which means there is no timeout.
|
||||
if (timeout) {
|
||||
// FIXME I don't like the approach with timeoutPromise because:
|
||||
//
|
||||
// * It merely abandons the underlying XHR and, consequently, opens
|
||||
// us to potential issues with NetworkActivityIndicator which
|
||||
// tracks XHRs.
|
||||
//
|
||||
// * @paweldomas also reported that timeouts seem to be respected by
|
||||
// the XHR implementation on iOS. Given that we have
|
||||
// implementation of loadScript based on fetch and XHR (in an
|
||||
// earlier revision), I don't see why we're not using an XHR
|
||||
// directly on iOS.
|
||||
//
|
||||
// * The approach of timeoutPromise I found on the Internet is to
|
||||
// directly use XHR instead of fetch and abort the XHR on timeout.
|
||||
// Which may deal with the NetworkActivityIndicator at least.
|
||||
fetch_ = timeoutPromise(fetch_, timeout);
|
||||
}
|
||||
|
||||
fetch_
|
||||
.then(response => {
|
||||
switch (response.status) {
|
||||
case 200:
|
||||
return response.responseText || response.text();
|
||||
|
||||
default:
|
||||
throw response.statusText;
|
||||
}
|
||||
})
|
||||
.then(responseText => {
|
||||
eval.call(window, responseText); // eslint-disable-line no-eval
|
||||
})
|
||||
.then(resolve, reject);
|
||||
});
|
||||
return eval.call(window, txt); // eslint-disable-line no-eval
|
||||
}
|
||||
default:
|
||||
throw new Error(`loadScript error: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
5
react/features/base/util/logger.js
Normal file
5
react/features/base/util/logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/util');
|
||||
17
react/features/base/util/openURLInBrowser.native.js
Normal file
17
react/features/base/util/openURLInBrowser.native.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Opens URL in the browser.
|
||||
*
|
||||
* @param {string} url - The URL to be opened.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function openURLInBrowser(url: string) {
|
||||
Linking.openURL(url).catch(error => {
|
||||
logger.error(`An error occurred while trying to open ${url}`, error);
|
||||
});
|
||||
}
|
||||
11
react/features/base/util/openURLInBrowser.web.js
Normal file
11
react/features/base/util/openURLInBrowser.web.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Opens URL in the browser.
|
||||
*
|
||||
* @param {string} url - The URL to be opened.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function openURLInBrowser(url: string) {
|
||||
window.open(url, '', 'noopener');
|
||||
}
|
||||
@@ -57,6 +57,21 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
.format(TIMESTAMP_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the message text to be redered in the component.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getMessageText() {
|
||||
const { message } = this.props;
|
||||
|
||||
return message.messageType === 'error'
|
||||
? this.props.t('chat.error', {
|
||||
error: message.message
|
||||
})
|
||||
: message.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that is displayed as a notice for private messages.
|
||||
*
|
||||
|
||||
@@ -47,13 +47,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
textWrapperStyle.push(styles.systemTextWrapper);
|
||||
}
|
||||
|
||||
const messageText = message.messageType === 'error'
|
||||
? this.props.t('chat.error', {
|
||||
error: message.error,
|
||||
originalText: message.message
|
||||
})
|
||||
: message.message;
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
{ this._renderAvatar() }
|
||||
@@ -65,7 +58,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
&& this._renderDisplayName()
|
||||
}
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{ replaceNonUnicodeEmojis(messageText) }
|
||||
{ replaceNonUnicodeEmojis(this._getMessageText()) }
|
||||
</Linkify>
|
||||
{
|
||||
message.privateMessage
|
||||
@@ -87,6 +80,8 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
_getMessageText: () => string;
|
||||
|
||||
_getPrivateNoticeMessage: () => string;
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,17 +24,10 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const messageToDisplay = message.messageType === 'error'
|
||||
? this.props.t('chat.error', {
|
||||
error: message.error,
|
||||
originalText: message.message
|
||||
})
|
||||
: message.message;
|
||||
|
||||
const processedMessage = [];
|
||||
|
||||
// content is an array of text and emoji components
|
||||
const content = toArray(messageToDisplay, { className: 'smiley' });
|
||||
const content = toArray(this._getMessageText(), { className: 'smiley' });
|
||||
|
||||
content.forEach(i => {
|
||||
if (typeof i === 'string') {
|
||||
@@ -67,6 +60,8 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
|
||||
_getFormattedTimestamp: () => string;
|
||||
|
||||
_getMessageText: () => string;
|
||||
|
||||
_getPrivateNoticeMessage: () => string;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
getCurrentConference
|
||||
} from '../base/conference';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
@@ -139,10 +142,10 @@ StateListenerRegistry.register(
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addChatMsgListener(conference, { dispatch, getState }) {
|
||||
function _addChatMsgListener(conference, store) {
|
||||
if ((typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly)
|
||||
|| (typeof APP !== 'undefined' && !isButtonEnabled('chat'))
|
||||
|| getState()['features/base/config'].iAmRecorder) {
|
||||
|| store.getState()['features/base/config'].iAmRecorder) {
|
||||
// We don't register anything on web if we're in filmStripOnly mode, or
|
||||
// the chat button is not enabled in interfaceConfig.
|
||||
// or we are in iAmRecorder mode
|
||||
@@ -152,10 +155,7 @@ function _addChatMsgListener(conference, { dispatch, getState }) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
(id, message, timestamp, nick) => {
|
||||
_handleReceivedMessage({
|
||||
dispatch,
|
||||
getState
|
||||
}, {
|
||||
_handleReceivedMessage(store, {
|
||||
id,
|
||||
message,
|
||||
nick,
|
||||
@@ -168,10 +168,7 @@ function _addChatMsgListener(conference, { dispatch, getState }) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,
|
||||
(id, message, timestamp) => {
|
||||
_handleReceivedMessage({
|
||||
dispatch,
|
||||
getState
|
||||
}, {
|
||||
_handleReceivedMessage(store, {
|
||||
id,
|
||||
message,
|
||||
privateMessage: true,
|
||||
@@ -180,6 +177,28 @@ function _addChatMsgListener(conference, { dispatch, getState }) {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_ERROR, (errorType, error) => {
|
||||
errorType === JitsiConferenceErrors.CHAT_ERROR && _handleChatError(store, error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a chat error received from the xmpp server.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {string} error - The error message.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleChatError({ dispatch }, error) {
|
||||
dispatch(addMessage({
|
||||
hasRead: true,
|
||||
messageType: 'error',
|
||||
message: error,
|
||||
privateMessage: false,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
import { DisplayNameLabel } from '../../../display-name';
|
||||
import { SharedDocument } from '../../../etherpad';
|
||||
import {
|
||||
FILMSTRIP_SIZE,
|
||||
Filmstrip,
|
||||
@@ -161,107 +162,13 @@ class Conference extends AbstractConference<Props, *> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_connecting,
|
||||
_filmstripVisible,
|
||||
_largeVideoParticipantId,
|
||||
_reducedUI,
|
||||
_shouldDisplayTileView,
|
||||
_toolboxVisible
|
||||
} = this.props;
|
||||
const showGradient = _toolboxVisible;
|
||||
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
|
||||
|
||||
return (
|
||||
<Container style = { styles.conference }>
|
||||
<StatusBar
|
||||
barStyle = 'light-content'
|
||||
hidden = { true }
|
||||
translucent = { true } />
|
||||
|
||||
<Chat />
|
||||
<AddPeopleDialog />
|
||||
|
||||
{/*
|
||||
* The LargeVideo is the lowermost stacking layer.
|
||||
*/
|
||||
_shouldDisplayTileView
|
||||
? <TileView onClick = { this._onClick } />
|
||||
: <LargeVideo onClick = { this._onClick } />
|
||||
}
|
||||
|
||||
{/*
|
||||
* If there is a ringing call, show the callee's info.
|
||||
*/
|
||||
_reducedUI || <CalleeInfoContainer />
|
||||
}
|
||||
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
*/
|
||||
_connecting
|
||||
&& <TintedView>
|
||||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
|
||||
{ showGradient && <LinearGradient
|
||||
colors = { NAVBAR_GRADIENT_COLORS }
|
||||
end = {{
|
||||
x: 0.0,
|
||||
y: 0.0
|
||||
}}
|
||||
pointerEvents = 'none'
|
||||
start = {{
|
||||
x: 0.0,
|
||||
y: 1.0
|
||||
}}
|
||||
style = { [
|
||||
styles.bottomGradient,
|
||||
applyGradientStretching ? styles.gradientStretchBottom : undefined
|
||||
] } />}
|
||||
|
||||
<Labels />
|
||||
|
||||
<Captions onPress = { this._onClick } />
|
||||
|
||||
{ _shouldDisplayTileView || <DisplayNameLabel participantId = { _largeVideoParticipantId } /> }
|
||||
|
||||
{/*
|
||||
* The Toolbox is in a stacking layer below the Filmstrip.
|
||||
*/}
|
||||
<Toolbox />
|
||||
|
||||
{/*
|
||||
* The Filmstrip is in a stacking layer above the
|
||||
* LargeVideo. The LargeVideo and the Filmstrip form what
|
||||
* the Web/React app calls "videospace". Presumably, the
|
||||
* name and grouping stem from the fact that these two
|
||||
* React Components depict the videos of the conference's
|
||||
* participants.
|
||||
*/
|
||||
_shouldDisplayTileView ? undefined : <Filmstrip />
|
||||
}
|
||||
</View>
|
||||
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.navBarSafeView }>
|
||||
<NavigationBar />
|
||||
{ this.renderNotificationsContainer() }
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{
|
||||
this._renderConferenceNotification()
|
||||
}
|
||||
|
||||
{ this._renderContent() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -320,6 +227,138 @@ class Conference extends AbstractConference<Props, *> {
|
||||
: undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content for the Conference container.
|
||||
*
|
||||
* @private
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderContent() {
|
||||
const {
|
||||
_connecting,
|
||||
_filmstripVisible,
|
||||
_largeVideoParticipantId,
|
||||
_reducedUI,
|
||||
_shouldDisplayTileView,
|
||||
_toolboxVisible
|
||||
} = this.props;
|
||||
const showGradient = _toolboxVisible;
|
||||
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
|
||||
|
||||
if (_reducedUI) {
|
||||
return this._renderContentForReducedUi();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddPeopleDialog />
|
||||
<Chat />
|
||||
<SharedDocument />
|
||||
|
||||
{/*
|
||||
* The LargeVideo is the lowermost stacking layer.
|
||||
*/
|
||||
_shouldDisplayTileView
|
||||
? <TileView onClick = { this._onClick } />
|
||||
: <LargeVideo onClick = { this._onClick } />
|
||||
}
|
||||
|
||||
{/*
|
||||
* If there is a ringing call, show the callee's info.
|
||||
*/
|
||||
<CalleeInfoContainer />
|
||||
}
|
||||
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
*/
|
||||
_connecting
|
||||
&& <TintedView>
|
||||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
|
||||
{ showGradient && <LinearGradient
|
||||
colors = { NAVBAR_GRADIENT_COLORS }
|
||||
end = {{
|
||||
x: 0.0,
|
||||
y: 0.0
|
||||
}}
|
||||
pointerEvents = 'none'
|
||||
start = {{
|
||||
x: 0.0,
|
||||
y: 1.0
|
||||
}}
|
||||
style = { [
|
||||
styles.bottomGradient,
|
||||
applyGradientStretching ? styles.gradientStretchBottom : undefined
|
||||
] } />}
|
||||
|
||||
<Labels />
|
||||
|
||||
<Captions onPress = { this._onClick } />
|
||||
|
||||
{ _shouldDisplayTileView || <DisplayNameLabel participantId = { _largeVideoParticipantId } /> }
|
||||
|
||||
{/*
|
||||
* The Toolbox is in a stacking layer below the Filmstrip.
|
||||
*/}
|
||||
<Toolbox />
|
||||
|
||||
{/*
|
||||
* The Filmstrip is in a stacking layer above the
|
||||
* LargeVideo. The LargeVideo and the Filmstrip form what
|
||||
* the Web/React app calls "videospace". Presumably, the
|
||||
* name and grouping stem from the fact that these two
|
||||
* React Components depict the videos of the conference's
|
||||
* participants.
|
||||
*/
|
||||
_shouldDisplayTileView ? undefined : <Filmstrip />
|
||||
}
|
||||
</View>
|
||||
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.navBarSafeView }>
|
||||
<NavigationBar />
|
||||
{ this._renderNotificationsContainer() }
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{ this._renderConferenceNotification() }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content for the Conference container when in "reduced UI" mode.
|
||||
*
|
||||
* @private
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderContentForReducedUi() {
|
||||
const { _connecting } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<LargeVideo onClick = { this._onClick } />
|
||||
|
||||
{
|
||||
_connecting
|
||||
&& <TintedView>
|
||||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a container for notifications to be displayed by the
|
||||
* base/notifications feature.
|
||||
@@ -327,7 +366,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
* @private
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
renderNotificationsContainer() {
|
||||
_renderNotificationsContainer() {
|
||||
const notificationsStyle = {};
|
||||
|
||||
// In the landscape mode (wide) there's problem with notifications being
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from 'react';
|
||||
|
||||
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||
|
||||
import { obtainConfig } from '../../../base/config';
|
||||
import { connect, disconnect } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect as reactReduxConnect } from '../../../base/redux';
|
||||
@@ -23,7 +22,6 @@ import {
|
||||
} from '../../../toolbox';
|
||||
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions';
|
||||
import logger from '../../logger';
|
||||
|
||||
import Labels from './Labels';
|
||||
import { default as Notice } from './Notice';
|
||||
@@ -123,31 +121,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
|
||||
const { configLocation } = config;
|
||||
|
||||
if (configLocation) {
|
||||
obtainConfig(configLocation, this.props._room)
|
||||
.then(() => {
|
||||
const now = window.performance.now();
|
||||
|
||||
APP.connectionTimes['configuration.fetched'] = now;
|
||||
logger.log('(TIME) configuration fetched:\t', now);
|
||||
|
||||
this._start();
|
||||
})
|
||||
.catch(err => {
|
||||
logger.log(err);
|
||||
|
||||
// Show obtain config error.
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: 'dialog.connectError',
|
||||
titleKey: 'connection.CONNFAIL'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._start();
|
||||
}
|
||||
this._start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
/**
|
||||
* The type of the action which signals document editing has been enabled.
|
||||
*
|
||||
* {
|
||||
* type: ETHERPAD_INITIALIZED
|
||||
* }
|
||||
*/
|
||||
export const ETHERPAD_INITIALIZED = 'ETHERPAD_INITIALIZED';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the action which signals document editing has stopped or started.
|
||||
*
|
||||
@@ -15,8 +5,16 @@ export const ETHERPAD_INITIALIZED = 'ETHERPAD_INITIALIZED';
|
||||
* type: SET_DOCUMENT_EDITING_STATUS
|
||||
* }
|
||||
*/
|
||||
export const SET_DOCUMENT_EDITING_STATUS
|
||||
= 'SET_DOCUMENT_EDITING_STATUS';
|
||||
export const SET_DOCUMENT_EDITING_STATUS = 'SET_DOCUMENT_EDITING_STATUS';
|
||||
|
||||
/**
|
||||
* The type of the action which updates the shared document URL.
|
||||
*
|
||||
* {
|
||||
* type: SET_DOCUMENT_URL
|
||||
* }
|
||||
*/
|
||||
export const SET_DOCUMENT_URL = 'SET_DOCUMENT_URL';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to start or stop editing a shared
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
ETHERPAD_INITIALIZED,
|
||||
SET_DOCUMENT_EDITING_STATUS,
|
||||
SET_DOCUMENT_URL,
|
||||
TOGGLE_DOCUMENT_EDITING
|
||||
} from './actionTypes';
|
||||
|
||||
@@ -24,15 +24,18 @@ export function setDocumentEditingState(editing: boolean) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to set Etherpad as having been initialized.
|
||||
* Dispatches an action to set the shared document URL.
|
||||
*
|
||||
* @param {string} documentUrl - The shared document URL.
|
||||
* @returns {{
|
||||
* type: ETHERPAD_INITIALIZED
|
||||
* type: SET_DOCUMENT_URL,
|
||||
* documentUrl: string
|
||||
* }}
|
||||
*/
|
||||
export function setEtherpadHasInitialzied() {
|
||||
export function setDocumentUrl(documentUrl: ?string) {
|
||||
return {
|
||||
type: ETHERPAD_INITIALIZED
|
||||
type: SET_DOCUMENT_URL,
|
||||
documentUrl
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
81
react/features/etherpad/components/SharedDocumentButton.js
Normal file
81
react/features/etherpad/components/SharedDocumentButton.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconShareDoc } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox';
|
||||
|
||||
import { toggleDocument } from '../actions';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether the shared document is being edited or not.
|
||||
*/
|
||||
_editing: boolean,
|
||||
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the chat screen on mobile.
|
||||
*/
|
||||
class SharedDocumentButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.document';
|
||||
icon = IconShareDoc;
|
||||
label = 'toolbar.documentOpen';
|
||||
toggledLabel = 'toolbar.documentClose';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens / closes the appropriate dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.etherpad',
|
||||
{
|
||||
enable: !this.props._editing
|
||||
}));
|
||||
this.props.dispatch(toggleDocument());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props._editing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component
|
||||
* instance.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Object) {
|
||||
const { documentUrl, editing } = state['features/etherpad'];
|
||||
const { visible = Boolean(documentUrl) } = ownProps;
|
||||
|
||||
return {
|
||||
_editing: editing,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(SharedDocumentButton));
|
||||
2
react/features/etherpad/components/index.native.js
Normal file
2
react/features/etherpad/components/index.native.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SharedDocument } from './native/SharedDocument';
|
||||
export { default as SharedDocumentButton } from './SharedDocumentButton';
|
||||
1
react/features/etherpad/components/index.web.js
Normal file
1
react/features/etherpad/components/index.web.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SharedDocumentButton } from './SharedDocumentButton';
|
||||
178
react/features/etherpad/components/native/SharedDocument.js
Normal file
178
react/features/etherpad/components/native/SharedDocument.js
Normal file
@@ -0,0 +1,178 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { HeaderWithNavigation, LoadingIndicator, SlidingView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import { toggleDocument } from '../../actions';
|
||||
import { getSharedDocumentUrl } from '../../functions';
|
||||
|
||||
import styles, { INDICATOR_COLOR } from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code ShareDocument}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* URL for the shared document.
|
||||
*/
|
||||
_documentUrl: string,
|
||||
|
||||
/**
|
||||
* Color schemed style of the header component.
|
||||
*/
|
||||
_headerStyles: Object,
|
||||
|
||||
/**
|
||||
* True if the chat window should be rendered.
|
||||
*/
|
||||
_isOpen: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React native component that renders the shared document window.
|
||||
*/
|
||||
class SharedDocument extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onClose = this._onClose.bind(this);
|
||||
this._onError = this._onError.bind(this);
|
||||
this._renderLoading = this._renderLoading.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _documentUrl, _isOpen } = this.props;
|
||||
const webViewStyles = this._getWebViewStyles();
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
onHide = { this._onClose }
|
||||
position = 'bottom'
|
||||
show = { _isOpen } >
|
||||
<View style = { styles.webViewWrapper }>
|
||||
<HeaderWithNavigation
|
||||
headerLabelKey = 'documentSharing.title'
|
||||
onPressBack = { this._onClose } />
|
||||
<SafeAreaView style = { webViewStyles }>
|
||||
<WebView
|
||||
onError = { this._onError }
|
||||
renderLoading = { this._renderLoading }
|
||||
source = {{ uri: _documentUrl }}
|
||||
startInLoadingState = { true } />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</SlidingView>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the styles required for the WebView component.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getWebViewStyles() {
|
||||
return {
|
||||
...styles.webView,
|
||||
backgroundColor: this.props._headerStyles.screenHeader.backgroundColor
|
||||
};
|
||||
}
|
||||
|
||||
_onClose: () => boolean
|
||||
|
||||
/**
|
||||
* Closes the window.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onClose() {
|
||||
const { _isOpen, dispatch } = this.props;
|
||||
|
||||
if (_isOpen) {
|
||||
dispatch(toggleDocument());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onError: () => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the error if the page fails to load.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onError() {
|
||||
const { _isOpen, dispatch } = this.props;
|
||||
|
||||
if (_isOpen) {
|
||||
dispatch(toggleDocument());
|
||||
}
|
||||
}
|
||||
|
||||
_renderLoading: () => React$Component<any>;
|
||||
|
||||
/**
|
||||
* Renders the loading indicator.
|
||||
*
|
||||
* @returns {React$Component<any>}
|
||||
*/
|
||||
_renderLoading() {
|
||||
return (
|
||||
<View style = { styles.indicatorWrapper }>
|
||||
<LoadingIndicator
|
||||
color = { INDICATOR_COLOR }
|
||||
size = 'large' />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link SharedDocument} React {@code Component} props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object) {
|
||||
const { editing } = state['features/etherpad'];
|
||||
const documentUrl = getSharedDocumentUrl(state);
|
||||
|
||||
return {
|
||||
_documentUrl: documentUrl,
|
||||
_headerStyles: ColorSchemeRegistry.get(state, 'Header'),
|
||||
_isOpen: editing
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(SharedDocument));
|
||||
24
react/features/etherpad/components/native/styles.js
Normal file
24
react/features/etherpad/components/native/styles.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// @flow
|
||||
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
|
||||
export const INDICATOR_COLOR = ColorPalette.lightGrey;
|
||||
|
||||
export default {
|
||||
|
||||
indicatorWrapper: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.white,
|
||||
height: '100%',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
webView: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
webViewWrapper: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
}
|
||||
};
|
||||
34
react/features/etherpad/functions.js
Normal file
34
react/features/etherpad/functions.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
const ETHERPAD_OPTIONS = {
|
||||
showControls: 'true',
|
||||
showChat: 'false',
|
||||
showLineNumbers: 'true',
|
||||
useMonospaceFont: 'false'
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current sahred document URL.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
||||
* @returns {?string} - Current shared document URL or undefined.
|
||||
*/
|
||||
export function getSharedDocumentUrl(stateful: Function | Object) {
|
||||
const state = toState(stateful);
|
||||
const { documentUrl } = state['features/etherpad'];
|
||||
const { displayName } = state['features/base/settings'];
|
||||
|
||||
if (!documentUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(ETHERPAD_OPTIONS);
|
||||
|
||||
if (displayName) {
|
||||
params.append('userName', displayName);
|
||||
}
|
||||
|
||||
return `${documentUrl}?${params.toString()}`;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
||||
import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
|
||||
import { setDocumentEditingState, setDocumentUrl } from './actions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
const ETHERPAD_COMMAND = 'etherpad';
|
||||
|
||||
/**
|
||||
* Middleware that captures actions related to collaborative document editing
|
||||
* and notifies components not hooked into redux.
|
||||
@@ -15,16 +19,49 @@ declare var APP: Object;
|
||||
* @returns {Function}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
if (typeof APP === 'undefined') {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_DOCUMENT_EDITING:
|
||||
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||
case TOGGLE_DOCUMENT_EDITING: {
|
||||
if (typeof APP === 'undefined') {
|
||||
const { editing } = getState()['features/etherpad'];
|
||||
|
||||
dispatch(setDocumentEditingState(!editing));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up state change listener to perform maintenance tasks when the conference
|
||||
* is left or failed, e.g. clear messages or close the chat modal if it's left
|
||||
* open.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, previousConference) => {
|
||||
if (conference) {
|
||||
conference.addCommandListener(ETHERPAD_COMMAND,
|
||||
({ value }) => {
|
||||
let url;
|
||||
const { etherpad_base: etherpadBase } = getState()['features/base/config'];
|
||||
|
||||
if (etherpadBase) {
|
||||
const u = new URL(value, etherpadBase);
|
||||
|
||||
url = u.toString();
|
||||
}
|
||||
|
||||
dispatch(setDocumentUrl(url));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (previousConference) {
|
||||
dispatch(setDocumentUrl(undefined));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,28 +2,22 @@
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ETHERPAD_INITIALIZED,
|
||||
SET_DOCUMENT_EDITING_STATUS
|
||||
} from './actionTypes';
|
||||
import { SET_DOCUMENT_EDITING_STATUS, SET_DOCUMENT_URL } from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
|
||||
/**
|
||||
* URL for the shared document.
|
||||
*/
|
||||
documentUrl: undefined,
|
||||
|
||||
/**
|
||||
* Whether or not Etherpad is currently open.
|
||||
*
|
||||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
editing: false,
|
||||
|
||||
/**
|
||||
* Whether or not Etherpad is ready to use.
|
||||
*
|
||||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
initialized: false
|
||||
editing: false
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,18 +27,18 @@ ReducerRegistry.register(
|
||||
'features/etherpad',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ETHERPAD_INITIALIZED:
|
||||
return {
|
||||
...state,
|
||||
initialized: true
|
||||
};
|
||||
|
||||
case SET_DOCUMENT_EDITING_STATUS:
|
||||
return {
|
||||
...state,
|
||||
editing: action.editing
|
||||
};
|
||||
|
||||
case SET_DOCUMENT_URL:
|
||||
return {
|
||||
...state,
|
||||
documentUrl: action.documentUrl
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NativeModules } from 'react-native';
|
||||
let GoogleSignin;
|
||||
|
||||
if (NativeModules.RNGoogleSignin) {
|
||||
GoogleSignin = require('react-native-google-signin').GoogleSignin;
|
||||
GoogleSignin = require('@react-native-community/google-signin').GoogleSignin;
|
||||
}
|
||||
|
||||
import {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default {
|
||||
indicatorWrapper: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.white,
|
||||
flex: 1,
|
||||
height: '100%',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
* The URL that is the main landing page for YouTube live streaming and should
|
||||
* have a user's live stream key.
|
||||
*/
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL
|
||||
= 'https://www.youtube.com/live_dashboard';
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL = 'https://www.youtube.com/live_dashboard';
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Linking, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props as AbstractProps
|
||||
@@ -120,7 +121,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
const { helpURL } = this;
|
||||
|
||||
if (typeof helpURL === 'string') {
|
||||
Linking.openURL(helpURL);
|
||||
openURLInBrowser(helpURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Linking,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
@@ -13,6 +12,7 @@ import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
|
||||
|
||||
@@ -153,7 +153,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenYoutubeDashboard() {
|
||||
Linking.openURL(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
openURLInBrowser(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
}
|
||||
|
||||
_onStreamPick: string => Function
|
||||
|
||||
56
react/features/toolbox/components/HelpButton.js
Normal file
56
react/features/toolbox/components/HelpButton.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconHelp } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { openURLInBrowser } from '../../base/util';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The URL to the user documenation.
|
||||
*/
|
||||
_userDocumentationURL: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the user documentation in a new window.
|
||||
*/
|
||||
class HelpButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.help';
|
||||
icon = IconHelp;
|
||||
label = 'toolbar.help';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens a new window with the user documentation.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
sendAnalytics(createToolbarEvent('help.pressed'));
|
||||
openURLInBrowser(this.props._userDocumentationURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { userDocumentationURL } = state['features/base/config'];
|
||||
const visible = typeof userDocumentationURL === 'string';
|
||||
|
||||
return {
|
||||
_userDocumentationURL: userDocumentationURL,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(HelpButton));
|
||||
@@ -8,6 +8,7 @@ import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
|
||||
import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { SharedDocumentButton } from '../../../etherpad';
|
||||
import { InfoDialogButton, InviteButton } from '../../../invite';
|
||||
import { AudioRouteButton } from '../../../mobile/audio-mode';
|
||||
import { LiveStreamButton, RecordButton } from '../../../recording';
|
||||
@@ -16,6 +17,7 @@ import { ClosedCaptionButton } from '../../../subtitles';
|
||||
import { TileViewButton } from '../../../video-layout';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import HelpButton from '../HelpButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ToggleCameraButton from './ToggleCameraButton';
|
||||
|
||||
@@ -108,6 +110,8 @@ class OverflowMenu extends Component<Props> {
|
||||
&& <InfoDialogButton { ...buttonProps } />
|
||||
}
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
<SharedDocumentButton { ...buttonProps } />
|
||||
<HelpButton { ...buttonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
IconRaisedHand,
|
||||
IconRec,
|
||||
IconShareDesktop,
|
||||
IconShareDoc,
|
||||
IconShareVideo
|
||||
} from '../../../base/icons';
|
||||
import {
|
||||
@@ -34,7 +33,7 @@ import { OverflowMenuItem } from '../../../base/toolbox';
|
||||
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
|
||||
import { VideoBlurButton } from '../../../blur';
|
||||
import { ChatCounter, toggleChat } from '../../../chat';
|
||||
import { toggleDocument } from '../../../etherpad';
|
||||
import { SharedDocumentButton } from '../../../etherpad';
|
||||
import { openFeedbackDialog } from '../../../feedback';
|
||||
import {
|
||||
beginAddPeople,
|
||||
@@ -72,6 +71,7 @@ import {
|
||||
import AudioMuteButton from '../AudioMuteButton';
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import HangupButton from '../HangupButton';
|
||||
import HelpButton from '../HelpButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import OverflowMenuProfileItem from './OverflowMenuProfileItem';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
@@ -111,16 +111,6 @@ type Props = {
|
||||
*/
|
||||
_dialog: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the local participant is currently editing a document.
|
||||
*/
|
||||
_editingDocument: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not collaborative document editing is enabled.
|
||||
*/
|
||||
_etherpadInitialized: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not call feedback can be sent.
|
||||
*/
|
||||
@@ -229,38 +219,22 @@ class Toolbox extends Component<Props, State> {
|
||||
this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this);
|
||||
|
||||
this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this);
|
||||
this._onShortcutToggleFullScreen
|
||||
= this._onShortcutToggleFullScreen.bind(this);
|
||||
this._onShortcutToggleRaiseHand
|
||||
= this._onShortcutToggleRaiseHand.bind(this);
|
||||
this._onShortcutToggleScreenshare
|
||||
= this._onShortcutToggleScreenshare.bind(this);
|
||||
this._onShortcutToggleVideoQuality
|
||||
= this._onShortcutToggleVideoQuality.bind(this);
|
||||
this._onToolbarOpenFeedback
|
||||
= this._onToolbarOpenFeedback.bind(this);
|
||||
this._onShortcutToggleFullScreen = this._onShortcutToggleFullScreen.bind(this);
|
||||
this._onShortcutToggleRaiseHand = this._onShortcutToggleRaiseHand.bind(this);
|
||||
this._onShortcutToggleScreenshare = this._onShortcutToggleScreenshare.bind(this);
|
||||
this._onShortcutToggleVideoQuality = this._onShortcutToggleVideoQuality.bind(this);
|
||||
this._onToolbarOpenFeedback = this._onToolbarOpenFeedback.bind(this);
|
||||
this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
|
||||
this._onToolbarOpenKeyboardShortcuts
|
||||
= this._onToolbarOpenKeyboardShortcuts.bind(this);
|
||||
this._onToolbarOpenSpeakerStats
|
||||
= this._onToolbarOpenSpeakerStats.bind(this);
|
||||
this._onToolbarOpenVideoQuality
|
||||
= this._onToolbarOpenVideoQuality.bind(this);
|
||||
this._onToolbarOpenKeyboardShortcuts = this._onToolbarOpenKeyboardShortcuts.bind(this);
|
||||
this._onToolbarOpenSpeakerStats = this._onToolbarOpenSpeakerStats.bind(this);
|
||||
this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
|
||||
this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
|
||||
this._onToolbarToggleEtherpad
|
||||
= this._onToolbarToggleEtherpad.bind(this);
|
||||
this._onToolbarToggleFullScreen
|
||||
= this._onToolbarToggleFullScreen.bind(this);
|
||||
this._onToolbarToggleProfile
|
||||
= this._onToolbarToggleProfile.bind(this);
|
||||
this._onToolbarToggleRaiseHand
|
||||
= this._onToolbarToggleRaiseHand.bind(this);
|
||||
this._onToolbarToggleScreenshare
|
||||
= this._onToolbarToggleScreenshare.bind(this);
|
||||
this._onToolbarToggleSharedVideo
|
||||
= this._onToolbarToggleSharedVideo.bind(this);
|
||||
this._onToolbarOpenLocalRecordingInfoDialog
|
||||
= this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
|
||||
this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
|
||||
this._onToolbarToggleProfile = this._onToolbarToggleProfile.bind(this);
|
||||
this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
|
||||
this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
|
||||
this._onToolbarToggleSharedVideo = this._onToolbarToggleSharedVideo.bind(this);
|
||||
this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
|
||||
|
||||
this.state = {
|
||||
windowWidth: window.innerWidth
|
||||
@@ -424,16 +398,6 @@ class Toolbox extends Component<Props, State> {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to show or hide document editing.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleEtherpad() {
|
||||
this.props.dispatch(toggleDocument());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to toggle screensharing.
|
||||
*
|
||||
@@ -749,25 +713,6 @@ class Toolbox extends Component<Props, State> {
|
||||
this._doToggleChat();
|
||||
}
|
||||
|
||||
_onToolbarToggleEtherpad: () => void;
|
||||
|
||||
/**
|
||||
* Creates an analytics toolbar event and dispatches an action for toggling
|
||||
* the display of document editing.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleEtherpad() {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.etherpad',
|
||||
{
|
||||
enable: !this.props._editingDocument
|
||||
}));
|
||||
|
||||
this._doToggleEtherpad();
|
||||
}
|
||||
|
||||
_onToolbarToggleFullScreen: () => void;
|
||||
|
||||
/**
|
||||
@@ -960,8 +905,6 @@ class Toolbox extends Component<Props, State> {
|
||||
*/
|
||||
_renderOverflowMenuContent() {
|
||||
const {
|
||||
_editingDocument,
|
||||
_etherpadInitialized,
|
||||
_feedbackConfigured,
|
||||
_fullScreen,
|
||||
_screensharing,
|
||||
@@ -980,16 +923,11 @@ class Toolbox extends Component<Props, State> {
|
||||
onClick = { this._onToolbarOpenVideoQuality } />,
|
||||
this._shouldShowButton('fullscreen')
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.fullScreen') }
|
||||
icon = { _fullScreen
|
||||
? IconExitFullScreen
|
||||
: IconFullScreen }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.fullScreen') }
|
||||
icon = { _fullScreen ? IconExitFullScreen : IconFullScreen }
|
||||
key = 'fullscreen'
|
||||
onClick = { this._onToolbarToggleFullScreen }
|
||||
text = { _fullScreen
|
||||
? t('toolbar.exitFullScreen')
|
||||
: t('toolbar.enterFullScreen') } />,
|
||||
text = { _fullScreen ? t('toolbar.exitFullScreen') : t('toolbar.enterFullScreen') } />,
|
||||
<LiveStreamButton
|
||||
key = 'livestreaming'
|
||||
showLabel = { true } />,
|
||||
@@ -998,25 +936,15 @@ class Toolbox extends Component<Props, State> {
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('sharedvideo')
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.sharedvideo') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.sharedvideo') }
|
||||
icon = { IconShareVideo }
|
||||
key = 'sharedvideo'
|
||||
onClick = { this._onToolbarToggleSharedVideo }
|
||||
text = { _sharingVideo
|
||||
? t('toolbar.stopSharedVideo')
|
||||
: t('toolbar.sharedvideo') } />,
|
||||
text = { _sharingVideo ? t('toolbar.stopSharedVideo') : t('toolbar.sharedvideo') } />,
|
||||
this._shouldShowButton('etherpad')
|
||||
&& _etherpadInitialized
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.document') }
|
||||
icon = { IconShareDoc }
|
||||
&& <SharedDocumentButton
|
||||
key = 'etherpad'
|
||||
onClick = { this._onToolbarToggleEtherpad }
|
||||
text = { _editingDocument
|
||||
? t('toolbar.documentClose')
|
||||
: t('toolbar.documentOpen') } />,
|
||||
showLabel = { true } />,
|
||||
<VideoBlurButton
|
||||
key = 'videobackgroundblur'
|
||||
showLabel = { true }
|
||||
@@ -1027,8 +955,7 @@ class Toolbox extends Component<Props, State> {
|
||||
visible = { this._shouldShowButton('settings') } />,
|
||||
this._shouldShowButton('stats')
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.speakerStats') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.speakerStats') }
|
||||
icon = { IconPresentation }
|
||||
key = 'stats'
|
||||
onClick = { this._onToolbarOpenSpeakerStats }
|
||||
@@ -1036,20 +963,22 @@ class Toolbox extends Component<Props, State> {
|
||||
this._shouldShowButton('feedback')
|
||||
&& _feedbackConfigured
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.feedback') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.feedback') }
|
||||
icon = { IconFeedback }
|
||||
key = 'feedback'
|
||||
onClick = { this._onToolbarOpenFeedback }
|
||||
text = { t('toolbar.feedback') } />,
|
||||
this._shouldShowButton('shortcuts')
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.shortcuts') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.shortcuts') }
|
||||
icon = { IconOpenInNew }
|
||||
key = 'shortcuts'
|
||||
onClick = { this._onToolbarOpenKeyboardShortcuts }
|
||||
text = { t('toolbar.shortcuts') } />
|
||||
text = { t('toolbar.shortcuts') } />,
|
||||
this._shouldShowButton('help')
|
||||
&& <HelpButton
|
||||
key = 'help'
|
||||
showLabel = { true } />
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1108,8 +1037,7 @@ class Toolbox extends Component<Props, State> {
|
||||
case 'invite':
|
||||
return (
|
||||
<OverflowMenuItem
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.invite') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.invite') }
|
||||
icon = { IconInvite }
|
||||
key = 'invite'
|
||||
onClick = { this._onToolbarOpenInvite }
|
||||
@@ -1120,13 +1048,10 @@ class Toolbox extends Component<Props, State> {
|
||||
case 'localrecording':
|
||||
return (
|
||||
<OverflowMenuItem
|
||||
accessibilityLabel
|
||||
= { t('toolbar.accessibilityLabel.localRecording') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.localRecording') }
|
||||
icon = { IconRec }
|
||||
key = 'localrecording'
|
||||
onClick = {
|
||||
this._onToolbarOpenLocalRecordingInfoDialog
|
||||
}
|
||||
onClick = { this._onToolbarOpenLocalRecordingInfoDialog }
|
||||
text = { t('localRecording.dialogTitle') } />
|
||||
);
|
||||
default:
|
||||
@@ -1149,8 +1074,7 @@ class Toolbox extends Component<Props, State> {
|
||||
t
|
||||
} = this.props;
|
||||
const overflowMenuContent = this._renderOverflowMenuContent();
|
||||
const overflowHasItems = Boolean(overflowMenuContent.filter(
|
||||
child => child).length);
|
||||
const overflowHasItems = Boolean(overflowMenuContent.filter(child => child).length);
|
||||
const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu';
|
||||
const buttonsLeft = [];
|
||||
const buttonsRight = [];
|
||||
@@ -1233,10 +1157,7 @@ class Toolbox extends Component<Props, State> {
|
||||
&& this._renderDesktopSharingButton() }
|
||||
{ buttonsLeft.indexOf('raisehand') !== -1
|
||||
&& <ToolbarButton
|
||||
accessibilityLabel =
|
||||
{
|
||||
t('toolbar.accessibilityLabel.raiseHand')
|
||||
}
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
|
||||
icon = { IconRaisedHand }
|
||||
onClick = { this._onToolbarToggleRaiseHand }
|
||||
toggled = { _raisedHand }
|
||||
@@ -1244,8 +1165,7 @@ class Toolbox extends Component<Props, State> {
|
||||
{ buttonsLeft.indexOf('chat') !== -1
|
||||
&& <div className = 'toolbar-button-with-badge'>
|
||||
<ToolbarButton
|
||||
accessibilityLabel =
|
||||
{ t('toolbar.accessibilityLabel.chat') }
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.chat') }
|
||||
icon = { IconChat }
|
||||
onClick = { this._onToolbarToggleChat }
|
||||
toggled = { _chatOpen }
|
||||
@@ -1365,8 +1285,6 @@ function _mapStateToProps(state) {
|
||||
_desktopSharingEnabled: desktopSharingEnabled,
|
||||
_desktopSharingDisabledTooltipKey: desktopSharingDisabledTooltipKey,
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
_editingDocument: Boolean(state['features/etherpad'].editing),
|
||||
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
|
||||
_feedbackConfigured: Boolean(callStatsID),
|
||||
_hideInviteButton:
|
||||
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
|
||||
|
||||
@@ -53,6 +53,15 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
*/
|
||||
this._additionalContentRef = null;
|
||||
|
||||
/**
|
||||
* The HTML Element used as the container for additional toolbar content. Used
|
||||
* for directly appending the additional content template to the dom.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLTemplateElement|null}
|
||||
*/
|
||||
this._additionalToolbarContentRef = null;
|
||||
|
||||
/**
|
||||
* The template to use as the main content for the welcome page. If
|
||||
* not found then only the welcome page head will display.
|
||||
@@ -63,11 +72,24 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalContentTemplate = document.getElementById(
|
||||
'welcome-page-additional-content-template');
|
||||
|
||||
/**
|
||||
* The template to use as the additional content for the welcome page header toolbar.
|
||||
* If not found then only the settings icon will be displayed.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLTemplateElement|null}
|
||||
*/
|
||||
this._additionalToolbarContentTemplate = document.getElementById(
|
||||
'settings-toolbar-additional-content-template'
|
||||
);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onFormSubmit = this._onFormSubmit.bind(this);
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._setAdditionalContentRef
|
||||
= this._setAdditionalContentRef.bind(this);
|
||||
this._setAdditionalToolbarContentRef
|
||||
= this._setAdditionalToolbarContentRef.bind(this);
|
||||
this._onTabSelected = this._onTabSelected.bind(this);
|
||||
}
|
||||
|
||||
@@ -90,6 +112,12 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalContentRef.appendChild(
|
||||
this._additionalContentTemplate.content.cloneNode(true));
|
||||
}
|
||||
|
||||
if (this._shouldShowAdditionalToolbarContent()) {
|
||||
this._additionalToolbarContentRef.appendChild(
|
||||
this._additionalToolbarContentTemplate.content.cloneNode(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +142,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
const { t } = this.props;
|
||||
const { APP_NAME } = interfaceConfig;
|
||||
const showAdditionalContent = this._shouldShowAdditionalContent();
|
||||
const showAdditionalToolbarContent = this._shouldShowAdditionalToolbarContent();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -127,6 +156,12 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
<div className = 'welcome-page-settings'>
|
||||
<SettingsButton
|
||||
defaultTab = { SETTINGS_TABS.CALENDAR } />
|
||||
{ showAdditionalToolbarContent
|
||||
? <div
|
||||
className = 'settings-toolbar-content'
|
||||
ref = { this._setAdditionalToolbarContentRef } />
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<div className = 'header-image' />
|
||||
<div className = 'header-text'>
|
||||
@@ -263,6 +298,19 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalContentRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLDivElement used to hold the
|
||||
* toolbar additional content.
|
||||
*
|
||||
* @param {HTMLDivElement} el - The HTMLElement for the div that is the root
|
||||
* of the additional toolbar content.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setAdditionalToolbarContentRef(el) {
|
||||
this._additionalToolbarContentRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not additional content should be displayed below
|
||||
* the welcome page's header for entering a room name.
|
||||
@@ -276,6 +324,20 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
&& this._additionalContentTemplate.content
|
||||
&& this._additionalContentTemplate.innerHTML.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not additional content should be displayed inside
|
||||
* the header toolbar.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_shouldShowAdditionalToolbarContent() {
|
||||
return interfaceConfig.DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT
|
||||
&& this._additionalToolbarContentTemplate
|
||||
&& this._additionalToolbarContentTemplate.content
|
||||
&& this._additionalToolbarContentTemplate.innerHTML.trim();
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(WelcomePage));
|
||||
|
||||
1
static/settingsToolbarAdditionalContent.html
Normal file
1
static/settingsToolbarAdditionalContent.html
Normal file
@@ -0,0 +1 @@
|
||||
<template id="settings-toolbar-additional-content-template"></template>
|
||||
@@ -310,5 +310,7 @@ function devServerProxyBypass({ path }) {
|
||||
return path;
|
||||
}
|
||||
|
||||
/* eslint-enable array-callback-return, indent */
|
||||
if (path.startsWith('/libs/')) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user