Compare commits
55 Commits
1939
...
analytics_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b597abda | ||
|
|
e68be6fa84 | ||
|
|
44d75b5253 | ||
|
|
440865f3ca | ||
|
|
e91c62142d | ||
|
|
1a892a689e | ||
|
|
04851b4baa | ||
|
|
beec60be47 | ||
|
|
36d40cdec9 | ||
|
|
23fea490aa | ||
|
|
1d60300016 | ||
|
|
5be3504fad | ||
|
|
bc9ef4421a | ||
|
|
4ffe668dd2 | ||
|
|
6536f82559 | ||
|
|
4464a11314 | ||
|
|
2855ea1500 | ||
|
|
258dc594dd | ||
|
|
a1476c68f1 | ||
|
|
3cc4d44376 | ||
|
|
bf163d221c | ||
|
|
7900b9c294 | ||
|
|
6a17d50423 | ||
|
|
9e7f8d0e16 | ||
|
|
3a99ef512e | ||
|
|
a14886031f | ||
|
|
ec881e0fd0 | ||
|
|
80989147ad | ||
|
|
3c31a60b32 | ||
|
|
db59b45076 | ||
|
|
0f0ff6788c | ||
|
|
47c07c2e76 | ||
|
|
896dcde2b2 | ||
|
|
a88409bbfa | ||
|
|
b8189a31ad | ||
|
|
e90d09a6d9 | ||
|
|
9fb49cb59b | ||
|
|
77ab05823d | ||
|
|
28ff188f96 | ||
|
|
bac191f96c | ||
|
|
e1a9487896 | ||
|
|
9e728e4b25 | ||
|
|
06d2c9fb7b | ||
|
|
63c862d925 | ||
|
|
a96a70869d | ||
|
|
ede5be119f | ||
|
|
b7c57d306a | ||
|
|
816eef1702 | ||
|
|
92eeba5392 | ||
|
|
2f3706bd37 | ||
|
|
e6f6884c36 | ||
|
|
ab5c2e9ded | ||
|
|
4f72225372 | ||
|
|
3af0976a43 | ||
|
|
96b1f0ca74 |
@@ -3,13 +3,9 @@ We would love to have your help. Before you start working however, please read
|
||||
and follow this short guide.
|
||||
|
||||
# Reporting Issues
|
||||
Before you open an issue on GitHub, please discuss it on one of our
|
||||
[mailing lists](https://jitsi.org/Development/MailingLists) and wait for
|
||||
confirmation from one of the committers. Once you have that confirmation,
|
||||
please proceed to reporting the issue on GitHub, while providing as much
|
||||
information as possible. Mention the version of Jitsi Meet, Jicofo and JVB
|
||||
you are using, and explain (as detailed as you can) how the problem can
|
||||
be reproduced.
|
||||
Provide as much information as possible. Mention the version of Jitsi Meet,
|
||||
Jicofo and JVB you are using, and explain (as detailed as you can) how the
|
||||
problem can be reproduced.
|
||||
|
||||
# Code contributions
|
||||
Found a bug and know how to fix it? Great! Please read on.
|
||||
|
||||
@@ -19,6 +19,9 @@ You can download Debian/Ubuntu binaries:
|
||||
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianTestingRepository))
|
||||
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
|
||||
|
||||
You can download source archives (produced by ```make source-package```):
|
||||
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
|
||||
|
||||
You can get our mobile versions from here:
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
rootProject.name = 'jitsi-meet-react'
|
||||
rootProject.name = 'jitsi-meet'
|
||||
|
||||
include ':app'
|
||||
include ':react-native-background-timer'
|
||||
|
||||
@@ -19,8 +19,7 @@ import analytics from './modules/analytics/analytics';
|
||||
|
||||
import EventEmitter from "events";
|
||||
|
||||
import { showDesktopSharingButton } from './react/features/toolbox';
|
||||
|
||||
import { getLocationContextRoot } from './react/features/app';
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
@@ -50,6 +49,7 @@ import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
suspendDetected
|
||||
} from './react/features/overlay';
|
||||
import { showDesktopSharingButton } from './react/features/toolbox';
|
||||
|
||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
@@ -273,9 +273,11 @@ function muteLocalVideo(muted) {
|
||||
function maybeRedirectToWelcomePage(options) {
|
||||
// if close page is enabled redirect to it, without further action
|
||||
if (config.enableClosePage) {
|
||||
const { isGuest } = APP.store.getState()['features/jwt'];
|
||||
|
||||
// save whether current user is guest or not, before navigating
|
||||
// to close page
|
||||
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
assignWindowLocationPathname('static/'
|
||||
+ (options.feedbackSubmitted ? "close.html" : "close2.html"));
|
||||
return;
|
||||
@@ -309,18 +311,11 @@ function assignWindowLocationPathname(pathname) {
|
||||
const windowLocation = window.location;
|
||||
|
||||
if (!pathname.startsWith('/')) {
|
||||
// XXX To support a deployment in a sub-directory, assume that the room
|
||||
// (name) is the last non-directory component of the path (name).
|
||||
let contextRoot = windowLocation.pathname;
|
||||
|
||||
contextRoot
|
||||
= contextRoot.substring(0, contextRoot.lastIndexOf('/') + 1);
|
||||
|
||||
// A pathname equal to ./ specifies the current directory. It will be
|
||||
// fine but pointless to include it because contextRoot is the current
|
||||
// directory.
|
||||
pathname.startsWith('./') && (pathname = pathname.substring(2));
|
||||
pathname = contextRoot + pathname;
|
||||
pathname = getLocationContextRoot(windowLocation) + pathname;
|
||||
}
|
||||
|
||||
windowLocation.pathname = pathname;
|
||||
@@ -1300,6 +1295,12 @@ export default {
|
||||
APP.UI.onSharedVideoStop(id);
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
|
||||
let user = room.getParticipantById(id);
|
||||
if (user) {
|
||||
APP.UI.updateUserStatus(user, status);
|
||||
}
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
|
||||
if (this.isLocalId(id)) {
|
||||
@@ -1656,13 +1657,6 @@ export default {
|
||||
});
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.SIP_DIAL, (sipNumber) => {
|
||||
room.dial(sipNumber)
|
||||
.catch((err) => {
|
||||
logger.error("Error dialing out", err);
|
||||
});
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.RESOLUTION_CHANGED,
|
||||
(id, oldResolution, newResolution, delay) => {
|
||||
var logObject = {
|
||||
|
||||
@@ -41,7 +41,7 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
// extension is required.
|
||||
desktopSharingFirefoxExtId: null,
|
||||
// Whether desktop sharing should be disabled on Firefox.
|
||||
desktopSharingFirefoxDisabled: true,
|
||||
desktopSharingFirefoxDisabled: false,
|
||||
// The maximum version of Firefox which requires a jidesha extension.
|
||||
// Example: if set to 41, we will require the extension for Firefox versions
|
||||
// up to and including 41. On Firefox 42 and higher, we will run without the
|
||||
@@ -76,6 +76,8 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
'During that time service will not be available. ' +
|
||||
'Apologise for inconvenience.',*/
|
||||
disableThirdPartyRequests: false,
|
||||
// The minumum value a video's height (or width, whichever is smaller) needs
|
||||
// to be in order to be considered high-definition.
|
||||
minHDHeight: 540,
|
||||
// If true - all users without token will be considered guests and all users
|
||||
// with token will be considered non-guests. Only guests will be allowed to
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* global APP, JitsiMeetJS, config */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
|
||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
@@ -61,22 +61,27 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
|
||||
let connectionConfig = Object.assign({}, config);
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { issuer, jwt } = APP.store.getState()['features/jwt'];
|
||||
|
||||
connectionConfig.bosh += '?room=' + roomName;
|
||||
|
||||
let connection
|
||||
= new JitsiMeetJS.JitsiConnection(null, config.token, connectionConfig);
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
null,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
connectionConfig);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
|
||||
);
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
|
||||
);
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
|
||||
function connectionFailedHandler(error, errMsg) {
|
||||
APP.store.dispatch(connectionFailed(connection, error, errMsg));
|
||||
@@ -91,12 +96,10 @@ function connect(id, password, roomName) {
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished
|
||||
);
|
||||
handleConnectionEstablished);
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed
|
||||
);
|
||||
handleConnectionFailed);
|
||||
}
|
||||
|
||||
function handleConnectionEstablished() {
|
||||
@@ -129,7 +132,6 @@ function connect(id, password, roomName) {
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function openConnection({id, password, retry, roomName}) {
|
||||
|
||||
let usernameOverride
|
||||
= jitsiLocalStorage.getItem("xmpp_username_override");
|
||||
let passwordOverride
|
||||
@@ -138,25 +140,20 @@ export function openConnection({id, password, retry, roomName}) {
|
||||
if (usernameOverride && usernameOverride.length > 0) {
|
||||
id = usernameOverride;
|
||||
}
|
||||
|
||||
if (passwordOverride && passwordOverride.length > 0) {
|
||||
password = passwordOverride;
|
||||
}
|
||||
|
||||
return connect(id, password, roomName).catch(function (err) {
|
||||
if (!retry) {
|
||||
throw err;
|
||||
}
|
||||
return connect(id, password, roomName).catch(err => {
|
||||
if (retry) {
|
||||
const { issuer, jwt } = APP.store.getState()['features/jwt'];
|
||||
|
||||
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
|
||||
// do not retry if token is not valid
|
||||
if (config.token) {
|
||||
throw err;
|
||||
} else {
|
||||
if (err === ConnectionErrors.PASSWORD_REQUIRED
|
||||
&& (!jwt || issuer === 'anonymous')) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
/* global config,
|
||||
createConnectionExternally,
|
||||
getConfigParamsFromUrl,
|
||||
getRoomName */
|
||||
/* global config, createConnectionExternally */
|
||||
|
||||
import getRoomName from '../react/features/base/config/getRoomName';
|
||||
import parseURLParams from '../react/features/base/config/parseURLParams';
|
||||
|
||||
/**
|
||||
* Implements external connect using createConnectionExternally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and token from
|
||||
* the URL and executes createConnectionExternally.
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
|
||||
* Token (JWT) from the URL and executes createConnectionExternally.
|
||||
*
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet you should use this
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
|
||||
* file as reference only because the implementation is Jitsi Meet-specific.
|
||||
*
|
||||
* NOTE: For optimal results this file should be included right after
|
||||
* external_connect.js.
|
||||
*/
|
||||
|
||||
const hashParams = getConfigParamsFromUrl('hash', true);
|
||||
const searchParams = getConfigParamsFromUrl('search', true);
|
||||
if (typeof createConnectionExternally === 'function') {
|
||||
// URL params have higher proirity than config params.
|
||||
let url
|
||||
= parseURLParams(window.location, true, 'hash')[
|
||||
'config.externalConnectUrl']
|
||||
|| config.externalConnectUrl;
|
||||
let roomName;
|
||||
|
||||
// URL params have higher proirity than config params.
|
||||
let url
|
||||
= hashParams.hasOwnProperty('config.externalConnectUrl')
|
||||
? hashParams['config.externalConnectUrl']
|
||||
: config.externalConnectUrl;
|
||||
|
||||
if (url && window.createConnectionExternally) {
|
||||
const roomName = getRoomName();
|
||||
|
||||
if (roomName) {
|
||||
if (url && (roomName = getRoomName())) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
const token
|
||||
= hashParams['config.token'] || config.token || searchParams.jwt;
|
||||
const token = parseURLParams(window.location, true, 'search').jwt;
|
||||
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
|
||||
64
css/_dial-out.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* The dialog content element.
|
||||
*/
|
||||
.dial-out-content {
|
||||
margin-top: 5px;
|
||||
|
||||
/**
|
||||
* The style of the flag icon.
|
||||
*/
|
||||
.dial-out-flag-icon {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the dial code element.
|
||||
*/
|
||||
.dial-out-code {
|
||||
padding-left: 25px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dial-out dialog error element.
|
||||
*/
|
||||
.dial-out-error {
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of the dial input element.
|
||||
*/
|
||||
.dial-out-input {
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styling the default dropdown inside the dial-out-content.
|
||||
*/
|
||||
.dropdown {
|
||||
left: $formPadding;
|
||||
position: absolute !important;
|
||||
width: 65px
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styling the default form-control inside the dial-out-content.
|
||||
*/
|
||||
.form-control {
|
||||
padding-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-trigger-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
35
css/_flag-icon.scss
Executable file
@@ -0,0 +1,35 @@
|
||||
.flag-icon-background {
|
||||
background-size: contain;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.flag-icon {
|
||||
background-size: contain;
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 1.33333333em;
|
||||
line-height: 1em;
|
||||
}
|
||||
.flag-icon:before {
|
||||
content: "\00a0";
|
||||
}
|
||||
.flag-icon-au {
|
||||
background-image: url(../images/countries/au.svg);
|
||||
}
|
||||
.flag-icon-ca {
|
||||
background-image: url(../images/countries/ca.svg);
|
||||
}
|
||||
.flag-icon-de {
|
||||
background-image: url(../images/countries/de.svg);
|
||||
}
|
||||
.flag-icon-gb {
|
||||
background-image: url(../images/countries/gb.svg);
|
||||
}
|
||||
.flag-icon-fr {
|
||||
background-image: url(../images/countries/fr.svg);
|
||||
}
|
||||
.flag-icon-us {
|
||||
background-image: url(../images/countries/us.svg);
|
||||
}
|
||||
@@ -52,9 +52,6 @@
|
||||
.icon-share-doc:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-telephone:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-kick:before {
|
||||
content: "\e904";
|
||||
}
|
||||
@@ -148,3 +145,6 @@
|
||||
.icon-visibility-off:before {
|
||||
content: "\e924";
|
||||
}
|
||||
.icon-telephone:before {
|
||||
content: "\e0cd";
|
||||
}
|
||||
|
||||
@@ -41,21 +41,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__text,
|
||||
&__slider {
|
||||
&__text {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&__slider {
|
||||
width: 50px;
|
||||
&__contents {
|
||||
display: flex;
|
||||
|
||||
/**
|
||||
* Positioning styles on the slider and its container are used to make
|
||||
* the container fit the popup width, by removing the slider from the
|
||||
* page flow, and then making the slider fit the container.
|
||||
*/
|
||||
.popupmenu__slider_container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.popupmenu__slider {
|
||||
bottom: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ $inputControlEmColor: #f29424;
|
||||
//buttons
|
||||
$linkFontColor: #489afe;
|
||||
$linkHoverFontColor: #287ade;
|
||||
$formPadding: 16px;
|
||||
|
||||
/**
|
||||
* Unsupported browser
|
||||
|
||||
@@ -496,7 +496,6 @@
|
||||
}
|
||||
|
||||
.audio-only-label {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
height: auto;
|
||||
justify-content: center;
|
||||
@@ -507,6 +506,7 @@
|
||||
.video-state-indicator {
|
||||
background: $videoStateIndicatorBackground;
|
||||
color: $videoStateIndicatorColor;
|
||||
cursor: default;
|
||||
font-size: 13px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
@@ -543,4 +543,58 @@
|
||||
|
||||
.moveToCorner + .moveToCorner {
|
||||
right: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-state-indicator-menu {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 20px;
|
||||
|
||||
.video-state-indicator-menu-options {
|
||||
background: $popoverBg;
|
||||
border-radius: 3px;
|
||||
color: $popoverFontColor;
|
||||
margin-top: 20px;
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
padding-right: 30px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
&.active {
|
||||
background: $toolbarToggleBackground;
|
||||
}
|
||||
&:hover:not(.active) {
|
||||
background: $popupMenuSelectedItemBackground;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-state-indicator-menu-options::after {
|
||||
content: " ";
|
||||
border-color: transparent transparent $popoverBg transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-state-indicator:hover,
|
||||
.video-state-indicator *:hover {
|
||||
.video-state-indicator-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.form-control {
|
||||
padding: 16px 0;
|
||||
padding: $formPadding 0;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
|
||||
@import 'font';
|
||||
@import 'font-awesome';
|
||||
|
||||
/* Fonts END */
|
||||
|
||||
@import 'flag-icon';
|
||||
|
||||
/* Modules BEGIN */
|
||||
|
||||
@import 'dial-out';
|
||||
@import 'toastr';
|
||||
@import 'base';
|
||||
@import 'utils';
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
|
||||
.invite-dialog {
|
||||
.dial-in-numbers {
|
||||
.dial-in-numbers-conference-id {
|
||||
color: orange;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
/*
|
||||
* dial-in-numbers-copy styling is needed for the feature of copying
|
||||
* text to the clipboard. The styling keeps the element invisible
|
||||
* to the user but still programmatically selectable for copying.
|
||||
*/
|
||||
.dial-in-numbers-copy {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.dial-in-numbers-trigger {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
6
debian/jitsi-meet-tokens.postinst
vendored
@@ -48,7 +48,9 @@ case "$1" in
|
||||
db_stop
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
if grep -q "plugin_paths" "$PROSODY_HOST_CONFIG"; then
|
||||
# search for --plugin_paths, if this is not enabled this is the
|
||||
# first time we install tokens package and needs a config change
|
||||
if grep -q "\-\-plugin_paths" "$PROSODY_HOST_CONFIG"; then
|
||||
# enable tokens in prosody host config
|
||||
sed -i 's/--plugin_paths/plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||
@@ -70,8 +72,6 @@ case "$1" in
|
||||
echo "Use the following command, after this package has been installed and"
|
||||
echo "after every prosody-trunk upgrade:"
|
||||
echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
|
||||
else
|
||||
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
||||
fi
|
||||
else
|
||||
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
projects. For example:
|
||||
|
||||
* The instance of lib-jitsi-meet's `JitsiConnection` type should be named
|
||||
`connection` or `jitsiConnection` in jitsi-meet-react, not `client`.
|
||||
`connection` or `jitsiConnection` in jitsi-meet, not `client`.
|
||||
|
||||
* The class `ReducerRegistry` should be defined in ReducerRegistry.js and its
|
||||
imports in other files should use the same name. Don't define the class
|
||||
|
||||
@@ -26,6 +26,7 @@ VirtualHost "jitmeet.example.com"
|
||||
c2s_require_encryption = false
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
storage = "null"
|
||||
--modules_enabled = { "token_verification" }
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
|
||||
@@ -238,23 +238,4 @@ You are now all set and ready to have your first meet by going to http://jitsi.e
|
||||
|
||||
|
||||
## Enabling recording
|
||||
Currently recording is only supported for linux-64 and macos. To enable it, add
|
||||
the following properties to sip-communicator.properties:
|
||||
```
|
||||
org.jitsi.videobridge.ENABLE_MEDIA_RECORDING=true
|
||||
org.jitsi.videobridge.MEDIA_RECORDING_PATH=/path/to/recordings/dir
|
||||
org.jitsi.videobridge.MEDIA_RECORDING_TOKEN=secret
|
||||
```
|
||||
|
||||
where /path/to/recordings/dir is the path to a pre-existing directory where recordings
|
||||
will be stored (needs to be writeable by the user running jitsi-videobridge),
|
||||
and "secret" is a string which will be used for authentication.
|
||||
|
||||
Then, edit the Jitsi-Meet config.js file and set:
|
||||
```
|
||||
enableRecording: true
|
||||
```
|
||||
|
||||
Restart jitsi-videobridge and start a new conference (making sure that the page
|
||||
is reloaded with the new config.js) -- the organizer of the conference should
|
||||
now have a "recording" button in the floating menu, near the "mute" button.
|
||||
[Jibri](https://github.com/jitsi/jibri)is a set of tools for recording and/or streaming a Jitsi Meet conference.
|
||||
|
||||
@@ -43,8 +43,8 @@ work properly with the native plugins we require.
|
||||
|
||||
Using Xcode
|
||||
|
||||
- Open **ios/jitsi-meet-react.xcworkspace** in Xcode. Make sure it's the
|
||||
workspace file!
|
||||
- Open **ios/jitsi-meet.xcworkspace** in Xcode. Make sure it's the workspace
|
||||
file!
|
||||
|
||||
- Select your device from the top bar and hit the "play" button.
|
||||
|
||||
|
||||
@@ -30,6 +30,17 @@ During the installation, you will be asked to enter the hostname of the Jitsi Me
|
||||
|
||||
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences.
|
||||
|
||||
#### Advanced configuration
|
||||
If installation is on a machine behind NAT further configuration of jitsi-videobridge is needed in order for it to be accessible.
|
||||
Provided that all required ports are routed (forwarded) to the machine that it runs on. By default these ports are (TCP/443 or TCP/4443 and UDP 10000).
|
||||
The following extra lines need to be added the file `/etc/jitsi/videobridge/sip-communicator.properties`:
|
||||
```
|
||||
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=<Local.IP.Address>
|
||||
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||
```
|
||||
See [the documenation of ice4j](https://github.com/jitsi/ice4j/blob/master/doc/configuration.md)
|
||||
for details.
|
||||
|
||||
### Open a conference
|
||||
|
||||
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||
@@ -63,7 +74,7 @@ Enjoy!
|
||||
## Uninstall
|
||||
|
||||
```sh
|
||||
apt-get purge jigasi jitsi-meet jicofo jitsi-videobridge
|
||||
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-web jicofo jitsi-videobridge
|
||||
```
|
||||
|
||||
Sometimes the following packages will fail to uninstall properly:
|
||||
|
||||
BIN
fonts/jitsi.eot
@@ -7,6 +7,7 @@
|
||||
<font-face units-per-em="1024" ascent="1024" descent="0" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " d="" />
|
||||
<glyph unicode="" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44z" />
|
||||
<glyph unicode="" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
|
||||
<glyph unicode="" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
|
||||
<glyph unicode="" glyph-name="recEnable" horiz-adv-x="1142" d="M581.278 1025.708c284.857-0.19 514.807-230.517 514.427-514.997-0.378-285.047-230.073-514.553-514.869-514.615-284.541-0.062-515.311 230.517-514.933 514.422 0.439 285.936 230.009 515.439 515.375 515.19zM580.579 875.756c-201.764-0.123-364.666-163.032-364.478-364.663 0-202.018 162.524-364.735 364.478-364.984 202.018-0.316 365.174 163.030 365.048 365.423-0.252 201.767-163.156 364.35-365.048 364.224zM287.698 688.907h98.196c63.442 1.767 94.785-24.518 94.027-78.863 0.254-19.081-2.211-34.882-7.456-47.521-6.005-12.508-18.706-21.988-38.167-28.181v-0.819c28.373-6.259 43.031-23.573 43.981-51.946v-57.689c0-11.247 0.254-22.813 0.758-34.756 0.819-12.005 3.033-20.979 6.696-27.043h-71.846c-3.727 6.064-6.128 15.038-7.14 27.043-1.012 11.943-1.454 23.509-1.138 34.756v52.321c0 9.603-2.214 16.553-6.573 20.979-4.675 4.107-12.701 6.19-24.012 6.19h-14.599v-141.291h-72.73v326.82zM360.428 558.861h19.463c13.271 0 21.359 3.794 24.331 11.375 2.276 7.204 3.221 16.304 2.969 27.171 0 5.815-0.126 10.867-0.442 15.418-0.252 4.675-1.392 8.404-3.352 11.247-1.831 3.157-4.926 5.561-9.352 7.14-4.233 1.454-10.299 2.211-18.2 2.211h-15.418v-74.564zM498.372 688.907h162.082v-62.687h-89.35v-65.587h78.103v-62.685h-78.103v-73.11h92.822v-62.749h-165.557v326.818zM682.507 599.999c0.316 31.782 9.416 55.542 27.425 71.407 17.44 15.29 40.185 22.936 68.181 22.936 28.247 0 51.119-7.646 68.623-23 17.82-15.798 26.92-39.623 27.171-71.407v-30.333h-72.73v37.031c0.254 6.192-0.57 12.639-2.527 19.209-1.264 3.157-3.475 5.938-6.573 8.214-3.221 1.515-7.898 2.404-13.964 2.404-10.615-0.316-17.249-3.855-19.967-10.618-2.211-6.573-3.223-13.017-2.907-19.209v-161.956c0-2.273 0.126-4.865 0.38-7.772 0.568-3.411 1.454-6.824 2.527-10.233 2.717-7.775 9.352-11.756 19.967-12.007 6.067 0 10.744 1.261 13.964 3.791 3.098 2.15 5.309 4.867 6.573 8.216 1.96 7.33 2.782 13.33 2.527 18.007v47.837h72.73v-41.328c-1.451-61.547-33.364-93.015-95.794-94.469-62.685 1.454-94.53 32.922-95.607 94.343v148.937z" />
|
||||
@@ -20,7 +21,6 @@
|
||||
<glyph unicode="" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
||||
<glyph unicode="" glyph-name="edit" d="M884 724l-78-78-160 160 78 78c16 16 44 16 60 0l100-100c16-16 16-44 0-60zM128 288l472 472 160-160-472-472h-160v160z" />
|
||||
<glyph unicode="" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" />
|
||||
<glyph unicode="" glyph-name="telephone" d="M854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24zM854 810v44h-44v-44h44zM768 896h128v-128h-86v-86h-42v214zM640 810v-128h-128v44h86v42h-86v128h128v-42h-86v-44h86zM726 896v-214h-44v214h44z" />
|
||||
<glyph unicode="" glyph-name="star-full" d="M512 288l-264-160 70 300-232 202 306 26 120 282 120-282 306-26-232-202 70-300z" />
|
||||
<glyph unicode="" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" />
|
||||
<glyph unicode="" glyph-name="exit-full-screen" d="M682 682h128v-84h-212v212h84v-128zM598 214v212h212v-84h-128v-128h-84zM342 682v128h84v-212h-212v84h128zM214 342v84h212v-212h-84v128h-128z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.woff
@@ -27,7 +27,7 @@
|
||||
"code": 59651
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
@@ -56,7 +56,7 @@
|
||||
"code": 59677
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 1
|
||||
},
|
||||
{
|
||||
@@ -85,7 +85,7 @@
|
||||
"code": 59676
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 2
|
||||
},
|
||||
{
|
||||
@@ -111,7 +111,7 @@
|
||||
"name": "avatar"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 3
|
||||
},
|
||||
{
|
||||
@@ -137,7 +137,7 @@
|
||||
"name": "hangup"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 4
|
||||
},
|
||||
{
|
||||
@@ -163,7 +163,7 @@
|
||||
"name": "chat"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 5
|
||||
},
|
||||
{
|
||||
@@ -189,7 +189,7 @@
|
||||
"name": "download"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 6
|
||||
},
|
||||
{
|
||||
@@ -215,7 +215,7 @@
|
||||
"name": "edit"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 7
|
||||
},
|
||||
{
|
||||
@@ -241,35 +241,9 @@
|
||||
"name": "share-doc"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 8
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M854 662c24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44l-94 94c62 122 162 220 282 282l94-94c12-12 30-14 44-10 48 16 98 24 152 24zM854 214v-44h-44v44h44zM768 128h128v128h-86v86h-42v-214zM640 214v128h-128v-44h86v-42h-86v-128h128v42h-86v44h86zM726 128v214h-44v-214h44z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"tags": [
|
||||
"dialer_sip"
|
||||
],
|
||||
"grid": 0
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 9,
|
||||
"order": 95,
|
||||
"ligatures": "dialer_sip",
|
||||
"prevSize": 32,
|
||||
"code": 59657,
|
||||
"name": "telephone"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"iconIdx": 9
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
@@ -293,7 +267,7 @@
|
||||
"name": "kick"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 10
|
||||
},
|
||||
{
|
||||
@@ -319,7 +293,7 @@
|
||||
"name": "menu-up"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 11
|
||||
},
|
||||
{
|
||||
@@ -345,7 +319,7 @@
|
||||
"name": "menu-down"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 12
|
||||
},
|
||||
{
|
||||
@@ -371,7 +345,7 @@
|
||||
"name": "full-screen"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 13
|
||||
},
|
||||
{
|
||||
@@ -397,7 +371,7 @@
|
||||
"name": "exit-full-screen"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 14
|
||||
},
|
||||
{
|
||||
@@ -423,7 +397,7 @@
|
||||
"name": "star-full"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 15
|
||||
},
|
||||
{
|
||||
@@ -449,7 +423,7 @@
|
||||
"name": "security"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 16
|
||||
},
|
||||
{
|
||||
@@ -475,7 +449,7 @@
|
||||
"name": "security-locked"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 17
|
||||
},
|
||||
{
|
||||
@@ -501,7 +475,7 @@
|
||||
"name": "reload"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 18
|
||||
},
|
||||
{
|
||||
@@ -527,7 +501,7 @@
|
||||
"name": "microphone"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 19
|
||||
},
|
||||
{
|
||||
@@ -553,7 +527,7 @@
|
||||
"name": "mic-empty"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 20
|
||||
},
|
||||
{
|
||||
@@ -579,7 +553,7 @@
|
||||
"name": "mic-disabled"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 21
|
||||
},
|
||||
{
|
||||
@@ -605,7 +579,7 @@
|
||||
"name": "raised-hand"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 22
|
||||
},
|
||||
{
|
||||
@@ -631,7 +605,7 @@
|
||||
"name": "contactList"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 23
|
||||
},
|
||||
{
|
||||
@@ -657,7 +631,7 @@
|
||||
"name": "link"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 24
|
||||
},
|
||||
{
|
||||
@@ -683,7 +657,7 @@
|
||||
"name": "shared-video"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 25
|
||||
},
|
||||
{
|
||||
@@ -709,7 +683,7 @@
|
||||
"name": "settings"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 26
|
||||
},
|
||||
{
|
||||
@@ -735,7 +709,7 @@
|
||||
"name": "star"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 27
|
||||
},
|
||||
{
|
||||
@@ -761,7 +735,7 @@
|
||||
"name": "switch-camera"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 28
|
||||
},
|
||||
{
|
||||
@@ -787,7 +761,7 @@
|
||||
"name": "share-desktop"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 29
|
||||
},
|
||||
{
|
||||
@@ -813,7 +787,7 @@
|
||||
"name": "camera"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 30
|
||||
},
|
||||
{
|
||||
@@ -839,7 +813,7 @@
|
||||
"name": "camera-disabled"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 31
|
||||
},
|
||||
{
|
||||
@@ -865,7 +839,7 @@
|
||||
"name": "volume"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 32
|
||||
},
|
||||
{
|
||||
@@ -913,7 +887,7 @@
|
||||
"code": 59648
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 33
|
||||
},
|
||||
{
|
||||
@@ -986,7 +960,7 @@
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 34
|
||||
},
|
||||
{
|
||||
@@ -1015,7 +989,7 @@
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 35
|
||||
},
|
||||
{
|
||||
@@ -1045,7 +1019,7 @@
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 36
|
||||
},
|
||||
{
|
||||
@@ -1075,7 +1049,7 @@
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 3,
|
||||
"setId": 1,
|
||||
"iconIdx": 37
|
||||
},
|
||||
{
|
||||
@@ -1095,14 +1069,14 @@
|
||||
"properties": {
|
||||
"order": 115,
|
||||
"ligatures": "dialpad",
|
||||
"id": 217,
|
||||
"id": 38,
|
||||
"prevSize": 32,
|
||||
"code": 59685,
|
||||
"name": "dialpad"
|
||||
},
|
||||
"setIdx": 1,
|
||||
"setId": 2,
|
||||
"iconIdx": 217
|
||||
"setIdx": 0,
|
||||
"setId": 1,
|
||||
"iconIdx": 38
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
@@ -1121,14 +1095,14 @@
|
||||
"properties": {
|
||||
"order": 114,
|
||||
"ligatures": "remove_red_eye, visibility",
|
||||
"id": 622,
|
||||
"id": 39,
|
||||
"prevSize": 32,
|
||||
"code": 59683,
|
||||
"name": "visibility"
|
||||
},
|
||||
"setIdx": 1,
|
||||
"setId": 2,
|
||||
"iconIdx": 622
|
||||
"setIdx": 0,
|
||||
"setId": 1,
|
||||
"iconIdx": 39
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
@@ -1147,14 +1121,41 @@
|
||||
"properties": {
|
||||
"order": 113,
|
||||
"ligatures": "visibility_off",
|
||||
"id": 816,
|
||||
"id": 40,
|
||||
"prevSize": 32,
|
||||
"code": 59684,
|
||||
"name": "visibility-off"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 1,
|
||||
"iconIdx": 40
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M282 460c62 120 162 220 282 282l94-94c12-12 30-16 44-10 48 16 100 24 152 24 24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44z"
|
||||
],
|
||||
"attrs": [],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"tags": [
|
||||
"phone"
|
||||
],
|
||||
"defaultCode": 57549,
|
||||
"grid": 24
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"ligatures": "call, local_phone, phone",
|
||||
"id": 120,
|
||||
"order": 848,
|
||||
"prevSize": 24,
|
||||
"code": 57549,
|
||||
"name": "phone"
|
||||
},
|
||||
"setIdx": 1,
|
||||
"setId": 2,
|
||||
"iconIdx": 816
|
||||
"setId": 0,
|
||||
"iconIdx": 120
|
||||
}
|
||||
],
|
||||
"height": 1024,
|
||||
@@ -1185,7 +1186,8 @@
|
||||
"useClassSelector": true
|
||||
},
|
||||
"historySize": 100,
|
||||
"showCodes": true,
|
||||
"search": ""
|
||||
"showCodes": false,
|
||||
"search": "",
|
||||
"showLiga": false
|
||||
}
|
||||
}
|
||||
9
images/countries/au.svg
Executable file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<g stroke-width="1pt">
|
||||
<path fill="#006" d="M0 0h640v480H0z"/>
|
||||
<path d="M0 0v27.95L307.037 250h38.647v-27.95L38.647 0H0zm345.684 0v27.95L38.647 250H0v-27.95L307.037 0h38.647z" fill="#fff"/>
|
||||
<path d="M144.035 0v250h57.614V0h-57.615zM0 83.333v83.333h345.684V83.333H0z" fill="#fff"/>
|
||||
<path d="M0 100v50h345.684v-50H0zM155.558 0v250h34.568V0h-34.568zM0 250l115.228-83.334h25.765L25.765 250H0zM0 0l115.228 83.333H89.463L0 18.633V0zm204.69 83.333L319.92 0h25.764L230.456 83.333H204.69zM345.685 250l-115.228-83.334h25.765l89.464 64.7V250z" fill="#c00"/>
|
||||
<path d="M299.762 392.523l-43.653 3.795 6.013 43.406-30.187-31.764-30.186 31.764 6.014-43.406-43.653-3.795 37.68-22.364-24.244-36.495 40.97 15.514 13.42-41.713 13.42 41.712 40.97-15.515-24.242 36.494m224.444 62.372l-10.537-15.854 17.81 6.742 5.824-18.125 5.825 18.126 17.807-6.742-10.537 15.854 16.37 9.718-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m16.368-291.815l-10.537-15.856 17.81 6.742 5.824-18.122 5.825 18.12 17.807-6.74-10.537 15.855 16.37 9.717-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m-89.418 104.883l-10.537-15.853 17.808 6.742 5.825-18.125 5.825 18.125 17.808-6.742-10.536 15.853 16.37 9.72-18.965 1.65 2.615 18.85-13.117-13.795-13.117 13.795 2.617-18.85-18.964-1.65m216.212-37.929l-10.558-15.854 17.822 6.742 5.782-18.125 5.854 18.125 17.772-6.742-10.508 15.854 16.362 9.718-18.97 1.65 2.608 18.85-13.118-13.793-13.117 13.793 2.61-18.85-18.936-1.65m-22.251 73.394l-10.367 6.425 2.914-11.84-9.316-7.863 12.165-.896 4.605-11.29 4.606 11.29 12.165.897-9.317 7.863 2.912 11.84" fill-rule="evenodd" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
6
images/countries/ca.svg
Executable file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<g transform="translate(74.118) scale(.9375)">
|
||||
<path fill="#fff" d="M81.137 0h362.276v512H81.137z"/>
|
||||
<path fill="#bf0a30" d="M-100 0H81.138v512H-100zm543.413 0H624.55v512H443.414zM135.31 247.41l-14.067 4.808 65.456 57.446c4.95 14.764-1.72 19.116-5.97 26.86l71.06-9.02-1.85 71.512 14.718-.423-3.21-70.918 71.13 8.432c-4.402-9.297-8.32-14.233-4.247-29.098l65.414-54.426-11.447-4.144c-9.36-7.222 4.044-34.784 6.066-52.178 0 0-38.195 13.135-40.698 6.262l-9.727-18.685-34.747 38.17c-3.796.91-5.413-.6-6.304-3.808l16.053-79.766-25.42 14.297c-2.128.91-4.256.125-5.658-2.355l-24.45-49.06-25.21 50.95c-1.9 1.826-3.803 2.037-5.382.796l-24.204-13.578 14.53 79.143c-1.156 3.14-3.924 4.025-7.18 2.324l-33.216-37.737c-4.345 6.962-7.29 18.336-13.033 20.885-5.744 2.387-24.98-4.823-37.873-7.637 4.404 15.895 18.176 42.302 9.46 50.957z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 934 B |
5
images/countries/de.svg
Executable file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<path fill="#ffce00" d="M0 320h640v160.002H0z"/>
|
||||
<path d="M0 0h640v160H0z"/>
|
||||
<path fill="#d00" d="M0 160h640v160H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 220 B |
7
images/countries/fr.svg
Executable file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="#fff" d="M0 0h640v480H0z"/>
|
||||
<path fill="#00267f" d="M0 0h213.337v480H0z"/>
|
||||
<path fill="#f31830" d="M426.662 0H640v480H426.662z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 301 B |
15
images/countries/gb.svg
Executable file
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill-opacity=".67" d="M-85.333 0h682.67v512h-682.67z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#a)" transform="translate(80) scale(.94)">
|
||||
<g stroke-width="1pt">
|
||||
<path fill="#006" d="M-256 0H768.02v512.01H-256z"/>
|
||||
<path d="M-256 0v57.244l909.535 454.768H768.02V454.77L-141.515 0H-256zM768.02 0v57.243L-141.515 512.01H-256v-57.243L653.535 0H768.02z" fill="#fff"/>
|
||||
<path d="M170.675 0v512.01h170.67V0h-170.67zM-256 170.67v170.67H768.02V170.67H-256z" fill="#fff"/>
|
||||
<path d="M-256 204.804v102.402H768.02V204.804H-256zM204.81 0v512.01h102.4V0h-102.4zM-256 512.01L85.34 341.34h76.324l-341.34 170.67H-256zM-256 0L85.34 170.67H9.016L-256 38.164V0zm606.356 170.67L691.696 0h76.324L426.68 170.67h-76.324zM768.02 512.01L426.68 341.34h76.324L768.02 473.848v38.162z" fill="#c00"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 956 B |
18
images/countries/us.svg
Executable file
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" transform="scale(.9375)">
|
||||
<g stroke-width="1pt">
|
||||
<path d="M0 0h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#bd3d44"/>
|
||||
<path d="M0 39.385h972.81V78.77H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#fff"/>
|
||||
</g>
|
||||
<path fill="#192f5d" d="M0 0h389.12v275.69H0z"/>
|
||||
<g fill="#fff">
|
||||
<path d="M32.427 11.8l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 39.37l3.54 10.896h11.458L70.583 57l3.542 10.897-9.27-6.734-9.269 6.734L59.126 57l-9.269-6.734h11.458zm64.852 0l3.54 10.896h11.457L135.435 57l3.54 10.897-9.268-6.734-9.27 6.734L123.978 57l-9.27-6.734h11.458zm64.855 0l3.54 10.896h11.458L200.29 57l3.541 10.897-9.27-6.734-9.268 6.734L188.833 57l-9.269-6.734h11.457zm64.855 0l3.54 10.896h11.458L265.145 57l3.541 10.897-9.269-6.734-9.27 6.734L253.69 57l-9.27-6.734h11.458zm64.852 0l3.54 10.896h11.457L329.997 57l3.54 10.897-9.268-6.734-9.27 6.734L318.54 57l-9.27-6.734h11.458zM32.427 66.939l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 94.508l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zM32.427 122.078l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 149.647l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
|
||||
<g>
|
||||
<path d="M32.427 177.217l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 204.786l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M32.427 232.356l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -4,6 +4,7 @@
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--#include virtual="base.html" -->
|
||||
<!--#include virtual="local.html" -->
|
||||
<script>
|
||||
window.indexLoadedTime = window.performance.now();
|
||||
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
|
||||
@@ -127,7 +128,6 @@
|
||||
'error', loadErrHandler, true /* capture phase type of listener */);
|
||||
</script>
|
||||
<script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="static/utils.js?v=1"></script>
|
||||
<!--#include virtual="connection_optimization/connection_optimization.html" -->
|
||||
<script src="libs/do_external_connect.min.js?v=1"></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
|
||||
@@ -38,7 +38,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
||||
//main toolbar
|
||||
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'hangup',
|
||||
//extended toolbar
|
||||
'profile', 'contacts', 'chat', 'audioonly', 'recording', 'etherpad', 'sharedvideo', 'sip', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
|
||||
'profile', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
|
||||
/**
|
||||
* Main Toolbar Buttons
|
||||
* All of them should be in TOOLBAR_BUTTONS
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = "<group>"; };
|
||||
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
|
||||
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet-react.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = app/AppDelegate.m; sourceTree = "<group>"; };
|
||||
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
@@ -303,7 +303,7 @@
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
B30EF2301DC0ED7C00690F45 /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
|
||||
B3A9D0241E0481E10009343D /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = POSIX.m; path = app/POSIX.m; sourceTree = "<group>"; };
|
||||
B3B083EB1D4955FF0069CEE7 /* jitsi-meet-react.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "jitsi-meet-react.entitlements"; sourceTree = "<group>"; };
|
||||
B3B083EB1D4955FF0069CEE7 /* jitsi-meet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "jitsi-meet.entitlements"; sourceTree = "<group>"; };
|
||||
B96AF9B6FBC0453798399985 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
|
||||
BF9643811C34FBB300B0BBDF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
BF9643831C34FBBB00B0BBDF /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
@@ -530,7 +530,7 @@
|
||||
children = (
|
||||
13B07FAE1A68108700A75B9A /* app */,
|
||||
B3BA19B71DC6B02F00BCD481 /* Frameworks */,
|
||||
B3B083EB1D4955FF0069CEE7 /* jitsi-meet-react.entitlements */,
|
||||
B3B083EB1D4955FF0069CEE7 /* jitsi-meet.entitlements */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
6956B374CC3C453DB7B8E82D /* Resources */,
|
||||
@@ -542,7 +542,7 @@
|
||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */,
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -581,9 +581,9 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
13B07F861A680F5B00A75B9A /* jitsi-meet-react */ = {
|
||||
13B07F861A680F5B00A75B9A /* jitsi-meet */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet-react" */;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */;
|
||||
buildPhases = (
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
@@ -596,9 +596,9 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "jitsi-meet-react";
|
||||
name = "jitsi-meet";
|
||||
productName = "Hello World";
|
||||
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -620,7 +620,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet-react" */;
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
@@ -695,7 +695,7 @@
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* jitsi-meet-react */,
|
||||
13B07F861A680F5B00A75B9A /* jitsi-meet */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -989,7 +989,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "jitsi-meet-react.entitlements";
|
||||
CODE_SIGN_ENTITLEMENTS = "jitsi-meet.entitlements";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1019,7 +1019,7 @@
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
|
||||
PRODUCT_NAME = "jitsi-meet-react";
|
||||
PRODUCT_NAME = "jitsi-meet";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1028,7 +1028,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "jitsi-meet-react.entitlements";
|
||||
CODE_SIGN_ENTITLEMENTS = "jitsi-meet.entitlements";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -1057,7 +1057,7 @@
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
|
||||
PRODUCT_NAME = "jitsi-meet-react";
|
||||
PRODUCT_NAME = "jitsi-meet";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -1164,7 +1164,7 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet-react" */ = {
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13B07F941A680F5B00A75B9A /* Debug */,
|
||||
@@ -1173,7 +1173,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet-react" */ = {
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||
@@ -29,9 +29,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "jitsi-meet-react.app"
|
||||
BlueprintName = "jitsi-meet-react"
|
||||
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
|
||||
BuildableName = "jitsi-meet.app"
|
||||
BlueprintName = "jitsi-meet"
|
||||
ReferencedContainer = "container:jitsi-meet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
@@ -47,9 +47,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "jitsi-meet-react.app"
|
||||
BlueprintName = "jitsi-meet-react"
|
||||
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
|
||||
BuildableName = "jitsi-meet.app"
|
||||
BlueprintName = "jitsi-meet"
|
||||
ReferencedContainer = "container:jitsi-meet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
@@ -70,9 +70,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "jitsi-meet-react.app"
|
||||
BlueprintName = "jitsi-meet-react"
|
||||
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
|
||||
BuildableName = "jitsi-meet.app"
|
||||
BlueprintName = "jitsi-meet"
|
||||
ReferencedContainer = "container:jitsi-meet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
@@ -89,9 +89,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "jitsi-meet-react.app"
|
||||
BlueprintName = "jitsi-meet-react"
|
||||
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
|
||||
BuildableName = "jitsi-meet.app"
|
||||
BlueprintName = "jitsi-meet"
|
||||
ReferencedContainer = "container:jitsi-meet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:jitsi-meet-react.xcodeproj">
|
||||
location = "group:jitsi-meet.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
20
lang/languages-vi.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"zhCN": "",
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
}
|
||||
415
lang/main-vi.json
Normal file
@@ -0,0 +1,415 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "",
|
||||
"roomUnlocked": "",
|
||||
"passwordSetRemotely": "",
|
||||
"connectionsettings": "",
|
||||
"poweredby": "",
|
||||
"feedback": "",
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"me": "",
|
||||
"speaker": "",
|
||||
"raisedHand": "",
|
||||
"defaultNickname": "",
|
||||
"defaultLink": "",
|
||||
"callingName": "",
|
||||
"audioOnly": {
|
||||
"audioOnly": "",
|
||||
"featureToggleDisabled": "",
|
||||
"howToDisable": ""
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "",
|
||||
"edgeGrantPermissions": ""
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "",
|
||||
"raiseHand": "",
|
||||
"pushToTalk": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"toggleChat": "",
|
||||
"mute": "",
|
||||
"fullScreen": "",
|
||||
"videoMute": "",
|
||||
"showSpeakerStats": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"disable": "",
|
||||
"feature1": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature2": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature3": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature4": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature5": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature6": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature7": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature8": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"roomname": "",
|
||||
"roomnamePlaceHolder": "",
|
||||
"sendFeedback": "",
|
||||
"terms": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "",
|
||||
"text": "",
|
||||
"rejoinKeyTitle": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"audioonly": "",
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"chat": "",
|
||||
"etherpad": "",
|
||||
"sharedvideo": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"dialpad": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "",
|
||||
"micDisabled": "",
|
||||
"filmstrip": "",
|
||||
"profile": "",
|
||||
"raiseHand": ""
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"joinConversation": "",
|
||||
"startConference": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
},
|
||||
"messagebox": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"selectAudioOutput": "",
|
||||
"followMe": "",
|
||||
"noDevice": "",
|
||||
"cameraAndMic": "",
|
||||
"moderator": "",
|
||||
"password": "",
|
||||
"audioVideo": ""
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailLabel": "",
|
||||
"setEmailInput": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"remoteControl": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"framerate": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural_undefined": "",
|
||||
"localport": "",
|
||||
"localport_plural_undefined": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural_undefined": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural_undefined": "",
|
||||
"transport": "",
|
||||
"transport_plural_undefined": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": ""
|
||||
},
|
||||
"dialog": {
|
||||
"add": "",
|
||||
"allow": "",
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"incorrectPassword": "",
|
||||
"connecting": "",
|
||||
"copy": "",
|
||||
"error": "",
|
||||
"createPassword": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"rejoinNow": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"internalErrorTitle": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"currentPassword": "",
|
||||
"passwordLabel": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"done": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareVideoLinkError": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"WaitingForHost": "",
|
||||
"WaitForHostMsg": "",
|
||||
"IamHost": "",
|
||||
"Cancel": "",
|
||||
"Submit": "",
|
||||
"retry": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"Save": "",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "",
|
||||
"token": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "",
|
||||
"displayNameRequired": "",
|
||||
"enterDisplayName": "",
|
||||
"extensionRequired": "",
|
||||
"firefoxExtensionPrompt": "",
|
||||
"rateExperience": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"thankYou": "",
|
||||
"sorryFeedback": "",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "",
|
||||
"startLiveStreaming": "",
|
||||
"stopStreamingWarning": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"doNotShowWarningAgain": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"permissionDenied": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"micErrorPresent": "",
|
||||
"cameraErrorPresent": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingData": "",
|
||||
"goToStore": "",
|
||||
"externalInstallationTitle": "",
|
||||
"externalInstallationMsg": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"remoteControlTitle": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"remoteControlStopMessage": "",
|
||||
"close": "",
|
||||
"shareYourScreen": "",
|
||||
"yourEntireScreen": "",
|
||||
"applicationWindow": ""
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": "",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"and": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "",
|
||||
"CONNECTING": "",
|
||||
"RECONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ATTACHED": ""
|
||||
},
|
||||
"recording": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unavailable": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"streamIdRequired": "",
|
||||
"streamIdHelp": "",
|
||||
"error": "",
|
||||
"busy": ""
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "",
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "",
|
||||
"testAudio": ""
|
||||
},
|
||||
"invite": {
|
||||
"addPassword": "",
|
||||
"dialInNumbers": "",
|
||||
"errorFetchingNumbers": "",
|
||||
"hidePassword": "",
|
||||
"inviteTo": "",
|
||||
"loadingNumbers": "",
|
||||
"locked": "",
|
||||
"noNumbers": "",
|
||||
"numbersDisabled": "",
|
||||
"showPassword": "",
|
||||
"unlocked": ""
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,7 @@
|
||||
"callingName": "__name__",
|
||||
"audioOnly": {
|
||||
"audioOnly": "Audio only",
|
||||
"featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode",
|
||||
"howToDisable": "Audio only mode is currently enabled. Click the audio only button in the toolbar to disable the feature."
|
||||
"featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode"
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
@@ -278,8 +277,6 @@
|
||||
"Save": "Save",
|
||||
"recording": "Recording",
|
||||
"recordingToken": "Enter recording token",
|
||||
"Dial": "Dial",
|
||||
"sipMsg": "Enter SIP number",
|
||||
"passwordCheck": "Are you sure you would like to remove your password?",
|
||||
"passwordMsg": "Set a password to lock your room",
|
||||
"shareLink": "Share the link to the call",
|
||||
@@ -437,15 +434,27 @@
|
||||
},
|
||||
"invite": {
|
||||
"addPassword": "Add password",
|
||||
"dialInNumbers": "Dial-in telephone numbers",
|
||||
"errorFetchingNumbers": "Failed to obtain dial-in numbers",
|
||||
"callNumber": "Call __number__",
|
||||
"enterId": "Enter Meeting ID: __meetingId__ following by # to dial in from a phone",
|
||||
"howToDialIn": "To dial in, use one of the following numbers and meeting ID",
|
||||
"hidePassword": "Hide password",
|
||||
"inviteTo": "Invite people to __conferenceName__",
|
||||
"loadingNumbers": "Loading...",
|
||||
"invitedYouTo": "__userName__ has invited you to the __meetingUrl__ conference",
|
||||
"locked": "This call is locked. New callers must have the link and enter the password to join.",
|
||||
"noNumbers": "No numbers available",
|
||||
"numbersDisabled": "Dialing in has been disabled",
|
||||
"showPassword": "Show password",
|
||||
"unlocked": "This call is unlocked. Any new caller with the link may join the call."
|
||||
},
|
||||
"videoStatus": {
|
||||
"hd": "HD",
|
||||
"hdVideo": "HD video",
|
||||
"sd": "SD",
|
||||
"sdVideo": "SD video"
|
||||
},
|
||||
"dialOut": {
|
||||
"dial": "Dial",
|
||||
"dialOut": "Call a phone number",
|
||||
"statusMessage": "is now __status__",
|
||||
"enterPhone": "Enter phone number",
|
||||
"phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!"
|
||||
}
|
||||
}
|
||||
|
||||
0
local.html
Normal file
@@ -1,9 +1,13 @@
|
||||
declare var getConfigParamsFromUrl: Function;
|
||||
// XXX The function parseURLParams is exported by the feature base/config (as
|
||||
// defined in the terminology of react/). However, this file is (very likely)
|
||||
// bundled in external_api in addition to app.bundle and, consequently, it is
|
||||
// best to import as little as possible here (rather than the whole feature
|
||||
// base/config) in order to minimize the amount of source code bundled into
|
||||
// multiple bundles.
|
||||
import parseURLParams from '../../react/features/base/config/parseURLParams';
|
||||
|
||||
/**
|
||||
* JitsiMeetExternalAPI id - unique for a webpage.
|
||||
*/
|
||||
export const API_ID
|
||||
= typeof getConfigParamsFromUrl === 'function'
|
||||
? getConfigParamsFromUrl().jitsi_meet_external_api_id
|
||||
: undefined;
|
||||
= parseURLParams(window.location).jitsi_meet_external_api_id;
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
showDialPadButton,
|
||||
showEtherpadButton,
|
||||
showSharedVideoButton,
|
||||
showSIPCallButton,
|
||||
showDialOutButton,
|
||||
showToolbox
|
||||
} from '../../react/features/toolbox';
|
||||
|
||||
@@ -362,9 +362,18 @@ UI.start = function () {
|
||||
|
||||
}
|
||||
|
||||
if(APP.tokenData.callee) {
|
||||
UI.showRingOverlay();
|
||||
}
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && UI.showRingOverlay();
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes cleanup of any deferred execution within relevant UI modules.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.stopDaemons = () => {
|
||||
VideoLayout.resetLargeVideo();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -535,7 +544,7 @@ UI.onPeerVideoTypeChanged
|
||||
UI.updateLocalRole = isModerator => {
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
APP.store.dispatch(showSIPCallButton(isModerator));
|
||||
APP.store.dispatch(showDialOutButton(isModerator));
|
||||
APP.store.dispatch(showSharedVideoButton());
|
||||
|
||||
Recording.showRecordingButton(isModerator);
|
||||
@@ -580,6 +589,21 @@ UI.updateUserRole = user => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the user status.
|
||||
*
|
||||
* @param {JitsiParticipant} user - The user which status we need to update.
|
||||
* @param {string} status - The new status.
|
||||
*/
|
||||
UI.updateUserStatus = (user, status) => {
|
||||
let displayName = user.getDisplayName();
|
||||
messageHandler.notify(
|
||||
displayName, '', 'connected', "dialOut.statusMessage",
|
||||
{
|
||||
status: UIUtil.escapeHtml(status)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles smileys in the chat.
|
||||
*/
|
||||
@@ -1332,7 +1356,10 @@ UI.setMicrophoneButtonEnabled
|
||||
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
|
||||
|
||||
Filmstrip.toggleFilmstrip(false, false);
|
||||
};
|
||||
|
||||
@@ -1397,7 +1424,13 @@ const UIListeners = new Map([
|
||||
UI.toggleContactList
|
||||
], [
|
||||
UIEvents.TOGGLE_PROFILE,
|
||||
() => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
|
||||
() => {
|
||||
const {
|
||||
isGuest
|
||||
} = APP.store.getState()['features/jwt'];
|
||||
|
||||
isGuest && UI.toggleSidePanel('profile_container');
|
||||
}
|
||||
], [
|
||||
UIEvents.TOGGLE_FILMSTRIP,
|
||||
UI.handleToggleFilmstrip
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* global APP, config, JitsiMeetJS, Promise */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import { openConnection } from '../../../connection';
|
||||
import { setJWT } from '../../../react/features/jwt';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import {openConnection} from '../../../connection';
|
||||
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
let externalAuthWindow;
|
||||
let authRequiredDialog;
|
||||
@@ -73,15 +75,20 @@ function redirectToTokenAuthService(roomName) {
|
||||
* @param room the name fo the conference room.
|
||||
*/
|
||||
function initJWTTokenListener(room) {
|
||||
var listener = function (event) {
|
||||
if (externalAuthWindow !== event.source) {
|
||||
var listener = function ({ data, source }) {
|
||||
if (externalAuthWindow !== source) {
|
||||
logger.warn("Ignored message not coming " +
|
||||
"from external authnetication window");
|
||||
return;
|
||||
}
|
||||
if (event.data && event.data.jwtToken) {
|
||||
config.token = event.data.jwtToken;
|
||||
logger.info("Received JWT token:", config.token);
|
||||
|
||||
let jwt;
|
||||
|
||||
if (data && (jwt = data.jwtToken)) {
|
||||
logger.info("Received JSON Web Token (JWT):", jwt);
|
||||
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
|
||||
var roomName = room.getName();
|
||||
openConnection({retry: false, roomName: roomName })
|
||||
.then(function (connection) {
|
||||
|
||||
@@ -22,7 +22,8 @@ function onAvatarVisible(shown) {
|
||||
*/
|
||||
class RingOverlay {
|
||||
/**
|
||||
* @param callee instance of User class from TokenData.js
|
||||
*
|
||||
* @param callee The callee (Object) as defined by the JWT support.
|
||||
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
|
||||
*/
|
||||
constructor(callee, disableRingingSound) {
|
||||
@@ -77,9 +78,9 @@ class RingOverlay {
|
||||
<div id="${this._containerId}" class='ringing' >
|
||||
<div class='ringing__content'>
|
||||
${callingLabel}
|
||||
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
|
||||
<img class='ringing__avatar' src="${callee.avatarUrl}" />
|
||||
<div class="ringing__caller-info">
|
||||
<p>${callee.getName()}${callerStateLabel}</p>
|
||||
<p>${callee.name}${callerStateLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
${audioHTML}
|
||||
@@ -137,9 +138,12 @@ class RingOverlay {
|
||||
export default {
|
||||
/**
|
||||
* Shows the ring overlay for the passed callee.
|
||||
* @param callee {class User} the callee. Instance of User class from
|
||||
* TokenData.js
|
||||
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
|
||||
*
|
||||
* @param {Object} callee - The callee. Object containing data about
|
||||
* callee.
|
||||
* @param {boolean} disableRingingSound - If true the ringing sound won't be
|
||||
* played.
|
||||
* @returns {void}
|
||||
*/
|
||||
show(callee, disableRingingSound = false) {
|
||||
if(overlay) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
/* global $, APP, config */
|
||||
/* global $, APP */
|
||||
/* jshint -W101 */
|
||||
import {
|
||||
setLargeVideoHDStatus
|
||||
} from '../../../react/features/base/conference';
|
||||
|
||||
import JitsiPopover from "../util/JitsiPopover";
|
||||
import VideoLayout from "./VideoLayout";
|
||||
import UIUtil from "../util/UIUtil";
|
||||
|
||||
/**
|
||||
@@ -38,7 +34,6 @@ function ConnectionIndicator(videoContainer, videoId) {
|
||||
this.bitrate = null;
|
||||
this.showMoreValue = false;
|
||||
this.resolution = null;
|
||||
this.isResolutionHD = null;
|
||||
this.transport = [];
|
||||
this.framerate = null;
|
||||
this.popover = null;
|
||||
@@ -405,10 +400,6 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||
let width = qualityToWidth.find(x => percent >= x.percent);
|
||||
this.fullIcon.style.width = width.width;
|
||||
|
||||
if (object && typeof object.isResolutionHD === 'boolean') {
|
||||
this.isResolutionHD = object.isResolutionHD;
|
||||
}
|
||||
this.updateResolutionIndicator();
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
@@ -418,7 +409,6 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateResolution = function (resolution) {
|
||||
this.resolution = resolution;
|
||||
this.updateResolutionIndicator();
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
@@ -461,31 +451,6 @@ ConnectionIndicator.prototype.hideIndicator = function () {
|
||||
this.popover.forceHide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the resolution indicator.
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateResolutionIndicator = function () {
|
||||
|
||||
if (this.id !== null
|
||||
&& VideoLayout.isCurrentlyOnLarge(this.id)) {
|
||||
|
||||
let showResolutionLabel = false;
|
||||
|
||||
if (this.isResolutionHD !== null)
|
||||
showResolutionLabel = this.isResolutionHD;
|
||||
else if (this.resolution !== null) {
|
||||
let resolutions = this.resolution || {};
|
||||
Object.keys(resolutions).map(function (ssrc) {
|
||||
const { height } = resolutions[ssrc];
|
||||
if (height >= config.minHDHeight)
|
||||
showResolutionLabel = true;
|
||||
});
|
||||
}
|
||||
|
||||
APP.store.dispatch(setLargeVideoHDStatus(showResolutionLabel));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a hover listener to the popover.
|
||||
*/
|
||||
|
||||
@@ -15,18 +15,19 @@ const Filmstrip = {
|
||||
this.filmstripContainerClassName = 'filmstrip';
|
||||
this.filmstrip = $('#remoteVideos');
|
||||
this.eventEmitter = eventEmitter;
|
||||
this._initFilmstripToolbar();
|
||||
this.registerListeners();
|
||||
|
||||
// Show the toggle button and add event listeners only when out of
|
||||
// filmstrip only mode.
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
this._initFilmstripToolbar();
|
||||
this.registerListeners();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the filmstrip toolbar.
|
||||
*/
|
||||
_initFilmstripToolbar() {
|
||||
// Do not show the toggle button in filmstrip only mode.
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
|
||||
let toolbarContainerHTML = this._generateToolbarHTML();
|
||||
let className = this.filmstripContainerClassName;
|
||||
let container = document.querySelector(`.${className}`);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* global $, APP, JitsiMeetJS */
|
||||
/* global $, APP, config, JitsiMeetJS */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import { setLargeVideoHDStatus } from '../../../react/features/base/conference';
|
||||
|
||||
import Avatar from "../avatar/Avatar";
|
||||
import {createDeferred} from '../../util/helpers';
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
@@ -12,6 +14,13 @@ import AudioLevels from "../audio_levels/AudioLevels";
|
||||
const ParticipantConnectionStatus
|
||||
= JitsiMeetJS.constants.participantConnectionStatus;
|
||||
const DESKTOP_CONTAINER_TYPE = 'desktop';
|
||||
/**
|
||||
* The time interval in milliseconds to check the video resolution of the video
|
||||
* being displayed.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const VIDEO_RESOLUTION_POLL_INTERVAL = 2000;
|
||||
|
||||
/**
|
||||
* Manager for all Large containers.
|
||||
@@ -49,6 +58,39 @@ export default class LargeVideoManager {
|
||||
e => this.onHoverIn(e),
|
||||
e => this.onHoverOut(e)
|
||||
);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._updateVideoResolutionStatus
|
||||
= this._updateVideoResolutionStatus.bind(this);
|
||||
|
||||
this.videoContainer.addResizeListener(
|
||||
this._updateVideoResolutionStatus);
|
||||
|
||||
if (!JitsiMeetJS.util.RTCUIHelper.isResizeEventSupported()) {
|
||||
/**
|
||||
* An interval for polling if the displayed video resolution is or
|
||||
* is not high-definition. For browsers that do not support video
|
||||
* resize events, polling is the fallback.
|
||||
*
|
||||
* @private
|
||||
* @type {timeoutId}
|
||||
*/
|
||||
this._updateVideoResolutionInterval = window.setInterval(
|
||||
this._updateVideoResolutionStatus,
|
||||
VIDEO_RESOLUTION_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any polling intervals on the instance and and removes any
|
||||
* listeners registered on child components.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
destroy() {
|
||||
window.clearInterval(this._updateVideoResolutionInterval);
|
||||
this.videoContainer.removeResizeListener(
|
||||
this._updateVideoResolutionStatus);
|
||||
}
|
||||
|
||||
onHoverIn (e) {
|
||||
@@ -517,4 +559,18 @@ export default class LargeVideoManager {
|
||||
onLocalFlipXChange(val) {
|
||||
this.videoContainer.setLocalFlipX(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the known resolution state of the
|
||||
* large video.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_updateVideoResolutionStatus() {
|
||||
const { height, width } = this.videoContainer.getStreamSize();
|
||||
const isCurrentlyHD = Math.min(height, width) >= config.minHDHeight;
|
||||
|
||||
APP.store.dispatch(setLargeVideoHDStatus(isCurrentlyHD));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,12 +375,14 @@ RemoteVideo.prototype._generatePopupMenuSliderItem = function (options) {
|
||||
<span class='popupmenu__icon'>
|
||||
<i class=${options.icon}></i>
|
||||
</span>
|
||||
<input class='popupmenu__slider'
|
||||
type='range'
|
||||
min='0'
|
||||
max=${options.maxValue || 100}
|
||||
value=${options.initialValue || 0}>
|
||||
</input>
|
||||
<div class='popupmenu__slider_container'>
|
||||
<input class='popupmenu__slider'
|
||||
type='range'
|
||||
min='0'
|
||||
max=${options.maxValue || 100}
|
||||
value=${options.initialValue || 0}>
|
||||
</input>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const menuItem = document.createElement('li');
|
||||
|
||||
@@ -216,6 +216,28 @@ export class VideoContainer extends LargeContainer {
|
||||
// copied between new <object> elements
|
||||
//this.$video.on('play', onPlay);
|
||||
this.$video[0].onplay = onPlayCallback;
|
||||
|
||||
/**
|
||||
* A Set of functions to invoke when the video element resizes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this._resizeListeners = new Set();
|
||||
|
||||
// As of May 16, 2017, temasys does not support resize events.
|
||||
this.$video[0].onresize = this._onResize.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a function to the known subscribers of video element resize
|
||||
* events.
|
||||
*
|
||||
* @param {Function} callback - The subscriber to notify when the video
|
||||
* element resizes.
|
||||
* @returns {void}
|
||||
*/
|
||||
addResizeListener(callback) {
|
||||
this._resizeListeners.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,6 +366,18 @@ export class VideoContainer extends LargeContainer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a function from the known subscribers of video element resize
|
||||
* events.
|
||||
*
|
||||
* @param {Function} callback - The callback to remove from known
|
||||
* subscribers of video resize events.
|
||||
* @returns {void}
|
||||
*/
|
||||
removeResizeListener(callback) {
|
||||
this._resizeListeners.delete(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update video stream.
|
||||
* @param {JitsiTrack?} stream new stream
|
||||
@@ -502,4 +536,14 @@ export class VideoContainer extends LargeContainer {
|
||||
(this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar)
|
||||
? "#000" : interfaceConfig.DEFAULT_BACKGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when the video element changes dimensions.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onResize() {
|
||||
this._resizeListeners.forEach(callback => callback());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,18 @@ var VideoLayout = {
|
||||
this.registerListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleans up any existing largeVideo instance.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
resetLargeVideo() {
|
||||
if (largeVideo) {
|
||||
largeVideo.destroy();
|
||||
}
|
||||
largeVideo = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Registering listeners for UI events in Video layout component.
|
||||
*
|
||||
@@ -132,6 +144,8 @@ var VideoLayout = {
|
||||
},
|
||||
|
||||
initLargeVideo () {
|
||||
this.resetLargeVideo();
|
||||
|
||||
largeVideo = new LargeVideoManager(eventEmitter);
|
||||
if(localFlipX) {
|
||||
largeVideo.onLocalFlipXChange(localFlipX);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* global JitsiMeetJS, config, APP */
|
||||
|
||||
/**
|
||||
* Load the integration of a third-party analytics API such as Google
|
||||
* Analytics. Since we cannot guarantee the quality of the third-party service
|
||||
@@ -101,26 +102,37 @@ class Analytics {
|
||||
* null.
|
||||
*/
|
||||
init() {
|
||||
let analytics = JitsiMeetJS.analytics;
|
||||
if(!this.isEnabled() || !analytics)
|
||||
const { analytics } = JitsiMeetJS;
|
||||
|
||||
if (!this.isEnabled() || !analytics)
|
||||
return;
|
||||
|
||||
this._loadHandlers()
|
||||
.then(handlers => {
|
||||
let permanentProperties = {
|
||||
userAgent: navigator.userAgent,
|
||||
roomName: APP.conference.roomName
|
||||
this._loadHandlers().then(
|
||||
handlers => {
|
||||
const permanentProperties = {
|
||||
roomName: APP.conference.roomName,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
let {server, group} = APP.tokenData;
|
||||
if(server) {
|
||||
|
||||
const { group, server } = APP.store.getState()['features/jwt'];
|
||||
|
||||
if (server) {
|
||||
permanentProperties.server = server;
|
||||
}
|
||||
if(group) {
|
||||
if (group) {
|
||||
permanentProperties.group = group;
|
||||
}
|
||||
if (window.jitsiAnalyticsPermanentProperties) {
|
||||
for (var key in window.jitsiAnalyticsPermanentProperties) {
|
||||
permanentProperties[key]
|
||||
= window.jitsiAnalyticsPermanentProperties[key];
|
||||
}
|
||||
}
|
||||
|
||||
analytics.addPermanentProperties(permanentProperties);
|
||||
analytics.setAnalyticsHandlers(handlers);
|
||||
}, error => analytics.dispose() && console.error(error));
|
||||
},
|
||||
error => analytics.dispose() && console.error(error));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var JSSHA = require('jssha');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Looks for a list of possible BOSH addresses in 'config.boshList' and
|
||||
* sets the value of 'config.bosh' based on that list and 'roomName'.
|
||||
* @param config the configuration object.
|
||||
* @param roomName the name of the room/conference.
|
||||
*/
|
||||
chooseAddress: function(config, roomName) {
|
||||
if (!roomName || !config.boshList || !Array.isArray(config.boshList) ||
|
||||
!config.boshList.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This implements the actual choice of an entry in the list based on
|
||||
// roomName. Please consider the implications for existing deployments
|
||||
// before introducing changes.
|
||||
var hash = (new JSSHA(roomName, 'TEXT')).getHash('SHA-1', 'HEX');
|
||||
var n = parseInt("0x"+hash.substr(-6));
|
||||
var idx = n % config.boshList.length;
|
||||
var attemptFirstAddress;
|
||||
|
||||
config.bosh = config.boshList[idx];
|
||||
logger.log('Setting config.bosh to ' + config.bosh +
|
||||
' (idx=' + idx + ')');
|
||||
|
||||
if (config.boshAttemptFirstList &&
|
||||
Array.isArray(config.boshAttemptFirstList) &&
|
||||
config.boshAttemptFirstList.length > 0) {
|
||||
|
||||
idx = n % config.boshAttemptFirstList.length;
|
||||
attemptFirstAddress = config.boshAttemptFirstList[idx];
|
||||
|
||||
if (attemptFirstAddress != config.bosh) {
|
||||
config.boshAttemptFirst = attemptFirstAddress;
|
||||
logger.log('Setting config.boshAttemptFirst=' +
|
||||
attemptFirstAddress + ' (idx=' + idx + ')');
|
||||
} else {
|
||||
logger.log('Not setting boshAttemptFirst, address matches.');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
/* global $, config, interfaceConfig */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var configUtil = require('./Util');
|
||||
|
||||
var HttpConfig = {
|
||||
/**
|
||||
* Sends HTTP POST request to specified <tt>endpoint</tt>. In request
|
||||
* the name of the room is included in JSON format:
|
||||
* {
|
||||
* "rooomName": "someroom12345"
|
||||
* }
|
||||
* @param endpoint the name of HTTP endpoint to which HTTP POST request will
|
||||
* be sent.
|
||||
* @param roomName the name of the conference room for which config will be
|
||||
* requested.
|
||||
* @param complete
|
||||
*/
|
||||
obtainConfig: function (endpoint, roomName, complete) {
|
||||
logger.info(
|
||||
"Send config request to " + endpoint + " for room: " + roomName);
|
||||
|
||||
|
||||
$.ajax(
|
||||
endpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({"roomName": roomName}),
|
||||
dataType: 'json',
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
logger.error("Get config error: ", jqXHR, errorThrown);
|
||||
var error = "Get config response status: " + textStatus;
|
||||
complete(false, error);
|
||||
},
|
||||
success: function(data) {
|
||||
try {
|
||||
configUtil.overrideConfigJSON(
|
||||
config, interfaceConfig, data);
|
||||
complete(true);
|
||||
return;
|
||||
} catch (exception) {
|
||||
logger.error("Parse config error: ", exception);
|
||||
complete(false, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = HttpConfig;
|
||||
@@ -1,69 +0,0 @@
|
||||
/* global config, getConfigParamsFromUrl, interfaceConfig, loggingConfig */
|
||||
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var configUtils = require('./Util');
|
||||
var params = {};
|
||||
|
||||
if (typeof getConfigParamsFromUrl === 'function') {
|
||||
params = getConfigParamsFromUrl();
|
||||
}
|
||||
|
||||
var URLProcessor = {
|
||||
setConfigParametersFromUrl: function () {
|
||||
// Convert 'params' to JSON object
|
||||
// We have:
|
||||
// {
|
||||
// "config.disableAudioLevels": false,
|
||||
// "config.channelLastN": -1,
|
||||
// "interfaceConfig.APP_NAME": "Jitsi Meet"
|
||||
// }
|
||||
// We want to have:
|
||||
// {
|
||||
// "config": {
|
||||
// "disableAudioLevels": false,
|
||||
// "channelLastN": -1
|
||||
// },
|
||||
// interfaceConfig: {
|
||||
// APP_NAME: "Jitsi Meet"
|
||||
// }
|
||||
// }
|
||||
var configJSON = {
|
||||
config: {},
|
||||
interfaceConfig: {},
|
||||
loggingConfig: {}
|
||||
};
|
||||
for (var key in params) {
|
||||
if (typeof key !== "string") {
|
||||
logger.warn("Invalid config key: ", key);
|
||||
continue;
|
||||
}
|
||||
var confObj = null, confKey;
|
||||
if (key.indexOf("config.") === 0) {
|
||||
confObj = configJSON.config;
|
||||
confKey = key.substr("config.".length);
|
||||
|
||||
// prevent passing some parameters which can inject scripts
|
||||
if (confKey === 'analyticsScriptUrls'
|
||||
|| confKey === 'callStatsCustomScriptUrl')
|
||||
continue;
|
||||
|
||||
} else if (key.indexOf("interfaceConfig.") === 0) {
|
||||
confObj = configJSON.interfaceConfig;
|
||||
confKey = key.substr("interfaceConfig.".length);
|
||||
} else if (key.indexOf("loggingConfig.") === 0) {
|
||||
confObj = configJSON.loggingConfig;
|
||||
confKey = key.substr("loggingConfig.".length);
|
||||
}
|
||||
|
||||
if (!confObj)
|
||||
continue;
|
||||
|
||||
confObj[confKey] = params[key];
|
||||
}
|
||||
configUtils.overrideConfigJSON(
|
||||
config, interfaceConfig, loggingConfig, configJSON);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = URLProcessor;
|
||||
@@ -1,54 +0,0 @@
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var ConfigUtil = {
|
||||
/**
|
||||
* Method overrides JSON properties in <tt>config</tt> and
|
||||
* <tt>interfaceConfig</tt> Objects with the values from <tt>newConfig</tt>
|
||||
* @param config the config object for which we'll be overriding properties
|
||||
* @param interfaceConfig the interfaceConfig object for which we'll be
|
||||
* overriding properties.
|
||||
* @param loggingConfig the logging config object for which we'll be
|
||||
* overriding properties.
|
||||
* @param newConfig object containing configuration properties. Destination
|
||||
* object is selected based on root property name:
|
||||
* {
|
||||
* config: {
|
||||
* // config.js properties to be
|
||||
* },
|
||||
* interfaceConfig: {
|
||||
* // interface_config.js properties here
|
||||
* },
|
||||
* loggingConfig: {
|
||||
* // logging_config.js properties here
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
overrideConfigJSON: function (config,
|
||||
interfaceConfig, loggingConfig, newConfig) {
|
||||
var configRoot, key, value, confObj;
|
||||
for (configRoot in newConfig) {
|
||||
confObj = null;
|
||||
if (configRoot == "config") {
|
||||
confObj = config;
|
||||
} else if (configRoot == "interfaceConfig") {
|
||||
confObj = interfaceConfig;
|
||||
} else if (configRoot == "loggingConfig") {
|
||||
confObj = loggingConfig;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (key in newConfig[configRoot]) {
|
||||
value = newConfig[configRoot][key];
|
||||
if (confObj[key] && typeof confObj[key] !== typeof value) {
|
||||
logger.log("Overriding a " + configRoot +
|
||||
" property with a property of different type.");
|
||||
}
|
||||
logger.info("Overriding " + key + " with: " + value);
|
||||
confObj[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ConfigUtil;
|
||||
@@ -97,7 +97,7 @@ export default class Controller extends RemoteControlParticipant {
|
||||
* null(the participant has left).
|
||||
*/
|
||||
requestPermissions(userId: string, eventCaptureArea: Object) {
|
||||
if (!this.enabled) {
|
||||
if (!this._enabled) {
|
||||
return Promise.reject(new Error('Remote control is disabled!'));
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export default class Controller extends RemoteControlParticipant {
|
||||
_handleReply(participant: Object, event: Object) {
|
||||
const userId = participant.getId();
|
||||
|
||||
if (this.enabled
|
||||
if (this._enabled
|
||||
&& event.name === REMOTE_CONTROL_EVENT_NAME
|
||||
&& event.type === EVENT_TYPES.permissions
|
||||
&& userId === this._requestedParticipant) {
|
||||
@@ -205,7 +205,7 @@ export default class Controller extends RemoteControlParticipant {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleRemoteControlStoppedEvent(participant: Object, event: Object) {
|
||||
if (this.enabled
|
||||
if (this._enabled
|
||||
&& event.name === REMOTE_CONTROL_EVENT_NAME
|
||||
&& event.type === EVENT_TYPES.stop
|
||||
&& participant.getId() === this._controlledParticipant) {
|
||||
@@ -239,7 +239,7 @@ export default class Controller extends RemoteControlParticipant {
|
||||
* @returns {void}
|
||||
*/
|
||||
resume() {
|
||||
if (!this.enabled || this._isCollectingEvents || !this._area) {
|
||||
if (!this._enabled || this._isCollectingEvents || !this._area) {
|
||||
return;
|
||||
}
|
||||
logger.log('Resuming remote control controller.');
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/* global getConfigParamsFromUrl, config */
|
||||
|
||||
/**
|
||||
* Parses and handles JWT tokens. Sets config.token.
|
||||
*/
|
||||
|
||||
import * as jws from "jws";
|
||||
|
||||
/**
|
||||
* Get the JWT token from the URL.
|
||||
*/
|
||||
let params = getConfigParamsFromUrl("search", true);
|
||||
let jwt = params.jwt;
|
||||
|
||||
/**
|
||||
* Implements a user of conference.
|
||||
*/
|
||||
class User {
|
||||
/**
|
||||
* @param name {string} the name of the user.
|
||||
* @param email {string} the email of the user.
|
||||
* @param avatarUrl {string} the URL for the avatar of the user.
|
||||
*/
|
||||
constructor(name, email, avatarUrl) {
|
||||
this._name = name;
|
||||
this._email = email;
|
||||
this._avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* GETERS START.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the name property
|
||||
*/
|
||||
getName() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email property
|
||||
*/
|
||||
getEmail() {
|
||||
return this._email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the avatar
|
||||
*/
|
||||
getAvatarUrl() {
|
||||
return this._avatarUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* GETERS END.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent the data parsed from the JWT token
|
||||
*/
|
||||
class TokenData{
|
||||
/**
|
||||
* @param {string} the JWT token
|
||||
*/
|
||||
constructor(jwt) {
|
||||
this.isGuest = true;
|
||||
if(!jwt)
|
||||
return;
|
||||
|
||||
this.isGuest = config.enableUserRolesBasedOnToken !== true;
|
||||
|
||||
this.jwt = jwt;
|
||||
|
||||
this._decode();
|
||||
// Use JWT param as token if there is not other token set and if the
|
||||
// iss field is not anonymous. If you want to pass data with JWT token
|
||||
// but you don't want to pass the JWT token for verification the iss
|
||||
// field should be set to "anonymous"
|
||||
if(!config.token && this.payload && this.payload.iss !== "anonymous")
|
||||
config.token = jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the JWT token and sets the decoded data to properties.
|
||||
*/
|
||||
_decode() {
|
||||
this.decodedJWT = jws.decode(jwt);
|
||||
if(!this.decodedJWT || !this.decodedJWT.payload)
|
||||
return;
|
||||
this.payload = this.decodedJWT.payload;
|
||||
if(!this.payload.context)
|
||||
return;
|
||||
this.server = this.payload.context.server;
|
||||
this.group = this.payload.context.group;
|
||||
let callerData = this.payload.context.user;
|
||||
let calleeData = this.payload.context.callee;
|
||||
if(callerData)
|
||||
this.caller = new User(callerData.name, callerData.email,
|
||||
callerData.avatarUrl);
|
||||
if(calleeData)
|
||||
this.callee = new User(calleeData.name, calleeData.email,
|
||||
calleeData.avatarUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the TokenData instance.
|
||||
*/
|
||||
let data = null;
|
||||
|
||||
/**
|
||||
* Returns the data variable. Creates new TokenData instance if <tt>data</tt>
|
||||
* variable is null.
|
||||
*/
|
||||
export default function getTokenData() {
|
||||
if(!data)
|
||||
data = new TokenData(jwt);
|
||||
return data;
|
||||
}
|
||||
22
package.json
@@ -29,30 +29,30 @@
|
||||
"bootstrap": "3.1.1",
|
||||
"es6-iterator": "2.0.1",
|
||||
"es6-symbol": "3.1.1",
|
||||
"i18next": "8.0.0",
|
||||
"i18next": "8.2.1",
|
||||
"i18next-browser-languagedetector": "1.0.1",
|
||||
"i18next-xhr-backend": "1.4.1",
|
||||
"jitsi-meet-logger": "jitsi/jitsi-meet-logger",
|
||||
"jquery": "2.1.4",
|
||||
"jquery-contextmenu": "2.4.3",
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.0",
|
||||
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jquery-ui": "1.10.5",
|
||||
"jssha": "1.5.0",
|
||||
"jws": "3.1.4",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
|
||||
"lodash": "4.17.4",
|
||||
"postis": "2.2.0",
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2",
|
||||
"react-i18next": "3.1.0",
|
||||
"react-i18next": "4.1.0",
|
||||
"react-native": "0.42.3",
|
||||
"react-native-background-timer": "1.0.0",
|
||||
"react-native-immersive": "0.0.4",
|
||||
"react-native-keep-awake": "2.0.3",
|
||||
"react-native-background-timer": "1.0.1",
|
||||
"react-native-immersive": "0.0.5",
|
||||
"react-native-keep-awake": "2.0.4",
|
||||
"react-native-locale-detector": "1.0.1",
|
||||
"react-native-prompt": "1.0.0",
|
||||
"react-native-vector-icons": "4.0.1",
|
||||
"react-native-vector-icons": "4.1.1",
|
||||
"react-native-webrtc": "jitsi/react-native-webrtc",
|
||||
"react-redux": "5.0.4",
|
||||
"redux": "3.6.0",
|
||||
@@ -74,11 +74,11 @@
|
||||
"babel-preset-react": "6.24.1",
|
||||
"babel-preset-stage-1": "6.24.1",
|
||||
"clean-css": "3.4.25",
|
||||
"css-loader": "0.28.0",
|
||||
"css-loader": "0.28.1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-flowtype": "2.30.4",
|
||||
"eslint-plugin-import": "2.2.0",
|
||||
"eslint-plugin-jsdoc": "3.0.2",
|
||||
"eslint-plugin-jsdoc": "3.1.0",
|
||||
"eslint-plugin-react": "6.10.3",
|
||||
"eslint-plugin-react-native": "2.3.2",
|
||||
"expose-loader": "0.7.3",
|
||||
@@ -91,7 +91,7 @@
|
||||
"node-sass": "3.13.1",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "1.2.0",
|
||||
"style-loader": "0.16.1",
|
||||
"style-loader": "0.17.0",
|
||||
"webpack": "1.14.0",
|
||||
"webpack-dev-server": "1.16.3"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { setRoom } from '../base/conference';
|
||||
import { setLocationURL } from '../base/connection';
|
||||
import { setConfig } from '../base/config';
|
||||
import { getDomain, setDomain } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||
@@ -18,95 +18,156 @@ import {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function appInit() {
|
||||
return () => init();
|
||||
return (dispatch: Dispatch<*>, getState: Function) =>
|
||||
init(getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an in-app navigation to a different route. Allows navigation to be
|
||||
* abstracted between the mobile and web versions.
|
||||
* Triggers an in-app navigation to a specific route. Allows navigation to be
|
||||
* abstracted between the mobile/React Native and Web/React applications.
|
||||
*
|
||||
* @param {(string|undefined)} uri - The URI to which to navigate. It may be a
|
||||
* full URL with an http(s) scheme, a full or partial URI with the app-specific
|
||||
* full URL with an HTTP(S) scheme, a full or partial URI with the app-specific
|
||||
* sheme, or a mere room name.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function appNavigate(uri) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const oldDomain = getDomain(state);
|
||||
export function appNavigate(uri: ?string) {
|
||||
return (dispatch: Dispatch<*>, getState: Function) =>
|
||||
_appNavigateToOptionalLocation(
|
||||
dispatch, getState,
|
||||
_parseURIString(uri));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { domain, room } = _parseURIString(uri);
|
||||
/**
|
||||
* Triggers an in-app navigation to a specific location URI.
|
||||
*
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Function} getState - The redux function that gets/retrieves the redux
|
||||
* state.
|
||||
* @param {Object} newLocation - The location URI to navigate to. The value
|
||||
* cannot be undefined and is assumed to have all properties such as
|
||||
* {@code host} and {@code room} defined values.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appNavigateToMandatoryLocation(
|
||||
dispatch: Dispatch<*>, getState: Function,
|
||||
newLocation: Object) {
|
||||
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
|
||||
// currently in a conference and ask her if she wants to close the
|
||||
// current conference and start a new one with the new room name or
|
||||
// domain.
|
||||
|
||||
// If the specified URI does not identify a domain, use the app's
|
||||
// default.
|
||||
if (typeof domain === 'undefined') {
|
||||
domain
|
||||
= _parseURIString(state['features/app'].app._getDefaultURL())
|
||||
.domain;
|
||||
const oldLocationURL = getState()['features/base/connection'].locationURL;
|
||||
const oldHost = oldLocationURL ? oldLocationURL.host : undefined;
|
||||
const newHost = newLocation.host;
|
||||
|
||||
if (oldHost === newHost) {
|
||||
dispatchSetLocationURL();
|
||||
dispatchSetRoomAndNavigate();
|
||||
} else {
|
||||
// If the host has changed, we need to load the config of the new host
|
||||
// and set it, and only after that we can navigate to a different route.
|
||||
_loadConfig(newLocation)
|
||||
.then(
|
||||
config => configLoaded(/* err */ undefined, config),
|
||||
err => configLoaded(err, /* config */ undefined))
|
||||
.then(dispatchSetRoomAndNavigate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that an attempt to load the config(uration) of domain has
|
||||
* completed.
|
||||
*
|
||||
* @param {string|undefined} err - If the loading has failed, the error
|
||||
* detailing the cause of the failure.
|
||||
* @param {Object|undefined} config - If the loading has succeeded, the
|
||||
* loaded config(uration).
|
||||
* @returns {void}
|
||||
*/
|
||||
function configLoaded(err, config) {
|
||||
if (err) {
|
||||
// XXX The failure could be, for example, because of a
|
||||
// certificate-related error. In which case the connection will
|
||||
// fail later in Strophe anyway even if we use the default
|
||||
// config here.
|
||||
|
||||
// The function loadConfig will log the err.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
|
||||
// currently in a conference and ask her if she wants to close the
|
||||
// current conference and start a new one with the new room name or
|
||||
// domain.
|
||||
dispatchSetLocationURL();
|
||||
dispatch(setConfig(config));
|
||||
}
|
||||
|
||||
if (typeof domain === 'undefined' || oldDomain === domain) {
|
||||
dispatchSetRoomAndNavigate();
|
||||
} else if (oldDomain !== domain) {
|
||||
// Update domain without waiting for config to be loaded to prevent
|
||||
// race conditions when we will start to load config multiple times.
|
||||
dispatch(setDomain(domain));
|
||||
/**
|
||||
* Dispatches {@link setLocationURL} in the redux store.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function dispatchSetLocationURL() {
|
||||
dispatch(
|
||||
setLocationURL(
|
||||
new URL(
|
||||
(newLocation.protocol || 'https:')
|
||||
|
||||
// If domain has changed, we need to load the config of the new
|
||||
// domain and set it, and only after that we can navigate to a
|
||||
// different route.
|
||||
loadConfig(`https://${domain}`)
|
||||
.then(
|
||||
config => configLoaded(/* err */ undefined, config),
|
||||
err => configLoaded(err, /* config */ undefined))
|
||||
.then(dispatchSetRoomAndNavigate);
|
||||
// TODO userinfo
|
||||
|
||||
+ newLocation.host
|
||||
+ (newLocation.pathname || '/')
|
||||
+ newLocation.search
|
||||
+ newLocation.hash)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches {@link _setRoomAndNavigate} in the redux store.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function dispatchSetRoomAndNavigate() {
|
||||
dispatch(_setRoomAndNavigate(newLocation.room));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an in-app navigation to a specific or undefined location (URI).
|
||||
*
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Function} getState - The redux function that gets/retrieves the redux
|
||||
* state.
|
||||
* @param {Object} location - The location (URI) to navigate to. The value may
|
||||
* be undefined.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appNavigateToOptionalLocation(
|
||||
dispatch: Dispatch<*>, getState: Function,
|
||||
location: Object) {
|
||||
// If the specified location (URI) does not identify a host, use the app's
|
||||
// default.
|
||||
if (!location || !location.host) {
|
||||
const defaultLocation
|
||||
= _parseURIString(getState()['features/app'].app._getDefaultURL());
|
||||
|
||||
if (location) {
|
||||
location.host = defaultLocation.host;
|
||||
|
||||
// FIXME Turn location's host, hostname, and port properties into
|
||||
// setters in order to reduce the risks of inconsistent state.
|
||||
location.hostname = defaultLocation.hostname;
|
||||
location.port = defaultLocation.port;
|
||||
location.protocol = defaultLocation.protocol;
|
||||
} else {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
location = defaultLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that an attempt to load the config(uration) of domain has
|
||||
* completed.
|
||||
*
|
||||
* @param {string|undefined} err - If the loading has failed, the error
|
||||
* detailing the cause of the failure.
|
||||
* @param {Object|undefined} config - If the loading has succeeded, the
|
||||
* loaded config(uration).
|
||||
* @returns {void}
|
||||
*/
|
||||
function configLoaded(err, config) {
|
||||
if (err) {
|
||||
// XXX The failure could be, for example, because of a
|
||||
// certificate-related error. In which case the connection will
|
||||
// fail later in Strophe anyway even if we use the default
|
||||
// config here.
|
||||
if (!location.protocol) {
|
||||
location.protocol = 'https:';
|
||||
}
|
||||
|
||||
// The function loadConfig will log the err.
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setConfig(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches _setRoomAndNavigate in the Redux store.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function dispatchSetRoomAndNavigate() {
|
||||
// If both domain and room vars became undefined, that means we're
|
||||
// actually dealing with just room name and not with URL.
|
||||
dispatch(
|
||||
_setRoomAndNavigate(
|
||||
typeof room === 'undefined' && typeof domain === 'undefined'
|
||||
? uri
|
||||
: room));
|
||||
}
|
||||
};
|
||||
_appNavigateToMandatoryLocation(dispatch, getState, location);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,6 +202,27 @@ export function appWillUnmount(app) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config.js from a specific host.
|
||||
*
|
||||
* @param {Object} location - The loction URI which specifies the host to load
|
||||
* the config.js from.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
function _loadConfig(location: Object) {
|
||||
let protocol = location.protocol.toLowerCase();
|
||||
|
||||
// The React Native app supports an app-specific scheme which is sure to not
|
||||
// be supported by fetch (or whatever loadConfig utilizes).
|
||||
if (protocol !== 'http:' && protocol !== 'https:') {
|
||||
protocol = 'https:';
|
||||
}
|
||||
|
||||
// TDOO userinfo
|
||||
|
||||
return loadConfig(protocol + location.host + (location.contextRoot || '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a route in accord with a specific Redux state.
|
||||
*
|
||||
|
||||
@@ -268,13 +268,13 @@ export class AbstractApp extends Component {
|
||||
// By default, open the domain configured in the configuration file
|
||||
// which may be the domain at which the whole server infrastructure is
|
||||
// deployed.
|
||||
const config = this.props.config;
|
||||
const { config } = this.props;
|
||||
|
||||
if (typeof config === 'object') {
|
||||
const hosts = config.hosts;
|
||||
const { hosts } = config;
|
||||
|
||||
if (typeof hosts === 'object') {
|
||||
const domain = hosts.domain;
|
||||
const { domain } = hosts;
|
||||
|
||||
if (domain) {
|
||||
return `https://${domain}`;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { appInit } from '../actions';
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
import { getLocationContextRoot } from '../functions';
|
||||
|
||||
import '../../room-lock';
|
||||
|
||||
@@ -65,13 +66,7 @@ export class App extends AbstractApp {
|
||||
* @returns {string} The context root of window.location i.e. this Web App.
|
||||
*/
|
||||
_getWindowLocationContextRoot() {
|
||||
const pathname = this.getWindowLocation().pathname;
|
||||
const contextRootEndIndex = pathname.lastIndexOf('/');
|
||||
|
||||
return (
|
||||
contextRootEndIndex === -1
|
||||
? '/'
|
||||
: pathname.substring(0, contextRootEndIndex + 1));
|
||||
return getLocationContextRoot(this.getWindowLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -112,39 +112,21 @@ function _fixURIStringScheme(uri) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets room name and domain from URL object.
|
||||
* Gets the (Web application) context root defined by a specific location (URI).
|
||||
*
|
||||
* @param {URL} url - URL object.
|
||||
* @private
|
||||
* @returns {{
|
||||
* domain: (string|undefined),
|
||||
* room: (string|undefined)
|
||||
* }}
|
||||
* @param {Object} location - The location (URI) which defines the (Web
|
||||
* application) context root.
|
||||
* @returns {string} - The (Web application) context root defined by the
|
||||
* specified {@code location} (URI).
|
||||
*/
|
||||
function _getRoomAndDomainFromURLObject(url) {
|
||||
let domain;
|
||||
let room;
|
||||
export function getLocationContextRoot(location: Object) {
|
||||
const pathname = location.pathname;
|
||||
const contextRootEndIndex = pathname.lastIndexOf('/');
|
||||
|
||||
if (url) {
|
||||
domain = url.host;
|
||||
|
||||
// The room (name) is the last component of pathname.
|
||||
room = url.pathname;
|
||||
room = room.substring(room.lastIndexOf('/') + 1);
|
||||
|
||||
// Convert empty string to undefined to simplify checks.
|
||||
if (room === '') {
|
||||
room = undefined;
|
||||
}
|
||||
if (domain === '') {
|
||||
domain = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
domain,
|
||||
room
|
||||
};
|
||||
return (
|
||||
contextRootEndIndex === -1
|
||||
? '/'
|
||||
: pathname.substring(0, contextRootEndIndex + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,12 +142,113 @@ export function _getRouteToRender(stateOrGetState) {
|
||||
= typeof stateOrGetState === 'function'
|
||||
? stateOrGetState()
|
||||
: stateOrGetState;
|
||||
const room = state['features/base/conference'].room;
|
||||
const { room } = state['features/base/conference'];
|
||||
const component = isRoomValid(room) ? Conference : WelcomePage;
|
||||
|
||||
return RouteRegistry.getRouteByComponent(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a specific URI string into an object with the well-known properties of
|
||||
* the {@link Location} and/or {@link URL} interfaces implemented by Web
|
||||
* browsers. The parsing attempts to be in accord with IETF's RFC 3986.
|
||||
*
|
||||
* @param {string} str - The URI string to parse.
|
||||
* @returns {{
|
||||
* hash: string,
|
||||
* host: (string|undefined),
|
||||
* hostname: (string|undefined),
|
||||
* pathname: string,
|
||||
* port: (string|undefined),
|
||||
* protocol: (string|undefined),
|
||||
* search: string
|
||||
* }}
|
||||
*/
|
||||
function _parseStandardURIString(str: string) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
const obj = {};
|
||||
|
||||
let regex;
|
||||
let match;
|
||||
|
||||
// protocol
|
||||
regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
if (match) {
|
||||
obj.protocol = match[1].toLowerCase();
|
||||
str = str.substring(regex.lastIndex);
|
||||
}
|
||||
|
||||
// authority
|
||||
regex = new RegExp(`^${_URI_AUTHORITY_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
if (match) {
|
||||
let authority = match[1].substring(/* // */ 2);
|
||||
|
||||
str = str.substring(regex.lastIndex);
|
||||
|
||||
// userinfo
|
||||
const userinfoEndIndex = authority.indexOf('@');
|
||||
|
||||
if (userinfoEndIndex !== -1) {
|
||||
authority = authority.substring(userinfoEndIndex + 1);
|
||||
}
|
||||
|
||||
obj.host = authority;
|
||||
|
||||
// port
|
||||
const portBeginIndex = authority.lastIndexOf(':');
|
||||
|
||||
if (portBeginIndex !== -1) {
|
||||
obj.port = authority.substring(portBeginIndex + 1);
|
||||
authority = authority.substring(0, portBeginIndex);
|
||||
}
|
||||
|
||||
// hostname
|
||||
obj.hostname = authority;
|
||||
}
|
||||
|
||||
// pathname
|
||||
regex = new RegExp(`^${_URI_PATH_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
|
||||
let pathname;
|
||||
|
||||
if (match) {
|
||||
pathname = match[1];
|
||||
str = str.substring(regex.lastIndex);
|
||||
}
|
||||
if (pathname) {
|
||||
if (!pathname.startsWith('/')) {
|
||||
pathname = `/${pathname}`;
|
||||
}
|
||||
} else {
|
||||
pathname = '/';
|
||||
}
|
||||
obj.pathname = pathname;
|
||||
|
||||
// query
|
||||
if (str.startsWith('?')) {
|
||||
let hashBeginIndex = str.indexOf('#', 1);
|
||||
|
||||
if (hashBeginIndex === -1) {
|
||||
hashBeginIndex = str.length;
|
||||
}
|
||||
obj.search = str.substring(0, hashBeginIndex);
|
||||
str = str.substring(hashBeginIndex);
|
||||
} else {
|
||||
obj.search = ''; // Google Chrome
|
||||
}
|
||||
|
||||
// fragment
|
||||
obj.hash = str.startsWith('#') ? str : '';
|
||||
|
||||
/* eslint-enable no-param-reassign */
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a specific URI which (supposedly) references a Jitsi Meet resource
|
||||
* (location).
|
||||
@@ -173,68 +256,28 @@ export function _getRouteToRender(stateOrGetState) {
|
||||
* @param {(string|undefined)} uri - The URI to parse which (supposedly)
|
||||
* references a Jitsi Meet resource (location).
|
||||
* @returns {{
|
||||
* domain: (string|undefined),
|
||||
* room: (string|undefined)
|
||||
* }}
|
||||
*/
|
||||
export function _parseURIString(uri) {
|
||||
let obj;
|
||||
|
||||
if (typeof uri === 'string') {
|
||||
let str = uri;
|
||||
|
||||
str = _fixURIStringScheme(str);
|
||||
str = _fixURIStringHierPart(str);
|
||||
|
||||
obj = {};
|
||||
|
||||
let regex;
|
||||
let match;
|
||||
|
||||
// protocol
|
||||
regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
if (match) {
|
||||
obj.protocol = match[1].toLowerCase();
|
||||
str = str.substring(regex.lastIndex);
|
||||
}
|
||||
|
||||
// authority
|
||||
regex = new RegExp(`^${_URI_AUTHORITY_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
if (match) {
|
||||
let authority = match[1].substring(/* // */ 2);
|
||||
|
||||
str = str.substring(regex.lastIndex);
|
||||
|
||||
// userinfo
|
||||
const userinfoEndIndex = authority.indexOf('@');
|
||||
|
||||
if (userinfoEndIndex !== -1) {
|
||||
authority = authority.substring(userinfoEndIndex + 1);
|
||||
}
|
||||
|
||||
obj.host = authority;
|
||||
|
||||
// port
|
||||
const portBeginIndex = authority.lastIndexOf(':');
|
||||
|
||||
if (portBeginIndex !== -1) {
|
||||
obj.port = authority.substring(portBeginIndex + 1);
|
||||
authority = authority.substring(0, portBeginIndex);
|
||||
}
|
||||
|
||||
obj.hostname = authority;
|
||||
}
|
||||
|
||||
// pathname
|
||||
regex = new RegExp(`^${_URI_PATH_PATTERN}`, 'gi');
|
||||
match = regex.exec(str);
|
||||
if (match) {
|
||||
obj.pathname = match[1] || '/';
|
||||
str = str.substring(regex.lastIndex);
|
||||
}
|
||||
export function _parseURIString(uri: ?string) {
|
||||
if (typeof uri !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return _getRoomAndDomainFromURLObject(obj);
|
||||
const obj
|
||||
= _parseStandardURIString(
|
||||
_fixURIStringHierPart(_fixURIStringScheme(uri)));
|
||||
|
||||
// Add the properties that are specific to a Jitsi Meet resource (location)
|
||||
// such as contextRoot, room:
|
||||
|
||||
// contextRoot
|
||||
obj.contextRoot = getLocationContextRoot(obj);
|
||||
|
||||
// The room (name) is the last component of pathname.
|
||||
const { pathname } = obj;
|
||||
|
||||
obj.room = pathname.substring(pathname.lastIndexOf('/') + 1) || undefined;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { WelcomePage } from '../welcome';
|
||||
|
||||
import KeyboardShortcut
|
||||
from '../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import getTokenData from '../../../modules/tokendata/TokenData';
|
||||
import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -79,7 +78,7 @@ const _INTERCEPT_COMPONENT_RULES = [
|
||||
}
|
||||
];
|
||||
|
||||
export { _parseURIString } from './functions.native';
|
||||
export { getLocationContextRoot, _parseURIString } from './functions.native';
|
||||
|
||||
/**
|
||||
* Determines which route is to be rendered in order to depict a specific Redux
|
||||
@@ -111,18 +110,20 @@ export function _getRouteToRender(stateOrGetState: Object | Function) {
|
||||
* Temporary solution. Later we'll get rid of global APP and set its properties
|
||||
* in redux store.
|
||||
*
|
||||
* @param {Object} state - Snapshot of current state of redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function init() {
|
||||
export function init(state: Object) {
|
||||
_initLogging();
|
||||
|
||||
APP.keyboardshortcut = KeyboardShortcut;
|
||||
APP.tokenData = getTokenData();
|
||||
|
||||
const { jwt } = state['features/jwt'];
|
||||
|
||||
// Force enable the API if jwt token is passed because most probably
|
||||
// jitsi meet is displayed inside of wrapper that will need to communicate
|
||||
// with jitsi meet.
|
||||
APP.API.init(APP.tokenData.jwt ? { forceEnable: true } : undefined);
|
||||
APP.API.init(jwt ? { forceEnable: true } : undefined);
|
||||
|
||||
APP.translation.init();
|
||||
}
|
||||
|
||||
@@ -3,4 +3,9 @@ export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
// We need to import the jwt module in order to register the reducer and
|
||||
// middleware, because the module is not used outside of this feature.
|
||||
import '../jwt';
|
||||
|
||||
import './reducer';
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* failed.
|
||||
* The type of (redux) action which signals that a specific conference failed.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_FAILED,
|
||||
@@ -13,8 +12,8 @@ import { Symbol } from '../react';
|
||||
export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* been joined.
|
||||
* The type of (redux) action which signals that a specific conference was
|
||||
* joined.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_JOINED,
|
||||
@@ -24,8 +23,7 @@ export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
|
||||
export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* been left.
|
||||
* The type of (redux) action which signals that a specific conference was left.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_LEFT,
|
||||
@@ -35,7 +33,7 @@ export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
|
||||
export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference will be
|
||||
* The type of (redux) action which signals that a specific conference will be
|
||||
* joined.
|
||||
*
|
||||
* {
|
||||
@@ -46,7 +44,7 @@ export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
|
||||
export const CONFERENCE_WILL_JOIN = Symbol('CONFERENCE_WILL_JOIN');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference will be
|
||||
* The type of (redux) action which signals that a specific conference will be
|
||||
* left.
|
||||
*
|
||||
* {
|
||||
@@ -57,8 +55,8 @@ export const CONFERENCE_WILL_JOIN = Symbol('CONFERENCE_WILL_JOIN');
|
||||
export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the lock state of a specific
|
||||
* <tt>JitsiConference</tt> changed.
|
||||
* The type of (redux) action which signals that the lock state of a specific
|
||||
* {@code JitsiConference} changed.
|
||||
*
|
||||
* {
|
||||
* type: LOCK_STATE_CHANGED,
|
||||
@@ -69,7 +67,7 @@ export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
|
||||
export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which sets the audio-only flag for the current
|
||||
* The type of (redux) action which sets the audio-only flag for the current
|
||||
* conference.
|
||||
*
|
||||
* {
|
||||
@@ -80,8 +78,8 @@ export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
|
||||
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
|
||||
|
||||
/**
|
||||
* The type of redux action which signals that video will be muted because the
|
||||
* audio-only mode was enabled / disabled.
|
||||
* The type of (redux) action which signals that video will be muted because the
|
||||
* audio-only mode was enabled/disabled.
|
||||
*
|
||||
* {
|
||||
* type: _SET_AUDIO_ONLY_VIDEO_MUTED,
|
||||
@@ -105,7 +103,7 @@ export const _SET_AUDIO_ONLY_VIDEO_MUTED
|
||||
export const SET_LARGE_VIDEO_HD_STATUS = Symbol('SET_LARGE_VIDEO_HD_STATUS');
|
||||
|
||||
/**
|
||||
* The type of redux action which sets the video channel's lastN (value).
|
||||
* The type of (redux) action which sets the video channel's lastN (value).
|
||||
*
|
||||
* {
|
||||
* type: SET_LASTN,
|
||||
@@ -115,8 +113,8 @@ export const SET_LARGE_VIDEO_HD_STATUS = Symbol('SET_LARGE_VIDEO_HD_STATUS');
|
||||
export const SET_LASTN = Symbol('SET_LASTN');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which sets the password to join or lock a
|
||||
* specific JitsiConference.
|
||||
* The type of (redux) action which sets the password to join or lock a specific
|
||||
* {@code JitsiConference}.
|
||||
*
|
||||
* {
|
||||
* type: SET_PASSWORD,
|
||||
@@ -128,8 +126,8 @@ export const SET_LASTN = Symbol('SET_LASTN');
|
||||
export const SET_PASSWORD = Symbol('SET_PASSWORD');
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that setting a password on a
|
||||
* JitsiConference failed (with an error).
|
||||
* The type of (redux) action which signals that setting a password on a
|
||||
* {@code JitsiConference} failed (with an error).
|
||||
*
|
||||
* {
|
||||
* type: SET_PASSWORD_FAILED,
|
||||
@@ -139,7 +137,7 @@ export const SET_PASSWORD = Symbol('SET_PASSWORD');
|
||||
export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which sets the name of the room of the
|
||||
* The type of (redux) action which sets the name of the room of the
|
||||
* conference to be joined.
|
||||
*
|
||||
* {
|
||||
|
||||
@@ -183,9 +183,9 @@ export function conferenceJoined(conference) {
|
||||
* @param {JitsiConference} conference - The JitsiConference instance which was
|
||||
* left by the local participant.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
*/
|
||||
export function conferenceLeft(conference) {
|
||||
return {
|
||||
@@ -202,9 +202,9 @@ export function conferenceLeft(conference) {
|
||||
* @param {string} room - The room (name) which identifies the conference the
|
||||
* local participant will (try to) join.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_WILL_JOIN,
|
||||
* room: string
|
||||
* }}
|
||||
* type: CONFERENCE_WILL_JOIN,
|
||||
* room: string
|
||||
* }}
|
||||
*/
|
||||
function _conferenceWillJoin(room) {
|
||||
return {
|
||||
@@ -222,9 +222,9 @@ function _conferenceWillJoin(room) {
|
||||
* @param {JitsiConference} conference - The JitsiConference instance which will
|
||||
* be left by the local participant.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
*/
|
||||
export function conferenceWillLeave(conference) {
|
||||
return {
|
||||
|
||||
@@ -242,10 +242,7 @@ function _lockStateChanged(state, action) {
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setAudioOnly(state, action) {
|
||||
return assign(state, {
|
||||
audioOnly: action.audioOnly,
|
||||
isLargeVideoHD: action.audioOnly ? false : state.isLargeVideoHD
|
||||
});
|
||||
return set(state, 'audioOnly', action.audioOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
247
react/features/base/config/functions.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/* @flow */
|
||||
|
||||
import JSSHA from 'jssha';
|
||||
|
||||
import parseURLParams from './parseURLParams';
|
||||
|
||||
declare var $: Object;
|
||||
|
||||
/**
|
||||
* The config keys to ignore because, for example, their values identify scripts
|
||||
* and it is not desireable to inject these through URL params.
|
||||
*
|
||||
* @private
|
||||
* @type Array
|
||||
*/
|
||||
const _KEYS_TO_IGNORE = [
|
||||
'analyticsScriptUrls',
|
||||
'callStatsCustomScriptUrl'
|
||||
];
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
// 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
|
||||
// want all functions to be bundled in do_external_connect.
|
||||
export { default as getRoomName } from './getRoomName';
|
||||
export { parseURLParams };
|
||||
|
||||
/* eslint-disable no-shadow */
|
||||
|
||||
/**
|
||||
* Looks for a list of possible BOSH addresses in {@code config.boshList} and
|
||||
* sets the value of {@code config.bosh} based on that list and
|
||||
* {@code roomName}.
|
||||
*
|
||||
* @param {Object} config - The configuration object.
|
||||
* @param {string} roomName - The name of the room/conference.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function chooseBOSHAddress(config: Object, roomName: string) {
|
||||
if (!roomName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { boshList } = config;
|
||||
|
||||
if (!boshList || !Array.isArray(boshList) || !boshList.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This implements the actual choice of an entry in the list based on
|
||||
// roomName. Please consider the implications for existing deployments
|
||||
// before introducing changes.
|
||||
const hash = (new JSSHA(roomName, 'TEXT')).getHash('SHA-1', 'HEX');
|
||||
const n = parseInt(hash.substr(-6), 16);
|
||||
let idx = n % boshList.length;
|
||||
|
||||
config.bosh = boshList[idx];
|
||||
logger.log(`Setting config.bosh to ${config.bosh} (idx=${idx})`);
|
||||
|
||||
const { boshAttemptFirstList } = config;
|
||||
|
||||
if (boshAttemptFirstList
|
||||
&& Array.isArray(boshAttemptFirstList)
|
||||
&& boshAttemptFirstList.length > 0) {
|
||||
idx = n % boshAttemptFirstList.length;
|
||||
|
||||
const attemptFirstAddress = boshAttemptFirstList[idx];
|
||||
|
||||
if (attemptFirstAddress === config.bosh) {
|
||||
logger.log('Not setting config.boshAttemptFirst, address matches.');
|
||||
} else {
|
||||
config.boshAttemptFirst = attemptFirstAddress;
|
||||
logger.log(
|
||||
`Setting config.boshAttemptFirst=${attemptFirstAddress} (idx=${
|
||||
idx})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable no-shadow */
|
||||
|
||||
/**
|
||||
* Sends HTTP POST request to specified <tt>endpoint</tt>. 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}
|
||||
*/
|
||||
export 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 */
|
||||
|
||||
/**
|
||||
* Overrides JSON properties in {@code config} and
|
||||
* {@code interfaceConfig} Objects with the values from {@code newConfig}.
|
||||
*
|
||||
* @param {Object} config - The config Object in which we'll be overriding
|
||||
* properties.
|
||||
* @param {Object} interfaceConfig - The interfaceConfig Object in which we'll
|
||||
* be overriding properties.
|
||||
* @param {Object} loggingConfig - The loggingConfig Object in which we'll be
|
||||
* overriding properties.
|
||||
* @param {Object} json - Object containing configuration properties.
|
||||
* Destination object is selected based on root property name:
|
||||
* {
|
||||
* config: {
|
||||
* // config.js properties here
|
||||
* },
|
||||
* interfaceConfig: {
|
||||
* // interface_config.js properties here
|
||||
* },
|
||||
* loggingConfig: {
|
||||
* // logging_config.js properties here
|
||||
* }
|
||||
* }.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function overrideConfigJSON(
|
||||
config: Object, interfaceConfig: Object, loggingConfig: Object,
|
||||
json: Object) {
|
||||
for (const configName of Object.keys(json)) {
|
||||
let configObj;
|
||||
|
||||
if (configName === 'config') {
|
||||
configObj = config;
|
||||
} else if (configName === 'interfaceConfig') {
|
||||
configObj = interfaceConfig;
|
||||
} else if (configName === 'loggingConfig') {
|
||||
configObj = loggingConfig;
|
||||
}
|
||||
if (configObj) {
|
||||
const configJSON = json[configName];
|
||||
|
||||
for (const key of Object.keys(configJSON)) {
|
||||
const oldValue = configObj[key];
|
||||
const newValue = configJSON[key];
|
||||
|
||||
if (oldValue && typeof oldValue !== typeof newValue) {
|
||||
logger.log(
|
||||
`Overriding a ${configName
|
||||
} property with a property of different type.`);
|
||||
}
|
||||
logger.info(`Overriding ${key} with: ${newValue}`);
|
||||
configObj[key] = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable max-params, no-shadow */
|
||||
|
||||
/**
|
||||
* Converts 'URL_PARAMS' to JSON object.
|
||||
* We have:
|
||||
* {
|
||||
* "config.disableAudioLevels": false,
|
||||
* "config.channelLastN": -1,
|
||||
* "interfaceConfig.APP_NAME": "Jitsi Meet"
|
||||
* }.
|
||||
* We want to have:
|
||||
* {
|
||||
* "config": {
|
||||
* "disableAudioLevels": false,
|
||||
* "channelLastN": -1
|
||||
* },
|
||||
* interfaceConfig: {
|
||||
* "APP_NAME": "Jitsi Meet"
|
||||
* }
|
||||
* }.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setConfigFromURLParams() {
|
||||
const params = parseURLParams(window.location);
|
||||
|
||||
const { config, interfaceConfig, loggingConfig } = window;
|
||||
const json = {};
|
||||
|
||||
// TODO We're still in the middle ground between old Web with config,
|
||||
// interfaceConfig, and loggingConfig used via global variables and new Web
|
||||
// and mobile reading the respective values from the redux store. On React
|
||||
// Native there's no interfaceConfig at all yet and loggingConfig is not
|
||||
// loaded but there's a default value in the redux store.
|
||||
config && (json.config = {});
|
||||
interfaceConfig && (json.interfaceConfig = {});
|
||||
loggingConfig && (json.loggingConfig = {});
|
||||
|
||||
for (const param of Object.keys(params)) {
|
||||
const objEnd = param.indexOf('.');
|
||||
|
||||
if (objEnd !== -1) {
|
||||
const obj = param.substring(0, objEnd);
|
||||
|
||||
if (json.hasOwnProperty(obj)) {
|
||||
const key = param.substring(objEnd + 1);
|
||||
|
||||
// Prevent passing some parameters which can inject scripts.
|
||||
if (key && _KEYS_TO_IGNORE.indexOf(key) === -1) {
|
||||
json[obj][key] = params[param];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overrideConfigJSON(config, interfaceConfig, loggingConfig, json);
|
||||
}
|
||||
29
react/features/base/config/getRoomName.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/* @flow */
|
||||
|
||||
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).toLowerCase()
|
||||
|| undefined;
|
||||
}
|
||||
|
||||
return roomName;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
|
||||
49
react/features/base/config/parseURLParams.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* @flow */
|
||||
|
||||
/**
|
||||
* Parses the parameters from the URL and returns them as a JS object.
|
||||
*
|
||||
* @param {string} url - URL to parse.
|
||||
* @param {boolean} dontParse - If false or undefined some transformations
|
||||
* (for parsing the value as JSON) are going to be executed.
|
||||
* @param {string} source - Values - "hash"/"search" if "search" the parameters
|
||||
* will parsed from location.search otherwise from location.hash.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export default function parseURLParams(
|
||||
url: URL,
|
||||
dontParse: boolean = false,
|
||||
source: string = 'hash'): Object {
|
||||
const paramStr = source === 'search' ? url.search : url.hash;
|
||||
const params = {};
|
||||
|
||||
// eslint-disable-next-line newline-per-chained-call
|
||||
paramStr && paramStr.substr(1).split('&').forEach(part => {
|
||||
const param = part.split('=');
|
||||
const key = param[0];
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value;
|
||||
|
||||
try {
|
||||
value = param[1];
|
||||
if (!dontParse) {
|
||||
value
|
||||
= JSON.parse(decodeURIComponent(value).replace(/\\&/, '&'));
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = `Failed to parse URL parameter value: ${String(value)}`;
|
||||
|
||||
console.warn(msg, e);
|
||||
window.onerror && window.onerror(msg, null, null, null, e);
|
||||
|
||||
return;
|
||||
}
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
@@ -58,6 +58,13 @@ function _setConfig(state, action) {
|
||||
// The config of INITIAL_STATE is meant to override the config
|
||||
// downloaded from the Jitsi Meet deployment because the former contains
|
||||
// values that are mandatory.
|
||||
//
|
||||
// FIXME At the time of this writing the hard-coded overriding values
|
||||
// are specific to mobile/React Native but the source code here is
|
||||
// executed on Web/React as well. The latter is not a practical problem
|
||||
// right now because the rest of the Web/React source code does not read
|
||||
// the overridden properties/values, it still relies on the global
|
||||
// variable config.
|
||||
...INITIAL_STATE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,26 +1,46 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Action type to signal that connection has disconnected.
|
||||
* The type of (redux) action which signals that a connection disconnected.
|
||||
*
|
||||
* {
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_DISCONNECTED = Symbol('CONNECTION_DISCONNECTED');
|
||||
|
||||
/**
|
||||
* Action type to signal that have successfully established a connection.
|
||||
* The type of (redux) action which signals that a connection was successfully
|
||||
* established.
|
||||
*
|
||||
* {
|
||||
* type: CONNECTION_ESTABLISHED,
|
||||
* connection: JitsiConnection
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_ESTABLISHED = Symbol('CONNECTION_ESTABLISHED');
|
||||
|
||||
/**
|
||||
* Action type to signal a connection failed.
|
||||
* The type of (redux) action which signals that a connection failed.
|
||||
*
|
||||
* {
|
||||
* type: CONNECTION_FAILED,
|
||||
* connection: JitsiConnection,
|
||||
* error: string,
|
||||
* message: string
|
||||
* }
|
||||
*/
|
||||
export const CONNECTION_FAILED = Symbol('CONNECTION_FAILED');
|
||||
|
||||
/**
|
||||
* Action to signal to change connection domain.
|
||||
* The type of (redux) action which sets the location URL of the application,
|
||||
* connection, conference, etc.
|
||||
*
|
||||
* {
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* type: SET_LOCATION_URL,
|
||||
* locationURL: ?URL
|
||||
* }
|
||||
*/
|
||||
export const SET_DOMAIN = Symbol('SET_DOMAIN');
|
||||
export const SET_LOCATION_URL = Symbol('SET_LOCATION_URL');
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
SET_DOMAIN
|
||||
SET_LOCATION_URL
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -21,11 +21,12 @@ export function connect() {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { options } = state['features/base/connection'];
|
||||
const { issuer, jwt } = state['features/jwt'];
|
||||
const { room } = state['features/base/conference'];
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
options.appId,
|
||||
options.token,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
{
|
||||
...options,
|
||||
bosh:
|
||||
@@ -87,7 +88,7 @@ export function connect() {
|
||||
function _onConnectionFailed(err) {
|
||||
unsubscribe();
|
||||
console.error('CONNECTION FAILED:', err);
|
||||
dispatch(connectionFailed(connection, err, ''));
|
||||
dispatch(connectionFailed(connection, err));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +108,70 @@ export function connect() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been lost.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* }}
|
||||
*/
|
||||
function _connectionDisconnected(connection: Object, message: string) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been established.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||
* established.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: CONNECTION_ESTABLISHED,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
export function connectionEstablished(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_ESTABLISHED,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection could not be created.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
||||
* @param {string} error - Error.
|
||||
* @param {string} message - Error message.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: CONNECTION_FAILED,
|
||||
* connection: JitsiConnection,
|
||||
* error: string,
|
||||
* message: string
|
||||
* }}
|
||||
*/
|
||||
export function connectionFailed(
|
||||
connection: Object,
|
||||
error: string,
|
||||
message: ?string) {
|
||||
return {
|
||||
type: CONNECTION_FAILED,
|
||||
connection,
|
||||
error,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
@@ -115,8 +180,8 @@ export function connect() {
|
||||
export function disconnect() {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const state = getState();
|
||||
const conference = state['features/base/conference'].conference;
|
||||
const connection = state['features/base/connection'].connection;
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { connection } = state['features/base/connection'];
|
||||
|
||||
let promise;
|
||||
|
||||
@@ -143,79 +208,18 @@ export function disconnect() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection domain.
|
||||
* Sets the location URL of the application, connecton, conference, etc.
|
||||
*
|
||||
* @param {string} domain - Domain name.
|
||||
* @param {URL} [locationURL] - The location URL of the application,
|
||||
* connection, conference, etc.
|
||||
* @returns {{
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* }}
|
||||
*/
|
||||
export function setDomain(domain: string) {
|
||||
return {
|
||||
type: SET_DOMAIN,
|
||||
domain
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been lost.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* type: SET_LOCATION_URL,
|
||||
* locationURL: URL
|
||||
* }}
|
||||
*/
|
||||
function _connectionDisconnected(connection, message: string) {
|
||||
export function setLocationURL(locationURL: ?URL) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been established.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||
* established.
|
||||
* @returns {{
|
||||
* type: CONNECTION_ESTABLISHED,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
export function connectionEstablished(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_ESTABLISHED,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection could not be created.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
||||
* @param {string} error - Error.
|
||||
* @param {string} errorMessage - Error message.
|
||||
* @returns {{
|
||||
* type: CONNECTION_FAILED,
|
||||
* connection: JitsiConnection,
|
||||
* error: string,
|
||||
* errorMessage: string
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
export function connectionFailed(
|
||||
connection: Object, error: string, errorMessage: string) {
|
||||
return {
|
||||
type: CONNECTION_FAILED,
|
||||
connection,
|
||||
error,
|
||||
errorMessage
|
||||
type: SET_LOCATION_URL,
|
||||
locationURL
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,11 +8,8 @@ import {
|
||||
WEBRTC_NOT_READY,
|
||||
WEBRTC_NOT_SUPPORTED
|
||||
} from '../lib-jitsi-meet';
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { SET_DOMAIN } from './actionTypes';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
|
||||
@@ -20,7 +17,8 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
export {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
connectionFailed,
|
||||
setLocationURL
|
||||
} from './actions.native.js';
|
||||
|
||||
/**
|
||||
@@ -106,19 +104,3 @@ export function disconnect() {
|
||||
// app.
|
||||
return () => APP.conference.hangup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection domain.
|
||||
*
|
||||
* @param {string} domain - Domain name.
|
||||
* @returns {{
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* }}
|
||||
*/
|
||||
export function setDomain(domain: string) {
|
||||
return {
|
||||
type: SET_DOMAIN,
|
||||
domain
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
/**
|
||||
* Returns current domain.
|
||||
*
|
||||
* @param {(Function|Object)} stateOrGetState - Redux getState() method or Redux
|
||||
* state.
|
||||
* @returns {(string|undefined)}
|
||||
*/
|
||||
export function getDomain(stateOrGetState: Function | Object) {
|
||||
const state
|
||||
= typeof stateOrGetState === 'function'
|
||||
? stateOrGetState()
|
||||
: stateOrGetState;
|
||||
const { options } = state['features/base/connection'];
|
||||
let domain;
|
||||
|
||||
try {
|
||||
domain = options.hosts.domain;
|
||||
} catch (e) {
|
||||
// XXX The value of options or any of the properties descending from it
|
||||
// may be undefined at some point in the execution (e.g. on start).
|
||||
// Instead of multiple checks for the undefined value, we just wrap it
|
||||
// in a try-catch block.
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import { ReducerRegistry, set } from '../redux';
|
||||
import { assign, ReducerRegistry, set } from '../redux';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_ESTABLISHED,
|
||||
SET_DOMAIN
|
||||
SET_LOCATION_URL
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -21,8 +21,8 @@ ReducerRegistry.register(
|
||||
case CONNECTION_ESTABLISHED:
|
||||
return _connectionEstablished(state, action);
|
||||
|
||||
case SET_DOMAIN:
|
||||
return _setDomain(state, action);
|
||||
case SET_LOCATION_URL:
|
||||
return _setLocationURL(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -38,8 +38,10 @@ ReducerRegistry.register(
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _connectionDisconnected(state: Object, action: Object) {
|
||||
if (state.connection === action.connection) {
|
||||
function _connectionDisconnected(
|
||||
state: Object,
|
||||
{ connection }: { connection: Object }) {
|
||||
if (state.connection === connection) {
|
||||
return set(state, 'connection', undefined);
|
||||
}
|
||||
|
||||
@@ -56,13 +58,15 @@ function _connectionDisconnected(state: Object, action: Object) {
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _connectionEstablished(state: Object, action: Object) {
|
||||
return set(state, 'connection', action.connection);
|
||||
function _connectionEstablished(
|
||||
state: Object,
|
||||
{ connection }: { connection: Object }) {
|
||||
return set(state, 'connection', connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs options to be passed to the constructor of JitsiConnection based
|
||||
* on a specific domain.
|
||||
* Constructs options to be passed to the constructor of {@code JitsiConnection}
|
||||
* based on a specific domain.
|
||||
*
|
||||
* @param {string} domain - The domain with which the returned options are to be
|
||||
* populated.
|
||||
@@ -105,20 +109,20 @@ function _constructOptions(domain: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_DOMAIN of the feature base/connection.
|
||||
* Reduces a specific redux action {@link SET_LOCATION_URL} of the feature
|
||||
* base/connection.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/connection.
|
||||
* @param {Action} action - The Redux action SET_DOMAIN to reduce.
|
||||
* @param {Object} state - The redux state of the feature base/connection.
|
||||
* @param {Action} action - The redux action {@code SET_LOCATION_URL} to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setDomain(state: Object, action: Object) {
|
||||
return {
|
||||
...state,
|
||||
options: {
|
||||
...state.options,
|
||||
..._constructOptions(action.domain)
|
||||
}
|
||||
};
|
||||
function _setLocationURL(
|
||||
state: Object,
|
||||
{ locationURL }: { locationURL: ?URL }) {
|
||||
return assign(state, {
|
||||
locationURL,
|
||||
options: locationURL ? _constructOptions(locationURL.host) : undefined
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ class Dialog extends AbstractDialog {
|
||||
appearance = 'primary'
|
||||
form = 'modal-dialog-form'
|
||||
id = 'modal-dialog-ok-button'
|
||||
isDisabled = { this.props.okDisabled }
|
||||
onClick = { this._onSubmit }>
|
||||
{ this.props.t(this.props.okTitleKey || 'dialog.Ok') }
|
||||
</AKButton>
|
||||
|
||||
@@ -20,14 +20,14 @@ import './native';
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
|
||||
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
|
||||
// of) the project jitsi-meet-react.
|
||||
// of) the project jitsi-meet.
|
||||
//
|
||||
// TODO The Web support implemented by the jitsi-meet project explicitly uses
|
||||
// the library lib-jitsi-meet as a binary and keeps it out of the application
|
||||
// bundle. The mobile support implemented by the jitsi-meet-react project did
|
||||
// not get to keeping the lib-jitsi-meet library out of the application bundle
|
||||
// and even used it from source. As an intermediate step, start using the
|
||||
// library lib-jitsi-meet as a binary on mobile at the time of this writing. In
|
||||
// the future, implement not packaging it in the application bundle.
|
||||
// bundle. The mobile support implemented by the jitsi-meet project did not get
|
||||
// to keeping the lib-jitsi-meet library out of the application bundle and even
|
||||
// used it from source. As an intermediate step, start using the library
|
||||
// lib-jitsi-meet as a binary on mobile at the time of this writing. In the
|
||||
// future, implement not packaging it in the application bundle.
|
||||
import JitsiMeetJS from 'lib-jitsi-meet/lib-jitsi-meet.min';
|
||||
export { JitsiMeetJS as default };
|
||||
|
||||
@@ -21,15 +21,9 @@ export function disposeLib() {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
dispatch({ type: LIB_WILL_DISPOSE });
|
||||
|
||||
// XXX We're wrapping it with Promise because:
|
||||
// a) to be better aligned with initLib() method which is async;
|
||||
// b) as currently there is no implementation for it in lib-jitsi-meet
|
||||
// and there is a big chance it will be async.
|
||||
// TODO Currently, lib-jitsi-meet doesn't have the functionality to
|
||||
// dispose itself.
|
||||
return (
|
||||
Promise.resolve()
|
||||
.then(() => dispatch({ type: LIB_DID_DISPOSE })));
|
||||
dispatch({ type: LIB_DID_DISPOSE });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { loadScript } from '../../base/util';
|
||||
/* @flow */
|
||||
|
||||
import URLProcessor from '../../../../modules/config/URLProcessor';
|
||||
import { setConfigFromURLParams } from '../../base/config';
|
||||
import { loadScript } from '../../base/util';
|
||||
|
||||
import JitsiMeetJS from './_';
|
||||
|
||||
@@ -8,6 +9,28 @@ declare var APP: Object;
|
||||
|
||||
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
/**
|
||||
* Creates a JitsiLocalTrack model from the given device id.
|
||||
*
|
||||
* @param {string} type - The media type of track being created. Expected values
|
||||
* are "video" or "audio".
|
||||
* @param {string} deviceId - The id of the target media source.
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*/
|
||||
export function createLocalTrack(type: string, deviceId: string) {
|
||||
return (
|
||||
JitsiMeetJS.createLocalTracks({
|
||||
cameraDeviceId: deviceId,
|
||||
devices: [ type ],
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
firefox_fake_device:
|
||||
window.config && window.config.firefox_fake_device,
|
||||
micDeviceId: deviceId
|
||||
})
|
||||
.then(([ jitsiLocalTrack ]) => jitsiLocalTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a specific JitsiConnectionErrors instance indicates a
|
||||
* fatal JitsiConnection error.
|
||||
@@ -29,63 +52,50 @@ export function isFatalJitsiConnectionError(error: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config.js file from remote server.
|
||||
* Loads config.js from a specific remote server.
|
||||
*
|
||||
* @param {string} host - Host where config.js is hosted.
|
||||
* @param {string} path='/config.js' - Relative pah to config.js file.
|
||||
* @param {string} path='config.js' - Relative pah to config.js file.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function loadConfig(host: string, path: string = '/config.js') {
|
||||
// Returns config.js file from global scope. We can't use the version that's
|
||||
// being used for the React Native app because the old/current Web app uses
|
||||
// config from the global scope.
|
||||
if (typeof APP !== 'undefined') {
|
||||
// FIXME The following call to setConfigParametersFromUrl is bad design
|
||||
// but URLProcessor still deals with the global variables config,
|
||||
// interfaceConfig, and loggingConfig and loadConfig. As the latter will
|
||||
// surely change in the future, so will the former then.
|
||||
URLProcessor.setConfigParametersFromUrl();
|
||||
export function loadConfig(host: string, path: string = 'config.js') {
|
||||
let promise;
|
||||
|
||||
return Promise.resolve(window.config);
|
||||
if (typeof APP === 'undefined') {
|
||||
promise
|
||||
= loadScript(new URL(path, host).toString())
|
||||
.then(() => {
|
||||
const { config } = window;
|
||||
|
||||
// We don't want to pollute global scope.
|
||||
window.config = undefined;
|
||||
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('window.config is not an object');
|
||||
}
|
||||
|
||||
return config;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`Failed to load ${path} from ${host}`, err);
|
||||
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
// Return config.js file from global scope. We can't use the version
|
||||
// that's being used for the React Native app because the old/current
|
||||
// Web app uses config from the global scope.
|
||||
promise = Promise.resolve(window.config);
|
||||
}
|
||||
|
||||
return loadScript(new URL(path, host).toString())
|
||||
.then(() => {
|
||||
const config = window.config;
|
||||
// FIXME It's neither here nor there at the time of this writing where
|
||||
// config, interfaceConfig, and loggingConfig should be overwritten by URL
|
||||
// params.
|
||||
promise = promise.then(value => {
|
||||
setConfigFromURLParams();
|
||||
|
||||
// We don't want to pollute global scope.
|
||||
window.config = undefined;
|
||||
return value;
|
||||
});
|
||||
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('window.config is not an object');
|
||||
}
|
||||
|
||||
return config;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`Failed to load ${path} from ${host}`, err);
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JitsiLocalTrack model from the given device id.
|
||||
*
|
||||
* @param {string} type - The media type of track being created. Expected values
|
||||
* are "video" or "audio".
|
||||
* @param {string} deviceId - The id of the target media source.
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
*/
|
||||
export function createLocalTrack(type, deviceId) {
|
||||
return JitsiMeetJS.createLocalTracks({
|
||||
cameraDeviceId: deviceId,
|
||||
devices: [ type ],
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
firefox_fake_device:
|
||||
window.config && window.config.firefox_fake_device,
|
||||
micDeviceId: deviceId
|
||||
})
|
||||
.then(([ jitsiLocalTrack ]) => jitsiLocalTrack);
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
|
||||
// of) the project jitsi-meet-react.
|
||||
// of) the project jitsi-meet.
|
||||
import JitsiMeetJS from './_';
|
||||
export { JitsiMeetJS as default };
|
||||
|
||||
|
||||
@@ -99,22 +99,23 @@ function _setConfig({ dispatch, getState }, next, action) {
|
||||
// disposed of first.
|
||||
// TODO Currently, disposeLib actually does not dispose of lib-jitsi-meet
|
||||
// because lib-jitsi-meet does not implement such functionality.
|
||||
const disposeLibPromise
|
||||
= initialized ? dispatch(disposeLib()) : Promise.resolve();
|
||||
if (initialized) {
|
||||
dispatch(disposeLib());
|
||||
}
|
||||
|
||||
disposeLibPromise.then(() => {
|
||||
// Let the new config into the Redux store (because initLib will read it
|
||||
// from there).
|
||||
next(action);
|
||||
// Let the new config into the Redux store (because initLib will read it
|
||||
// from there).
|
||||
const result = next(action);
|
||||
|
||||
// FIXME Obviously, the following is bad design. However, I'm currently
|
||||
// introducing the features base/config and base/logging and I'm trying
|
||||
// to minimize the scope of the changes while I'm attempting to preserve
|
||||
// compatibility with the existing partially React-ified Web source code
|
||||
// and what was already executing on React Native. Additionally, I do
|
||||
// not care to load logging_config.js on React Native.
|
||||
dispatch(setLoggingConfig(window.loggingConfig));
|
||||
// FIXME Obviously, the following is bad design. However, I'm currently
|
||||
// introducing the features base/config and base/logging and I'm trying to
|
||||
// minimize the scope of the changes while I'm attempting to preserve
|
||||
// compatibility with the existing partially React-ified Web source code and
|
||||
// what was already executing on React Native. Additionally, I do not care
|
||||
// to load logging_config.js on React Native.
|
||||
dispatch(setLoggingConfig(window.loggingConfig));
|
||||
|
||||
dispatch(initLib());
|
||||
});
|
||||
dispatch(initLib());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
const { userAgent } = navigator;
|
||||
let OS;
|
||||
|
||||
if (userAgent.match(/Android/i)) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../i18n';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ class Watermarks extends Component {
|
||||
let reactElement = null;
|
||||
|
||||
if (this.state.showJitsiWatermark
|
||||
|| (APP.tokenData.isGuest
|
||||
|| (this.props._isGuest
|
||||
&& this.state.showJitsiWatermarkForGuests)) {
|
||||
reactElement = <div className = 'watermark leftwatermark' />;
|
||||
|
||||
@@ -175,4 +175,27 @@ class Watermarks extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Watermarks);
|
||||
/**
|
||||
* Maps parts of Redux store to component prop types.
|
||||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _isGuest: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { isGuest } = state['features/jwt'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a
|
||||
* guest in the conference.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_isGuest: isGuest
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(translate(Watermarks));
|
||||
|
||||
@@ -215,8 +215,7 @@ function _getLocalTrack(tracks, mediaType) {
|
||||
track.isLocal()
|
||||
|
||||
// XXX JitsiTrack#getType() returns a MEDIA_TYPE value in the terms
|
||||
// of lib-jitsi-meet while mediaType is in the terms of
|
||||
// jitsi-meet-react.
|
||||
// of lib-jitsi-meet while mediaType is in the terms of jitsi-meet.
|
||||
&& track.getType() === mediaType);
|
||||
}
|
||||
|
||||
@@ -275,13 +274,12 @@ function _shouldMirror(track) {
|
||||
&& track.isLocal()
|
||||
&& track.isVideoTrack()
|
||||
|
||||
// XXX The type of the return value of
|
||||
// JitsiLocalTrack's getCameraFacingMode happens to be named
|
||||
// CAMERA_FACING_MODE as well, it's defined by lib-jitsi-meet. Note
|
||||
// though that the type of the value on the right side of the
|
||||
// equality check is defined by jitsi-meet-react. The type
|
||||
// definitions are surely compatible today but that may not be the
|
||||
// case tomorrow.
|
||||
// XXX The type of the return value of JitsiLocalTrack's
|
||||
// getCameraFacingMode happens to be named CAMERA_FACING_MODE as
|
||||
// well, it's defined by lib-jitsi-meet. Note though that the type
|
||||
// of the value on the right side of the equality check is defined
|
||||
// by jitsi-meet. The type definitions are surely compatible today
|
||||
// but that may not be the case tomorrow.
|
||||
&& track.getCameraFacingMode() === CAMERA_FACING_MODE.USER
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ function _trackUpdated(store, next, action) {
|
||||
if ('muted' in track) {
|
||||
// XXX The return value of JitsiTrack.getType() is of type MEDIA_TYPE
|
||||
// that happens to be compatible with the type MEDIA_TYPE defined by
|
||||
// jitsi-meet-react.
|
||||
// jitsi-meet.
|
||||
mediaType = track.jitsiTrack.getType();
|
||||
|
||||
const localTrack = _getLocalTrack(store, mediaType);
|
||||
|
||||
@@ -51,6 +51,7 @@ class Conference extends Component {
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
APP.UI.stopDaemons();
|
||||
APP.UI.unregisterListeners();
|
||||
APP.UI.unbindEvents();
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* global APP, config */
|
||||
|
||||
import BoshAddressChoice from '../../../modules/config/BoshAddressChoice';
|
||||
import HttpConfigFetch from '../../../modules/config/HttpConfigFetch';
|
||||
import ConferenceUrl from '../../../modules/URL/ConferenceUrl';
|
||||
|
||||
import { chooseBOSHAddress, obtainConfig } from '../base/config';
|
||||
import { RouteRegistry } from '../base/react';
|
||||
|
||||
import { Conference } from './components';
|
||||
@@ -47,15 +46,11 @@ function _initConference() {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _obtainConfig(location, room) {
|
||||
return new Promise((resolve, reject) => {
|
||||
HttpConfigFetch.obtainConfig(location, room, (success, error) => {
|
||||
if (success) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
return new Promise((resolve, reject) =>
|
||||
obtainConfig(location, room, (success, error) => {
|
||||
success ? resolve() : reject(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +82,7 @@ function _obtainConfigAndInit() {
|
||||
null, 'dialog.connectError', err);
|
||||
});
|
||||
} else {
|
||||
BoshAddressChoice.chooseAddress(config, room);
|
||||
chooseBOSHAddress(config, room);
|
||||
_initConference();
|
||||
}
|
||||
}
|
||||
@@ -113,12 +108,13 @@ function _obtainConfigHandler() {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setTokenData() {
|
||||
const localUser = APP.tokenData.caller;
|
||||
const state = APP.store.getState();
|
||||
const { caller } = state['features/jwt'];
|
||||
|
||||
if (localUser) {
|
||||
const email = localUser.getEmail();
|
||||
const avatarUrl = localUser.getAvatarUrl();
|
||||
const name = localUser.getName();
|
||||
if (caller) {
|
||||
const email = caller.email;
|
||||
const avatarUrl = caller.avatarUrl;
|
||||
const name = caller.name;
|
||||
|
||||
APP.settings.setEmail((email || '').trim(), true);
|
||||
APP.settings.setAvatarUrl((avatarUrl || '').trim());
|
||||
|
||||
48
react/features/dial-out/actionTypes.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Symbol } from '../base/react';
|
||||
|
||||
/**
|
||||
* The type of the action which signals a check for a dial-out phone number has
|
||||
* succeeded.
|
||||
*
|
||||
* {
|
||||
* type: PHONE_NUMBER_CHECKED,
|
||||
* response: Object
|
||||
* }
|
||||
*/
|
||||
export const PHONE_NUMBER_CHECKED
|
||||
= Symbol('PHONE_NUMBER_CHECKED');
|
||||
|
||||
/**
|
||||
* The type of the action which signals a cancel of the dial-out operation.
|
||||
*
|
||||
* {
|
||||
* type: DIAL_OUT_CANCELED,
|
||||
* response: Object
|
||||
* }
|
||||
*/
|
||||
export const DIAL_OUT_CANCELED
|
||||
= Symbol('DIAL_OUT_CANCELED');
|
||||
|
||||
/**
|
||||
* The type of the action which signals a request for dial-out country codes has
|
||||
* succeeded.
|
||||
*
|
||||
* {
|
||||
* type: DIAL_OUT_CODES_UPDATED,
|
||||
* response: Object
|
||||
* }
|
||||
*/
|
||||
export const DIAL_OUT_CODES_UPDATED
|
||||
= Symbol('DIAL_OUT_CODES_UPDATED');
|
||||
|
||||
/**
|
||||
* The type of the action which signals a failure in some of dial-out service
|
||||
* requests.
|
||||
*
|
||||
* {
|
||||
* type: DIAL_OUT_SERVICE_FAILED,
|
||||
* response: Object
|
||||
* }
|
||||
*/
|
||||
export const DIAL_OUT_SERVICE_FAILED
|
||||
= Symbol('DIAL_OUT_SERVICE_FAILED');
|
||||
97
react/features/dial-out/actions.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { openDialog } from '../../features/base/dialog';
|
||||
|
||||
import {
|
||||
DIAL_OUT_CANCELED,
|
||||
DIAL_OUT_CODES_UPDATED,
|
||||
DIAL_OUT_SERVICE_FAILED,
|
||||
PHONE_NUMBER_CHECKED
|
||||
} from './actionTypes';
|
||||
|
||||
import { DialOutDialog } from './components';
|
||||
|
||||
declare var $: Function;
|
||||
declare var config: Object;
|
||||
|
||||
/**
|
||||
* Dials the given number.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancel() {
|
||||
return {
|
||||
type: DIAL_OUT_CANCELED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dials the given number.
|
||||
*
|
||||
* @param {string} dialNumber - The number to dial.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function dial(dialNumber) {
|
||||
return (dispatch, getState) => {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
conference.dial(dialNumber);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request for dial-out country codes.
|
||||
*
|
||||
* @param {string} dialNumber - The dial number to check for validity.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function checkDialNumber(dialNumber) {
|
||||
return (dispatch, getState) => {
|
||||
const { dialOutAuthUrl } = getState()['features/base/config'];
|
||||
|
||||
const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
|
||||
|
||||
$.getJSON(fullUrl)
|
||||
.success(response =>
|
||||
dispatch({
|
||||
type: PHONE_NUMBER_CHECKED,
|
||||
response
|
||||
}))
|
||||
.error(error =>
|
||||
dispatch({
|
||||
type: DIAL_OUT_SERVICE_FAILED,
|
||||
error
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens the dial-out dialog.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openDialOutDialog() {
|
||||
return openDialog(DialOutDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request for dial-out country codes.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateDialOutCodes() {
|
||||
return (dispatch, getState) => {
|
||||
const { dialOutCodesUrl } = getState()['features/base/config'];
|
||||
|
||||
$.getJSON(dialOutCodesUrl)
|
||||
.success(response =>
|
||||
dispatch({
|
||||
type: DIAL_OUT_CODES_UPDATED,
|
||||
response
|
||||
}))
|
||||
.error(error =>
|
||||
dispatch({
|
||||
type: DIAL_OUT_SERVICE_FAILED,
|
||||
error
|
||||
}));
|
||||
};
|
||||
}
|
||||
40
react/features/dial-out/components/CountryIcon.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
* Implements a React Component to render a country flag icon.
|
||||
*/
|
||||
class CountryIcon extends Component {
|
||||
/**
|
||||
* {@code CountryIcon}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The css style class name.
|
||||
*/
|
||||
className: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The 2-letter country code.
|
||||
*/
|
||||
countryCode: React.PropTypes.string
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const iconClassName
|
||||
= `flag-icon flag-icon-${this.props.countryCode}
|
||||
flag-icon-squared ${this.props.className}`;
|
||||
|
||||
return <span className = { iconClassName } />;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default CountryIcon;
|
||||
226
react/features/dial-out/components/DialOutDialog.web.js
Normal file
@@ -0,0 +1,226 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
|
||||
import { cancel, checkDialNumber, dial } from '../actions';
|
||||
import DialOutNumbersForm from './DialOutNumbersForm';
|
||||
|
||||
/**
|
||||
* Implements a React Component which allows the user to dial out from the
|
||||
* conference.
|
||||
*/
|
||||
class DialOutDialog extends Component {
|
||||
|
||||
/**
|
||||
* {@code DialOutDialog} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Property indicating if a dial number is allowed.
|
||||
*/
|
||||
_isDialNumberAllowed: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The function performing the cancel action.
|
||||
*/
|
||||
cancel: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The function performing the phone number validity check.
|
||||
*/
|
||||
checkDialNumber: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The function performing the dial action.
|
||||
*/
|
||||
dial: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DialOutNumbersForm} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* The number to dial.
|
||||
*/
|
||||
dialNumber: '',
|
||||
|
||||
/**
|
||||
* Indicates if the dial input is currently empty.
|
||||
*/
|
||||
isDialInputEmpty: true
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDialNumberChange = this._onDialNumberChange.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _isDialNumberAllowed } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okDisabled = { this.state.isDialInputEmpty
|
||||
|| !_isDialNumberAllowed }
|
||||
okTitleKey = 'dialOut.dial'
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialOut.dialOut'
|
||||
width = 'small'>
|
||||
{ this._renderContent() }
|
||||
</Dialog>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the dial number in a way to remove all non digital characters
|
||||
* from it (including spaces, brackets, dash, dot, etc.).
|
||||
*
|
||||
* @param {string} dialNumber - The phone number to format.
|
||||
* @private
|
||||
* @returns {string} - The formatted phone number.
|
||||
*/
|
||||
_formatDialNumber(dialNumber) {
|
||||
return dialNumber.replace(/\D/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the dialog content.
|
||||
*
|
||||
* @returns {XML}
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
const { _isDialNumberAllowed } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'dial-out-content'>
|
||||
{ _isDialNumberAllowed ? '' : this._renderErrorMessage() }
|
||||
<DialOutNumbersForm
|
||||
onChange = { this._onDialNumberChange } />
|
||||
</div>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the error message to display if the dial phone number is not
|
||||
* allowed.
|
||||
*
|
||||
* @returns {XML}
|
||||
* @private
|
||||
*/
|
||||
_renderErrorMessage() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'dial-out-error'>
|
||||
{ t('dialOut.phoneNotAllowed') }
|
||||
</div>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the dial out.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - Returns true to indicate that the dialog should be
|
||||
* closed.
|
||||
*/
|
||||
_onCancel() {
|
||||
this.props.cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dials the number.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - Returns true to indicate that the dialog should be
|
||||
* closed.
|
||||
*/
|
||||
_onSubmit() {
|
||||
if (this.props._isDialNumberAllowed) {
|
||||
this.props.dial(this.state.dialNumber);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dialNumber and check for validity.
|
||||
*
|
||||
* @param {string} dialCode - The dial code value.
|
||||
* @param {string} dialInput - The dial input value.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDialNumberChange(dialCode, dialInput) {
|
||||
// We remove all starting zeros from the dial input before attaching it
|
||||
// to the country code.
|
||||
const formattedDialInput = dialInput.replace(/^(0+)/, '');
|
||||
|
||||
const dialNumber = `${dialCode}${formattedDialInput}`;
|
||||
|
||||
const formattedNumber = this._formatDialNumber(dialNumber);
|
||||
|
||||
this.props.checkDialNumber(formattedNumber);
|
||||
|
||||
this.setState({
|
||||
dialNumber: formattedNumber,
|
||||
isDialInputEmpty: !formattedDialInput
|
||||
|| formattedDialInput.length === 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code DialOutDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _isDialNumberAllowed: React.PropTypes.bool
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { isDialNumberAllowed } = state['features/dial-out'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* Property indicating if a dial number is allowed.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_isDialNumberAllowed: isDialNumberAllowed
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(
|
||||
connect(_mapStateToProps, {
|
||||
cancel,
|
||||
dial,
|
||||
checkDialNumber
|
||||
})(DialOutDialog));
|
||||
346
react/features/dial-out/components/DialOutNumbersForm.web.js
Normal file
@@ -0,0 +1,346 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ExpandIcon from '@atlaskit/icon/glyph/expand';
|
||||
import { StatelessDropdownMenu } from '@atlaskit/dropdown-menu';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import CountryIcon from './CountryIcon';
|
||||
import { updateDialOutCodes } from '../actions';
|
||||
|
||||
/**
|
||||
* The expand icon of the dropdown menu.
|
||||
*
|
||||
* @type {XML}
|
||||
*/
|
||||
const EXPAND_ICON = <ExpandIcon label = 'expand' />;
|
||||
|
||||
/**
|
||||
* The default value of the country if the fetch service is unavailable.
|
||||
*
|
||||
* @type {{name: string, dialCode: string, code: string}}
|
||||
*/
|
||||
const DEFAULT_COUNTRY = {
|
||||
name: 'United States',
|
||||
dialCode: '+1',
|
||||
code: 'US'
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} responsible for fetching and displaying dial-out
|
||||
* country codes, as well as dialing a phone number.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class DialOutNumbersForm extends Component {
|
||||
/**
|
||||
* {@code DialOutNumbersForm}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The redux state representing the list of dial-out codes.
|
||||
*/
|
||||
_dialOutCodes: React.PropTypes.array,
|
||||
|
||||
/**
|
||||
* The function called on every dial input change.
|
||||
*/
|
||||
onChange: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to send an ajax request for dial-out codes.
|
||||
*/
|
||||
updateDialOutCodes: React.PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DialOutNumbersForm} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dialInput: '',
|
||||
|
||||
/**
|
||||
* Whether or not the dropdown should be open.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isDropdownOpen: false,
|
||||
|
||||
/**
|
||||
* The selected country.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
selectedCountry: DEFAULT_COUNTRY
|
||||
};
|
||||
|
||||
/**
|
||||
* The internal reference to the DOM/HTML element backing the React
|
||||
* {@code Component} text input.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
this._dialInputElem = null;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onInputChange = this._onInputChange.bind(this);
|
||||
this._onOpenChange = this._onOpenChange.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._setDialInputElement = this._setDialInputElement.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a request for dial out codes if not already present in the
|
||||
* redux store. If dial out codes are present, sets a default code to
|
||||
* display in the dropdown trigger.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
const dialOutCodes = this.props._dialOutCodes;
|
||||
|
||||
if (dialOutCodes) {
|
||||
this._setDefaultCode(dialOutCodes);
|
||||
} else {
|
||||
this.props.updateDialOutCodes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors for dial out code updates and sets a default code to display in
|
||||
* the dropdown trigger if not already set.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.state.selectedCountry && nextProps._dialOutCodes) {
|
||||
this._setDefaultCode(nextProps._dialOutCodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t, _dialOutCodes } = this.props;
|
||||
|
||||
const items
|
||||
= _dialOutCodes ? this._formatCountryCodes(_dialOutCodes) : [];
|
||||
|
||||
return (
|
||||
<div className = 'form-control'>
|
||||
{ this._createDropdownMenu(items) }
|
||||
<div className = 'dial-out-input'>
|
||||
<input
|
||||
autoFocus = { true }
|
||||
className = 'input-control'
|
||||
onChange = { this._onInputChange }
|
||||
placeholder = { t('dialOut.enterPhone') }
|
||||
ref = { this._setDialInputElement }
|
||||
type = 'text' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code StatelessDropdownMenu} instance.
|
||||
*
|
||||
* @param {Array} items - The content to display within the dropdown.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_createDropdownMenu(items) {
|
||||
const { code, dialCode } = this.state.selectedCountry;
|
||||
|
||||
return (
|
||||
<StatelessDropdownMenu
|
||||
isOpen = { this.state.isDropdownOpen }
|
||||
items = { [ { items } ] }
|
||||
onItemActivated = { this._onSelect }
|
||||
onOpenChange = { this._onOpenChange }
|
||||
shouldFitContainer = { true }>
|
||||
{ this._createDropdownTrigger(dialCode, code) }
|
||||
</StatelessDropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a React {@code Component} with a readonly HTMLInputElement as a
|
||||
* trigger for displaying the dropdown menu. The {@code Component} will also
|
||||
* display the currently selected number.
|
||||
*
|
||||
* @param {string} dialCode - The +xx dial code.
|
||||
* @param {string} countryCode - The country 2 letter code.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_createDropdownTrigger(dialCode, countryCode) {
|
||||
return (
|
||||
<div className = 'dropdown'>
|
||||
<CountryIcon
|
||||
className = 'dial-out-flag-icon'
|
||||
countryCode = { `${countryCode}` } />
|
||||
<input
|
||||
className = 'input-control dial-out-code'
|
||||
readOnly = { true }
|
||||
type = 'text'
|
||||
value = { dialCode || '' } />
|
||||
<span className = 'dropdown-trigger-icon'>
|
||||
{ EXPAND_ICON }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the passed in numbers object into an array of objects that can
|
||||
* be parsed by {@code StatelessDropdownMenu}.
|
||||
*
|
||||
* @param {Object} countryCodes - The list of country codes.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_formatCountryCodes(countryCodes) {
|
||||
|
||||
return countryCodes.map(country => {
|
||||
const countryIcon
|
||||
= <CountryIcon countryCode = { `${country.code}` } />;
|
||||
|
||||
const countryElement
|
||||
= <span>{countryIcon} { country.name }</span>;
|
||||
|
||||
return {
|
||||
content: `${country.dialCode}`,
|
||||
elemBefore: countryElement,
|
||||
country
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dialNumber when changes to the dial text or code happen.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDialNumberChange() {
|
||||
const { dialCode } = this.state.selectedCountry;
|
||||
|
||||
this.props.onChange(dialCode, this.state.dialInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dialInput state when the input changes.
|
||||
*
|
||||
* @param {Object} e - The event notifying us of the change.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onInputChange(e) {
|
||||
this.setState({
|
||||
dialInput: e.target.value
|
||||
}, () => {
|
||||
this._onDialNumberChange();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal state to either open or close the dropdown. If the
|
||||
* dropdown is disabled, the state will always be set to false.
|
||||
*
|
||||
* @param {Object} dropdownEvent - The even returned from clicking on the
|
||||
* dropdown trigger.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenChange(dropdownEvent) {
|
||||
this.setState({
|
||||
isDropdownOpen: dropdownEvent.isOpen
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal state of the currently selected country code.
|
||||
*
|
||||
* @param {Object} selection - Event from choosing an dropdown option.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelect(selection) {
|
||||
this.setState({
|
||||
isDropdownOpen: false,
|
||||
selectedCountry: selection.item.country
|
||||
}, () => {
|
||||
this._onDialNumberChange();
|
||||
|
||||
this._dialInputElem.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal state of the currently selected number by defaulting
|
||||
* to the first available number.
|
||||
*
|
||||
* @param {Object} countryCodes - The list of country codes to choose from
|
||||
* for setting a default code.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setDefaultCode(countryCodes) {
|
||||
this.setState({
|
||||
selectedCountry: countryCodes[0]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the DOM/HTML element backing the React
|
||||
* {@code Component} dial input.
|
||||
*
|
||||
* @param {HTMLInputElement} input - The DOM/HTML element for this
|
||||
* {@code Component}'s text input.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setDialInputElement(input) {
|
||||
this._dialInputElem = input;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code DialOutNumbersForm}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _dialOutCodes: React.PropTypes.object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { dialOutCodes } = state['features/dial-out'];
|
||||
|
||||
return {
|
||||
_dialOutCodes: dialOutCodes
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps,
|
||||
{ updateDialOutCodes })(DialOutNumbersForm));
|
||||
1
react/features/dial-out/components/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as DialOutDialog } from './DialOutDialog';
|
||||
4
react/features/dial-out/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './components';
|
||||
|
||||
import './reducer';
|
||||