ref(overlay): The overlays to use React
This commit is contained in:
parent
f3269070b2
commit
92d0589a37
|
@ -20,6 +20,12 @@ import analytics from './modules/analytics/analytics';
|
||||||
|
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
|
|
||||||
|
import { conferenceFailed } from './react/features/base/conference';
|
||||||
|
import {
|
||||||
|
mediaPermissionPromptVisibilityChanged,
|
||||||
|
suspendDetected
|
||||||
|
} from './react/features/overlay';
|
||||||
|
|
||||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||||
|
|
||||||
|
@ -91,7 +97,10 @@ function createInitialLocalTracksAndConnect(roomName) {
|
||||||
|
|
||||||
JitsiMeetJS.mediaDevices.addEventListener(
|
JitsiMeetJS.mediaDevices.addEventListener(
|
||||||
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
|
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
|
||||||
browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
|
browser =>
|
||||||
|
APP.store.dispatch(
|
||||||
|
mediaPermissionPromptVisibilityChanged(true, browser))
|
||||||
|
);
|
||||||
|
|
||||||
// First try to retrieve both audio and video.
|
// First try to retrieve both audio and video.
|
||||||
let tryCreateLocalTracks = createLocalTracks(
|
let tryCreateLocalTracks = createLocalTracks(
|
||||||
|
@ -109,8 +118,7 @@ function createInitialLocalTracksAndConnect(roomName) {
|
||||||
|
|
||||||
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
||||||
.then(([tracks, con]) => {
|
.then(([tracks, con]) => {
|
||||||
APP.UI.hideUserMediaPermissionsGuidanceOverlay();
|
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||||
|
|
||||||
if (audioAndVideoError) {
|
if (audioAndVideoError) {
|
||||||
if (audioOnlyError) {
|
if (audioOnlyError) {
|
||||||
// If both requests for 'audio' + 'video' and 'audio' only
|
// If both requests for 'audio' + 'video' and 'audio' only
|
||||||
|
@ -334,6 +342,7 @@ class ConferenceConnector {
|
||||||
this._reject(err);
|
this._reject(err);
|
||||||
}
|
}
|
||||||
_onConferenceFailed(err, ...params) {
|
_onConferenceFailed(err, ...params) {
|
||||||
|
APP.store.dispatch(conferenceFailed(room, err, ...params));
|
||||||
logger.error('CONFERENCE FAILED:', err, ...params);
|
logger.error('CONFERENCE FAILED:', err, ...params);
|
||||||
APP.UI.hideRingOverLay();
|
APP.UI.hideRingOverLay();
|
||||||
switch (err) {
|
switch (err) {
|
||||||
|
@ -408,8 +417,6 @@ class ConferenceConnector {
|
||||||
// the app. Both the errors above are unrecoverable from the library
|
// the app. Both the errors above are unrecoverable from the library
|
||||||
// perspective.
|
// perspective.
|
||||||
room.leave().then(() => connection.disconnect());
|
room.leave().then(() => connection.disconnect());
|
||||||
APP.UI.showPageReloadOverlay(
|
|
||||||
false /* not a network type of failure */, err);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConferenceErrors.CONFERENCE_MAX_USERS:
|
case ConferenceErrors.CONFERENCE_MAX_USERS:
|
||||||
|
@ -466,6 +473,26 @@ function disconnect() {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
|
||||||
|
* @param {JitsiMeetJS.connection.error} error the error reported.
|
||||||
|
* @returns {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _connectionFailedHandler (error) {
|
||||||
|
switch (error) {
|
||||||
|
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||||
|
case ConnectionErrors.OTHER_ERROR:
|
||||||
|
case ConnectionErrors.SERVER_ERROR: {
|
||||||
|
APP.connection.removeEventListener( ConnectionEvents.CONNECTION_FAILED,
|
||||||
|
_connectionFailedHandler);
|
||||||
|
if (room)
|
||||||
|
room.leave();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isModerator: false,
|
isModerator: false,
|
||||||
audioMuted: false,
|
audioMuted: false,
|
||||||
|
@ -518,11 +545,13 @@ export default {
|
||||||
return createInitialLocalTracksAndConnect(options.roomName);
|
return createInitialLocalTracksAndConnect(options.roomName);
|
||||||
}).then(([tracks, con]) => {
|
}).then(([tracks, con]) => {
|
||||||
logger.log('initialized with %s local tracks', tracks.length);
|
logger.log('initialized with %s local tracks', tracks.length);
|
||||||
|
con.addEventListener(
|
||||||
|
ConnectionEvents.CONNECTION_FAILED,
|
||||||
|
_connectionFailedHandler);
|
||||||
APP.connection = connection = con;
|
APP.connection = connection = con;
|
||||||
this.isDesktopSharingEnabled =
|
this.isDesktopSharingEnabled =
|
||||||
JitsiMeetJS.isDesktopSharingEnabled();
|
JitsiMeetJS.isDesktopSharingEnabled();
|
||||||
APP.remoteControl.init();
|
APP.remoteControl.init();
|
||||||
this._bindConnectionFailedHandler(con);
|
|
||||||
this._createRoom(tracks);
|
this._createRoom(tracks);
|
||||||
|
|
||||||
if (UIUtil.isButtonEnabled('contacts')
|
if (UIUtil.isButtonEnabled('contacts')
|
||||||
|
@ -561,47 +590,6 @@ export default {
|
||||||
isLocalId (id) {
|
isLocalId (id) {
|
||||||
return this.getMyUserId() === id;
|
return this.getMyUserId() === id;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Binds a handler that will handle the case when the connection is dropped
|
|
||||||
* in the middle of the conference.
|
|
||||||
* @param {JitsiConnection} connection the connection to which the handler
|
|
||||||
* will be bound to.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_bindConnectionFailedHandler (connection) {
|
|
||||||
const handler = function (error, errMsg) {
|
|
||||||
/* eslint-disable no-case-declarations */
|
|
||||||
switch (error) {
|
|
||||||
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
|
||||||
case ConnectionErrors.OTHER_ERROR:
|
|
||||||
case ConnectionErrors.SERVER_ERROR:
|
|
||||||
|
|
||||||
logger.error("XMPP connection error: " + errMsg);
|
|
||||||
|
|
||||||
// From all of the cases above only CONNECTION_DROPPED_ERROR
|
|
||||||
// is considered a network type of failure
|
|
||||||
const isNetworkFailure
|
|
||||||
= error === ConnectionErrors.CONNECTION_DROPPED_ERROR;
|
|
||||||
|
|
||||||
APP.UI.showPageReloadOverlay(
|
|
||||||
isNetworkFailure,
|
|
||||||
"xmpp-conn-dropped:" + errMsg);
|
|
||||||
|
|
||||||
connection.removeEventListener(
|
|
||||||
ConnectionEvents.CONNECTION_FAILED, handler);
|
|
||||||
|
|
||||||
// FIXME it feels like the conference should be stopped
|
|
||||||
// by lib-jitsi-meet
|
|
||||||
if (room)
|
|
||||||
room.leave();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* eslint-enable no-case-declarations */
|
|
||||||
};
|
|
||||||
connection.addEventListener(
|
|
||||||
ConnectionEvents.CONNECTION_FAILED, handler);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
||||||
* @param mute true for mute and false for unmute.
|
* @param mute true for mute and false for unmute.
|
||||||
|
@ -1365,6 +1353,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
|
room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
|
||||||
|
APP.store.dispatch(suspendDetected());
|
||||||
// After wake up, we will be in a state where conference is left
|
// After wake up, we will be in a state where conference is left
|
||||||
// there will be dialog shown to user.
|
// there will be dialog shown to user.
|
||||||
// We do not want video/audio as we show an overlay and after it
|
// We do not want video/audio as we show an overlay and after it
|
||||||
|
@ -1385,9 +1374,6 @@ export default {
|
||||||
if (localAudio) {
|
if (localAudio) {
|
||||||
localAudio.dispose();
|
localAudio.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// show overlay
|
|
||||||
APP.UI.showSuspendedOverlay();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {
|
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {
|
||||||
|
|
|
@ -4,6 +4,11 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||||
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
|
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
|
||||||
|
|
||||||
|
import {
|
||||||
|
connectionEstablished,
|
||||||
|
connectionFailed
|
||||||
|
} from './react/features/base/connection';
|
||||||
|
|
||||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||||
|
|
||||||
|
@ -67,6 +72,23 @@ function connect(id, password, roomName) {
|
||||||
connection.addEventListener(
|
connection.addEventListener(
|
||||||
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
|
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
|
||||||
);
|
);
|
||||||
|
connection.addEventListener(
|
||||||
|
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
|
||||||
|
|
||||||
|
function connectionFailedHandler (error, errMsg) {
|
||||||
|
APP.store.dispatch(connectionFailed(connection, error, errMsg));
|
||||||
|
|
||||||
|
switch (error) {
|
||||||
|
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||||
|
case ConnectionErrors.OTHER_ERROR:
|
||||||
|
case ConnectionErrors.SERVER_ERROR: {
|
||||||
|
connection.removeEventListener(
|
||||||
|
ConnectionEvents.CONNECTION_FAILED,
|
||||||
|
connectionFailedHandler);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function unsubscribe() {
|
function unsubscribe() {
|
||||||
connection.removeEventListener(
|
connection.removeEventListener(
|
||||||
|
@ -80,6 +102,7 @@ function connect(id, password, roomName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConnectionEstablished() {
|
function handleConnectionEstablished() {
|
||||||
|
APP.store.dispatch(connectionEstablished(connection));
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
resolve(connection);
|
resolve(connection);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reload_overlay_msg {
|
.reload_overlay_text {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
@ -13,4 +13,4 @@
|
||||||
#reloadProgressBar {
|
#reloadProgressBar {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,13 @@ import UIEvents from "../../service/UI/UIEvents";
|
||||||
import EtherpadManager from './etherpad/Etherpad';
|
import EtherpadManager from './etherpad/Etherpad';
|
||||||
import SharedVideoManager from './shared_video/SharedVideo';
|
import SharedVideoManager from './shared_video/SharedVideo';
|
||||||
import Recording from "./recording/Recording";
|
import Recording from "./recording/Recording";
|
||||||
import GumPermissionsOverlay
|
|
||||||
from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
|
|
||||||
|
|
||||||
import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
|
|
||||||
import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
|
|
||||||
import VideoLayout from "./videolayout/VideoLayout";
|
import VideoLayout from "./videolayout/VideoLayout";
|
||||||
import FilmStrip from "./videolayout/FilmStrip";
|
import FilmStrip from "./videolayout/FilmStrip";
|
||||||
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
|
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
|
||||||
import Profile from "./side_pannels/profile/Profile";
|
import Profile from "./side_pannels/profile/Profile";
|
||||||
import Settings from "./../settings/Settings";
|
import Settings from "./../settings/Settings";
|
||||||
import RingOverlay from "./ring_overlay/RingOverlay";
|
import RingOverlay from "./ring_overlay/RingOverlay";
|
||||||
import { randomInt } from "../../react/features/base/util/randomUtil";
|
|
||||||
import UIErrors from './UIErrors';
|
import UIErrors from './UIErrors';
|
||||||
import { debounce } from "../util/helpers";
|
import { debounce } from "../util/helpers";
|
||||||
|
|
||||||
|
@ -40,6 +35,17 @@ import FollowMe from "../FollowMe";
|
||||||
var eventEmitter = new EventEmitter();
|
var eventEmitter = new EventEmitter();
|
||||||
UI.eventEmitter = eventEmitter;
|
UI.eventEmitter = eventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether an overlay is visible or not.
|
||||||
|
*
|
||||||
|
* FIXME: This is temporary solution. Don't use this variable!
|
||||||
|
* Should be removed when all the code is move to react.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
UI.overlayVisible = false;
|
||||||
|
|
||||||
let etherpadManager;
|
let etherpadManager;
|
||||||
let sharedVideoManager;
|
let sharedVideoManager;
|
||||||
|
|
||||||
|
@ -1087,20 +1093,6 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the user that the video conferencing service is badly broken and
|
|
||||||
* the page should be reloaded.
|
|
||||||
*
|
|
||||||
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's caused by
|
|
||||||
* network related failure or <tt>false</tt> when it's the infrastructure.
|
|
||||||
* @param {string} a label string identifying the reason for the page reload
|
|
||||||
* which will be included in details of the log event.
|
|
||||||
*/
|
|
||||||
UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
|
|
||||||
// Reload the page after 10 - 30 seconds
|
|
||||||
PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates auth info on the UI.
|
* Updates auth info on the UI.
|
||||||
* @param {boolean} isAuthEnabled if authentication is enabled
|
* @param {boolean} isAuthEnabled if authentication is enabled
|
||||||
|
@ -1414,10 +1406,7 @@ UI.hideRingOverLay = function () {
|
||||||
* @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
|
* @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
|
||||||
*/
|
*/
|
||||||
UI.isOverlayVisible = function () {
|
UI.isOverlayVisible = function () {
|
||||||
return RingOverlay.isVisible()
|
return RingOverlay.isVisible() || this.overlayVisible;
|
||||||
|| SuspendedOverlay.isVisible()
|
|
||||||
|| PageReloadOverlay.isVisible()
|
|
||||||
|| GumPermissionsOverlay.isVisible();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1429,29 +1418,6 @@ UI.isRingOverlayVisible = function () {
|
||||||
return RingOverlay.isVisible();
|
return RingOverlay.isVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows browser-specific overlay with guidance how to proceed with gUM prompt.
|
|
||||||
* @param {string} browser - name of browser for which to show the guidance
|
|
||||||
* overlay.
|
|
||||||
*/
|
|
||||||
UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
|
|
||||||
GumPermissionsOverlay.show(browser);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows suspended overlay with a button to rejoin conference.
|
|
||||||
*/
|
|
||||||
UI.showSuspendedOverlay = function () {
|
|
||||||
SuspendedOverlay.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides browser-specific overlay with guidance how to proceed with gUM prompt.
|
|
||||||
*/
|
|
||||||
UI.hideUserMediaPermissionsGuidanceOverlay = function () {
|
|
||||||
GumPermissionsOverlay.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles user's features changes.
|
* Handles user's features changes.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
/* global interfaceConfig */
|
|
||||||
|
|
||||||
import Overlay from '../overlay/Overlay';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An overlay with guidance how to proceed with gUM prompt.
|
|
||||||
*/
|
|
||||||
class GUMOverlayImpl extends Overlay {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs overlay with guidance how to proceed with gUM prompt.
|
|
||||||
* @param {string} browser - name of browser for which to construct the
|
|
||||||
* guidance overlay.
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
constructor(browser) {
|
|
||||||
super();
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
_buildOverlayContent() {
|
|
||||||
let textKey = `userMedia.${this.browser}GrantPermissions`;
|
|
||||||
let titleKey = 'startupoverlay.title';
|
|
||||||
let titleOptions = '{ "postProcess": "resolveAppName" }';
|
|
||||||
let policyTextKey = 'startupoverlay.policyText';
|
|
||||||
let policyLogo = '';
|
|
||||||
let policyLogoSrc = interfaceConfig.POLICY_LOGO;
|
|
||||||
if (policyLogoSrc) {
|
|
||||||
policyLogo += (
|
|
||||||
`<div class="policy__logo">
|
|
||||||
<img src="${policyLogoSrc}"/>
|
|
||||||
</div>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
`<div class="inlay">
|
|
||||||
<span class="inlay__icon icon-microphone"></span>
|
|
||||||
<span class="inlay__icon icon-camera"></span>
|
|
||||||
<h3 class="inlay__title" data-i18n="${titleKey}"
|
|
||||||
data-i18n-options='${titleOptions}'></h3>
|
|
||||||
<span class='inlay__text'data-i18n='[html]${textKey}'></span>
|
|
||||||
</div>
|
|
||||||
<div class="policy overlay__policy">
|
|
||||||
<p class="policy__text" data-i18n="[html]${policyTextKey}"></p>
|
|
||||||
${policyLogo}
|
|
||||||
</div>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores GUM overlay instance.
|
|
||||||
* @type {GUMOverlayImpl}
|
|
||||||
*/
|
|
||||||
let overlay;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* Checks whether the overlay is currently visible.
|
|
||||||
* @return {boolean} <tt>true</tt> if the overlay is visible
|
|
||||||
* or <tt>false</tt> otherwise.
|
|
||||||
*/
|
|
||||||
isVisible () {
|
|
||||||
return overlay && overlay.isVisible();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Shows browser-specific overlay with guidance how to proceed with
|
|
||||||
* gUM prompt.
|
|
||||||
* @param {string} browser - name of browser for which to show the
|
|
||||||
* guidance overlay.
|
|
||||||
*/
|
|
||||||
show(browser) {
|
|
||||||
if (!overlay) {
|
|
||||||
overlay = new GUMOverlayImpl(browser);
|
|
||||||
}
|
|
||||||
overlay.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides browser-specific overlay with guidance how to proceed with
|
|
||||||
* gUM prompt.
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
overlay && overlay.hide();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,94 +0,0 @@
|
||||||
/* global $, APP */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for overlay components - the components which are displayed on
|
|
||||||
* top of the application with semi-transparent background covering the whole
|
|
||||||
* screen.
|
|
||||||
*/
|
|
||||||
export default class Overlay{
|
|
||||||
/**
|
|
||||||
* Creates new <tt>Overlay</tt> instance.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {jQuery}
|
|
||||||
*/
|
|
||||||
this.$overlay = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if this overlay should use the light look & feel or the
|
|
||||||
* standard one.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.isLightOverlay = false;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Template method which should be used by subclasses to provide the overlay
|
|
||||||
* content. The contents provided by this method are later subject to
|
|
||||||
* the translation using {@link APP.translation.translateElement}.
|
|
||||||
* @return {string} HTML representation of the overlay dialog contents.
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
_buildOverlayContent() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Constructs the HTML body of the overlay dialog.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_buildOverlayHtml() {
|
|
||||||
|
|
||||||
let overlayContent = this._buildOverlayContent();
|
|
||||||
|
|
||||||
let containerClass = this.isLightOverlay ? "overlay__container-light"
|
|
||||||
: "overlay__container";
|
|
||||||
|
|
||||||
this.$overlay = $(`
|
|
||||||
<div class=${containerClass}>
|
|
||||||
<div class='overlay__content'>
|
|
||||||
${overlayContent}
|
|
||||||
</div>
|
|
||||||
</div>`);
|
|
||||||
|
|
||||||
APP.translation.translateElement(this.$overlay);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks whether the page reload overlay has been displayed.
|
|
||||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
|
|
||||||
* visible or <tt>false</tt> otherwise.
|
|
||||||
*/
|
|
||||||
isVisible() {
|
|
||||||
return this.$overlay && this.$overlay.parents('body').length > 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Template method called just after the overlay is displayed for the first
|
|
||||||
* time.
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
_onShow() {
|
|
||||||
// To be overridden by subclasses.
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Shows the overlay dialog and attaches the underlying HTML representation
|
|
||||||
* to the DOM.
|
|
||||||
*/
|
|
||||||
show() {
|
|
||||||
|
|
||||||
!this.$overlay && this._buildOverlayHtml();
|
|
||||||
|
|
||||||
if (!this.isVisible()) {
|
|
||||||
this.$overlay.appendTo('body');
|
|
||||||
this._onShow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the overlay dialog and detaches it's HTML representation from
|
|
||||||
* the DOM.
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
this.$overlay && this.$overlay.detach();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
/* global $, APP, AJS */
|
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
|
||||||
|
|
||||||
import Overlay from "../overlay/Overlay";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An overlay dialog which is shown before the conference is reloaded. Shows
|
|
||||||
* a warning message and counts down towards the reload.
|
|
||||||
*/
|
|
||||||
class PageReloadOverlayImpl extends Overlay{
|
|
||||||
/**
|
|
||||||
* Creates new <tt>PageReloadOverlayImpl</tt>
|
|
||||||
* @param {number} timeoutSeconds how long the overlay dialog will be
|
|
||||||
* displayed, before the conference will be reloaded.
|
|
||||||
* @param {string} title the title of the overlay message
|
|
||||||
* @param {string} message the message of the overlay
|
|
||||||
* @param {string} buttonHtml the button html or an empty string if there's
|
|
||||||
* no button
|
|
||||||
* @param {boolean} isLightOverlay indicates if the overlay should be a
|
|
||||||
* light overlay or a standard one
|
|
||||||
*/
|
|
||||||
constructor(timeoutSeconds, title, message, buttonHtml, isLightOverlay) {
|
|
||||||
super();
|
|
||||||
/**
|
|
||||||
* Conference reload counter in seconds.
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
this.timeLeft = timeoutSeconds;
|
|
||||||
/**
|
|
||||||
* Conference reload timeout in seconds.
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
this.timeout = timeoutSeconds;
|
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
this.message = message;
|
|
||||||
this.buttonHtml = buttonHtml;
|
|
||||||
this.isLightOverlay = isLightOverlay;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Constructs overlay body with the warning message and count down towards
|
|
||||||
* the conference reload.
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_buildOverlayContent() {
|
|
||||||
return `<div class="inlay">
|
|
||||||
<span data-i18n=${this.title}
|
|
||||||
class='reload_overlay_title'></span>
|
|
||||||
<span data-i18n=${this.message}
|
|
||||||
class='reload_overlay_msg'></span>
|
|
||||||
<div>
|
|
||||||
<div id='reloadProgressBar'
|
|
||||||
class="aui-progress-indicator">
|
|
||||||
<span class="aui-progress-indicator-value"></span>
|
|
||||||
</div>
|
|
||||||
<span id='reloadSecRemaining'
|
|
||||||
data-i18n="dialog.conferenceReloadTimeLeft"
|
|
||||||
class='reload_overlay_msg'>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
${this.buttonHtml}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the progress indicator position and the label with the time left.
|
|
||||||
*/
|
|
||||||
updateDisplay() {
|
|
||||||
|
|
||||||
APP.translation.translateElement(
|
|
||||||
$("#reloadSecRemaining"), { seconds: this.timeLeft });
|
|
||||||
|
|
||||||
const ratio = (this.timeout - this.timeLeft) / this.timeout;
|
|
||||||
AJS.progressBars.update("#reloadProgressBar", ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the reload countdown with the animation.
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_onShow() {
|
|
||||||
$("#reconnectNow").click(() => {
|
|
||||||
APP.ConferenceUrl.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize displays
|
|
||||||
this.updateDisplay();
|
|
||||||
|
|
||||||
var intervalId = window.setInterval(function() {
|
|
||||||
|
|
||||||
if (this.timeLeft >= 1) {
|
|
||||||
this.timeLeft -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateDisplay();
|
|
||||||
|
|
||||||
if (this.timeLeft === 0) {
|
|
||||||
window.clearInterval(intervalId);
|
|
||||||
APP.ConferenceUrl.reload();
|
|
||||||
}
|
|
||||||
}.bind(this), 1000);
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"The conference will be reloaded after "
|
|
||||||
+ this.timeLeft + " seconds.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the page reload overlay instance.
|
|
||||||
*
|
|
||||||
* {@type PageReloadOverlayImpl}
|
|
||||||
*/
|
|
||||||
let overlay;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the page reload overlay has been displayed.
|
|
||||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
|
|
||||||
* visible or <tt>false</tt> otherwise.
|
|
||||||
*/
|
|
||||||
export function isVisible() {
|
|
||||||
return overlay && overlay.isVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the page reload overlay which will do the conference reload after
|
|
||||||
* the given amount of time.
|
|
||||||
*
|
|
||||||
* @param {number} timeoutSeconds how many seconds before the conference
|
|
||||||
* reload will happen.
|
|
||||||
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's
|
|
||||||
* caused by network related failure or <tt>false</tt> when it's
|
|
||||||
* the infrastructure.
|
|
||||||
* @param {string} reason a label string identifying the reason for the page
|
|
||||||
* reload which will be included in details of the log event
|
|
||||||
*/
|
|
||||||
export function show(timeoutSeconds, isNetworkFailure, reason) {
|
|
||||||
let title;
|
|
||||||
let message;
|
|
||||||
let buttonHtml;
|
|
||||||
let isLightOverlay;
|
|
||||||
|
|
||||||
if (isNetworkFailure) {
|
|
||||||
title = "dialog.conferenceDisconnectTitle";
|
|
||||||
message = "dialog.conferenceDisconnectMsg";
|
|
||||||
buttonHtml
|
|
||||||
= `<button id="reconnectNow" data-i18n="dialog.reconnectNow"
|
|
||||||
class="button-control button-control_primary
|
|
||||||
button-control_center"></button>`;
|
|
||||||
isLightOverlay = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
title = "dialog.conferenceReloadTitle";
|
|
||||||
message = "dialog.conferenceReloadMsg";
|
|
||||||
buttonHtml = "";
|
|
||||||
isLightOverlay = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!overlay) {
|
|
||||||
overlay = new PageReloadOverlayImpl(timeoutSeconds,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
buttonHtml,
|
|
||||||
isLightOverlay);
|
|
||||||
}
|
|
||||||
// Log the page reload event
|
|
||||||
if (!this.isVisible()) {
|
|
||||||
// FIXME (CallStats - issue) this event will not make it to
|
|
||||||
// the CallStats, because the log queue is not flushed, before
|
|
||||||
// "fabric terminated" is sent to the backed
|
|
||||||
APP.conference.logEvent(
|
|
||||||
'page.reload', undefined /* value */, reason /* label */);
|
|
||||||
}
|
|
||||||
overlay.show();
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/* global $, APP */
|
|
||||||
|
|
||||||
import Overlay from '../overlay/Overlay';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An overlay dialog which is shown when a suspended event is detected.
|
|
||||||
*/
|
|
||||||
class SuspendedOverlayImpl extends Overlay{
|
|
||||||
/**
|
|
||||||
* Creates new <tt>SuspendedOverlayImpl</tt>
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
$(document).on('click', '#rejoin', () => {
|
|
||||||
APP.ConferenceUrl.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Constructs overlay body with the message and a button to rejoin.
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
_buildOverlayContent() {
|
|
||||||
return (
|
|
||||||
`<div class="inlay">
|
|
||||||
<span class="inlay__icon icon-microphone"></span>
|
|
||||||
<span class="inlay__icon icon-camera"></span>
|
|
||||||
<h3 class="inlay__title" data-i18n="suspendedoverlay.title"></h3>
|
|
||||||
<button id="rejoin"
|
|
||||||
data-i18n="suspendedoverlay.rejoinKeyTitle"
|
|
||||||
class="inlay__button button-control button-control_primary">
|
|
||||||
</button>
|
|
||||||
</div>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the page suspended overlay instance.
|
|
||||||
*
|
|
||||||
* {@type SuspendedOverlayImpl}
|
|
||||||
*/
|
|
||||||
let overlay;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* Checks whether the page suspended overlay has been displayed.
|
|
||||||
* @return {boolean} <tt>true</tt> if the page suspended overlay is
|
|
||||||
* currently visible or <tt>false</tt> otherwise.
|
|
||||||
*/
|
|
||||||
isVisible() {
|
|
||||||
return overlay && overlay.isVisible();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Shows the page suspended overlay.
|
|
||||||
*/
|
|
||||||
show() {
|
|
||||||
|
|
||||||
if (!overlay) {
|
|
||||||
overlay = new SuspendedOverlayImpl();
|
|
||||||
}
|
|
||||||
overlay.show();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global APP */
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { compose, createStore } from 'redux';
|
import { compose, createStore } from 'redux';
|
||||||
|
@ -300,6 +302,12 @@ export class AbstractApp extends Component {
|
||||||
|
|
||||||
if (typeof store === 'undefined') {
|
if (typeof store === 'undefined') {
|
||||||
store = this._createStore();
|
store = this._createStore();
|
||||||
|
|
||||||
|
// This is temporary workaround to be able to dispatch actions from
|
||||||
|
// non-reactified parts of the code (conference.js for example).
|
||||||
|
// Don't use in the react code!!!
|
||||||
|
// FIXME: remove when the reactification is finished!
|
||||||
|
APP.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
|
|
|
@ -34,7 +34,7 @@ function _addConferenceListeners(conference, dispatch) {
|
||||||
|
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiConferenceEvents.CONFERENCE_FAILED,
|
JitsiConferenceEvents.CONFERENCE_FAILED,
|
||||||
(...args) => dispatch(_conferenceFailed(conference, ...args)));
|
(...args) => dispatch(conferenceFailed(conference, ...args)));
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiConferenceEvents.CONFERENCE_JOINED,
|
JitsiConferenceEvents.CONFERENCE_JOINED,
|
||||||
(...args) => dispatch(_conferenceJoined(conference, ...args)));
|
(...args) => dispatch(_conferenceJoined(conference, ...args)));
|
||||||
|
@ -87,8 +87,9 @@ function _addConferenceListeners(conference, dispatch) {
|
||||||
* conference: JitsiConference,
|
* conference: JitsiConference,
|
||||||
* error: string
|
* error: string
|
||||||
* }}
|
* }}
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
function _conferenceFailed(conference, error) {
|
export function conferenceFailed(conference, error) {
|
||||||
return {
|
return {
|
||||||
type: CONFERENCE_FAILED,
|
type: CONFERENCE_FAILED,
|
||||||
conference,
|
conference,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* global APP */
|
||||||
import { CONNECTION_ESTABLISHED } from '../connection';
|
import { CONNECTION_ESTABLISHED } from '../connection';
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
|
@ -53,7 +54,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
function _connectionEstablished(store, next, action) {
|
function _connectionEstablished(store, next, action) {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
store.dispatch(createConference());
|
// FIXME: workaround for the web version. Currently the creation of the
|
||||||
|
// conference is handled by /conference.js
|
||||||
|
if (!APP) {
|
||||||
|
store.dispatch(createConference());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,13 @@ export function connect() {
|
||||||
|
|
||||||
connection.addEventListener(
|
connection.addEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||||
connectionDisconnected);
|
_onConnectionDisconnected);
|
||||||
connection.addEventListener(
|
connection.addEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||||
connectionEstablished);
|
_onConnectionEstablished);
|
||||||
connection.addEventListener(
|
connection.addEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||||
connectionFailed);
|
_onConnectionFailed);
|
||||||
|
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
|
||||||
|
@ -59,11 +59,12 @@ export function connect() {
|
||||||
*
|
*
|
||||||
* @param {string} message - Disconnect reason.
|
* @param {string} message - Disconnect reason.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
function connectionDisconnected(message: string) {
|
function _onConnectionDisconnected(message: string) {
|
||||||
connection.removeEventListener(
|
connection.removeEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||||
connectionDisconnected);
|
_onConnectionDisconnected);
|
||||||
|
|
||||||
dispatch(_connectionDisconnected(connection, message));
|
dispatch(_connectionDisconnected(connection, message));
|
||||||
}
|
}
|
||||||
|
@ -72,10 +73,11 @@ export function connect() {
|
||||||
* Resolves external promise when connection is established.
|
* Resolves external promise when connection is established.
|
||||||
*
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
function connectionEstablished() {
|
function _onConnectionEstablished() {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
dispatch(_connectionEstablished(connection));
|
dispatch(connectionEstablished(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,11 +85,12 @@ export function connect() {
|
||||||
*
|
*
|
||||||
* @param {JitsiConnectionErrors} err - Connection error.
|
* @param {JitsiConnectionErrors} err - Connection error.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
function connectionFailed(err) {
|
function _onConnectionFailed(err) {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
console.error('CONNECTION FAILED:', err);
|
console.error('CONNECTION FAILED:', err);
|
||||||
dispatch(_connectionFailed(connection, err));
|
dispatch(connectionFailed(connection, err, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,10 +102,10 @@ export function connect() {
|
||||||
function unsubscribe() {
|
function unsubscribe() {
|
||||||
connection.removeEventListener(
|
connection.removeEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||||
connectionEstablished);
|
_onConnectionEstablished);
|
||||||
connection.removeEventListener(
|
connection.removeEventListener(
|
||||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||||
connectionFailed);
|
_onConnectionFailed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -183,13 +186,13 @@ function _connectionDisconnected(connection, message: string) {
|
||||||
*
|
*
|
||||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||||
* established.
|
* established.
|
||||||
* @private
|
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: CONNECTION_ESTABLISHED,
|
* type: CONNECTION_ESTABLISHED,
|
||||||
* connection: JitsiConnection
|
* connection: JitsiConnection
|
||||||
* }}
|
* }}
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
function _connectionEstablished(connection) {
|
export function connectionEstablished(connection: Object) {
|
||||||
return {
|
return {
|
||||||
type: CONNECTION_ESTABLISHED,
|
type: CONNECTION_ESTABLISHED,
|
||||||
connection
|
connection
|
||||||
|
@ -200,18 +203,22 @@ function _connectionEstablished(connection) {
|
||||||
* Create an action for when the signaling connection could not be created.
|
* Create an action for when the signaling connection could not be created.
|
||||||
*
|
*
|
||||||
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
||||||
* @param {string} error - Error message.
|
* @param {string} error - Error.
|
||||||
* @private
|
* @param {string} errorMessage - Error message.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: CONNECTION_FAILED,
|
* type: CONNECTION_FAILED,
|
||||||
* connection: JitsiConnection,
|
* connection: JitsiConnection,
|
||||||
* error: string
|
* error: string,
|
||||||
|
* errorMessage: string
|
||||||
* }}
|
* }}
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
function _connectionFailed(connection, error: string) {
|
export function connectionFailed(
|
||||||
|
connection: Object, error: string, errorMessage: string) {
|
||||||
return {
|
return {
|
||||||
type: CONNECTION_FAILED,
|
type: CONNECTION_FAILED,
|
||||||
connection,
|
connection,
|
||||||
error
|
error,
|
||||||
|
errorMessage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,11 @@ declare var JitsiMeetJS: Object;
|
||||||
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
export {
|
||||||
|
connectionEstablished,
|
||||||
|
connectionFailed
|
||||||
|
} from './actions.native.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens new connection.
|
* Opens new connection.
|
||||||
*
|
*
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { connect, disconnect } from '../../base/connection';
|
||||||
import { Watermarks } from '../../base/react';
|
import { Watermarks } from '../../base/react';
|
||||||
import { FeedbackButton } from '../../feedback';
|
import { FeedbackButton } from '../../feedback';
|
||||||
|
|
||||||
|
import { OverlayContainer } from '../../overlay';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For legacy reasons, inline style for display none.
|
* For legacy reasons, inline style for display none.
|
||||||
*
|
*
|
||||||
|
@ -162,6 +164,7 @@ class Conference extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<OverlayContainer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Symbol } from '../base/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the Redux action which signals that a suspend was detected.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SUSPEND_DETECTED
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const SUSPEND_DETECTED = Symbol('SUSPEND_DETECTED');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the Redux action which signals that the prompt for media
|
||||||
|
* permission is visible or not.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||||
|
* isVisible: {boolean},
|
||||||
|
* browser: {string}
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||||
|
= Symbol('MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED');
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
SUSPEND_DETECTED,
|
||||||
|
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||||
|
} from './actionTypes';
|
||||||
|
import './reducer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that suspend was detected.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: SUSPEND_DETECTED
|
||||||
|
* }}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function suspendDetected() {
|
||||||
|
return {
|
||||||
|
type: SUSPEND_DETECTED
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the prompt for media permission is visible or not.
|
||||||
|
*
|
||||||
|
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||||
|
* permission is visible otherwise the value is false/undefined.
|
||||||
|
* @param {string} browser - The name of the current browser.
|
||||||
|
* @returns {{
|
||||||
|
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||||
|
* isVisible: {boolean},
|
||||||
|
* browser: {string}
|
||||||
|
* }}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function mediaPermissionPromptVisibilityChanged(isVisible, browser) {
|
||||||
|
return {
|
||||||
|
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||||
|
isVisible,
|
||||||
|
browser
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/* global $, APP */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements an abstract React Component for overlay - the components which
|
||||||
|
* are displayed on top of the application covering the whole screen.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
export default class AbstractOverlay extends Component {
|
||||||
|
/**
|
||||||
|
* Initializes a new AbstractOverlay instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
/**
|
||||||
|
* Indicates the css style of the overlay. if true - lighter and
|
||||||
|
* darker otherwise.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isLightOverlay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method which should be used by subclasses to provide the overlay
|
||||||
|
* content.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_renderOverlayContent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is executed when comonent is mounted.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
// XXX Temporary solution until we add React translation.
|
||||||
|
APP.translation.translateElement($('#overlay'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the page.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_reconnectNow() {
|
||||||
|
// FIXME: In future we should dispatch an action here that will result
|
||||||
|
// in reload.
|
||||||
|
APP.ConferenceUrl.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const containerClass = this.state.isLightOverlay
|
||||||
|
? 'overlay__container-light' : 'overlay__container';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className = { containerClass }
|
||||||
|
id = 'overlay'>
|
||||||
|
<div className = 'overlay__content'>
|
||||||
|
{ this._renderOverlayContent() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
/* global APP */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import PageReloadOverlay from './PageReloadOverlay';
|
||||||
|
import SuspendedOverlay from './SuspendedOverlay';
|
||||||
|
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React Component that will display the correct overlay when
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
class OverlayContainer extends Component {
|
||||||
|
/**
|
||||||
|
* OverlayContainer component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The browser which is used currently.
|
||||||
|
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_browser: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the status of
|
||||||
|
* JitsiConnection object has been "established" or not.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_connectionEstablished: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether a critical error for reload
|
||||||
|
* has been received.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_haveToReload: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the reload was caused by
|
||||||
|
* network failure.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_isNetworkFailure: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the GUM permissions prompt
|
||||||
|
* is displayed or not.
|
||||||
|
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_mediaPermissionPromptVisible: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason for the error that will cause the reload.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_reason: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the GUM permissions prompt
|
||||||
|
* is displayed or not.
|
||||||
|
* NOTE: Used by SuspendedOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_suspendDetected: React.PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component method that executes once component is updated.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
componentDidUpdate() {
|
||||||
|
// FIXME: Temporary workaround until everything is moved to react.
|
||||||
|
APP.UI.overlayVisible
|
||||||
|
= (this.props._connectionEstablished && this.props._haveToReload)
|
||||||
|
|| this.props._suspendDetected
|
||||||
|
|| this.props._mediaPermissionPromptVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
if (this.props._connectionEstablished && this.props._haveToReload) {
|
||||||
|
return (
|
||||||
|
<PageReloadOverlay
|
||||||
|
isNetworkFailure = { this.props._isNetworkFailure }
|
||||||
|
reason = { this.props._reason } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props._suspendDetected) {
|
||||||
|
return (
|
||||||
|
<SuspendedOverlay />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props._mediaPermissionPromptVisible) {
|
||||||
|
return (
|
||||||
|
<UserMediaPermissionsOverlay
|
||||||
|
browser = { this.props._browser } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated OverlayContainer's props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @returns {{
|
||||||
|
* _browser: string,
|
||||||
|
* _connectionEstablished: bool,
|
||||||
|
* _haveToReload: bool,
|
||||||
|
* _isNetworkFailure: bool,
|
||||||
|
* _mediaPermissionPromptVisible: bool,
|
||||||
|
* _reason: string,
|
||||||
|
* _suspendDetected: bool
|
||||||
|
* }}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* The browser which is used currently.
|
||||||
|
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_browser: state['features/overlay'].browser,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the status of
|
||||||
|
* JitsiConnection object has been "established" or not.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_connectionEstablished:
|
||||||
|
state['features/overlay'].connectionEstablished,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether a critical error for reload
|
||||||
|
* has been received.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_haveToReload: state['features/overlay'].haveToReload,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the reload was caused by
|
||||||
|
* network failure.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_isNetworkFailure: state['features/overlay'].isNetworkFailure,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the GUM permissions prompt
|
||||||
|
* is displayed or not.
|
||||||
|
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_mediaPermissionPromptVisible:
|
||||||
|
state['features/overlay'].mediaPermissionPromptVisible,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason for the error that will cause the reload.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_reason: state['features/overlay'].reason,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the GUM permissions prompt
|
||||||
|
* is displayed or not.
|
||||||
|
* NOTE: Used by SuspendedOverlay only.
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
_suspendDetected: state['features/overlay'].suspendDetected
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(OverlayContainer);
|
|
@ -0,0 +1,298 @@
|
||||||
|
/* global APP, AJS */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { randomInt } from '../../base/util/randomUtil';
|
||||||
|
|
||||||
|
import AbstractOverlay from './AbstractOverlay';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React Component for the reload timer. Starts counter from
|
||||||
|
* props.start, adds props.step to the current value on every props.interval
|
||||||
|
* seconds until the current value reaches props.end. Also displays progress
|
||||||
|
* bar.
|
||||||
|
*/
|
||||||
|
class ReloadTimer extends Component {
|
||||||
|
/**
|
||||||
|
* ReloadTimer component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The end of the timer. When this.state.current reaches this
|
||||||
|
* value the timer will stop and call onFinish function.
|
||||||
|
* @public
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
end: React.PropTypes.number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval in sec for adding this.state.step to this.state.current
|
||||||
|
* @public
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
interval: React.PropTypes.number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function that will be executed when timer finish (when
|
||||||
|
* this.state.current === this.props.end)
|
||||||
|
*/
|
||||||
|
onFinish: React.PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start of the timer. The initial value for this.state.current.
|
||||||
|
* @public
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
start: React.PropTypes.number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value which will be added to this.state.current on every step.
|
||||||
|
* @public
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
step: React.PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new ReloadTimer instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: this.props.start,
|
||||||
|
time: Math.abs(this.props.end - this.props.start)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component method that executes once component is mounted.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
AJS.progressBars.update('#reloadProgressBar', 0);
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
if (this.state.current === this.props.end) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
this.props.onFinish();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState((prevState, props) => {
|
||||||
|
return { current: prevState.current + props.step };
|
||||||
|
});
|
||||||
|
}, Math.abs(this.props.interval) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component method that executes once component is updated.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
componentDidUpdate() {
|
||||||
|
AJS.progressBars.update('#reloadProgressBar',
|
||||||
|
Math.abs(this.state.current - this.props.start) / this.state.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className = 'aui-progress-indicator'
|
||||||
|
id = 'reloadProgressBar'>
|
||||||
|
<span className = 'aui-progress-indicator-value' />
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className = 'reload_overlay_text'
|
||||||
|
id = 'reloadSeconds'>
|
||||||
|
{ this.state.current }
|
||||||
|
<span data-i18n = 'dialog.conferenceReloadTimeLeft' />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React Component for page reload overlay. Shown before
|
||||||
|
* the conference is reloaded. Shows a warning message and counts down towards
|
||||||
|
* the reload.
|
||||||
|
*/
|
||||||
|
export default class PageReloadOverlay extends AbstractOverlay {
|
||||||
|
/**
|
||||||
|
* PageReloadOverlay component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the reload was caused by
|
||||||
|
* network failure.
|
||||||
|
* @public
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isNetworkFailure: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reason for the error that will cause the reload.
|
||||||
|
* NOTE: Used by PageReloadOverlay only.
|
||||||
|
* @public
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
reason: React.PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new PageReloadOverlay instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long the overlay dialog will be
|
||||||
|
* displayed, before the conference will be reloaded.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
const timeoutSeconds = 10 + randomInt(0, 20);
|
||||||
|
|
||||||
|
let isLightOverlay, message, title;
|
||||||
|
|
||||||
|
if (this.props.isNetworkFailure) {
|
||||||
|
title = 'dialog.conferenceDisconnectTitle';
|
||||||
|
message = 'dialog.conferenceDisconnectMsg';
|
||||||
|
isLightOverlay = true;
|
||||||
|
} else {
|
||||||
|
title = 'dialog.conferenceReloadTitle';
|
||||||
|
message = 'dialog.conferenceReloadMsg';
|
||||||
|
isLightOverlay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the css style of the overlay. if true - lighter and
|
||||||
|
* darker otherwise.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isLightOverlay,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The translation key for the title of the overlay
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
message,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long the overlay dialog will be
|
||||||
|
* displayed, before the conference will be reloaded.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
timeoutSeconds,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The translation key for the title of the overlay
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the button for relaod the page if necessary.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderButton() {
|
||||||
|
if (this.props.isNetworkFailure) {
|
||||||
|
const cName = 'button-control button-control_primary '
|
||||||
|
+ 'button-control_center';
|
||||||
|
|
||||||
|
/* eslint-disable react/jsx-handler-names */
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className = { cName }
|
||||||
|
data-i18n = 'dialog.reconnectNow'
|
||||||
|
id = 'reconnectNow'
|
||||||
|
onClick = { this._reconnectNow } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs overlay body with the warning message and count down towards
|
||||||
|
* the conference reload.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @override
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_renderOverlayContent() {
|
||||||
|
|
||||||
|
/* eslint-disable react/jsx-handler-names */
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'inlay'>
|
||||||
|
<span
|
||||||
|
className = 'reload_overlay_title'
|
||||||
|
data-i18n = { this.state.title } />
|
||||||
|
<span
|
||||||
|
className = 'reload_overlay_text'
|
||||||
|
data-i18n = { this.state.message } />
|
||||||
|
<ReloadTimer
|
||||||
|
end = { 0 }
|
||||||
|
interval = { 1 }
|
||||||
|
onFinish = { this._reconnectNow }
|
||||||
|
start = { this.state.timeoutSeconds }
|
||||||
|
step = { -1 } />
|
||||||
|
{ this._renderButton() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is executed when comonent is mounted.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
super.componentDidMount();
|
||||||
|
|
||||||
|
// FIXME (CallStats - issue) this event will not make it to
|
||||||
|
// the CallStats, because the log queue is not flushed, before
|
||||||
|
// "fabric terminated" is sent to the backed
|
||||||
|
// FIXME: We should dispatch action for this
|
||||||
|
APP.conference.logEvent('page.reload', undefined /* value */,
|
||||||
|
this.props.reason /* label */);
|
||||||
|
logger.info(`The conference will be reloaded after
|
||||||
|
${this.state.timeoutSeconds} seconds.`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AbstractOverlay from './AbstractOverlay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React Component for suspended overlay. Shown when suspended
|
||||||
|
* is detected.
|
||||||
|
*/
|
||||||
|
export default class SuspendedOverlay extends AbstractOverlay {
|
||||||
|
/**
|
||||||
|
* Constructs overlay body with the message and a button to rejoin.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @override
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_renderOverlayContent() {
|
||||||
|
const btnClass = 'inlay__button button-control button-control_primary';
|
||||||
|
|
||||||
|
/* eslint-disable react/jsx-handler-names */
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'inlay'>
|
||||||
|
<span className = 'inlay__icon icon-microphone' />
|
||||||
|
<span className = 'inlay__icon icon-camera' />
|
||||||
|
<h3
|
||||||
|
className = 'inlay__title'
|
||||||
|
data-i18n = 'suspendedoverlay.title' />
|
||||||
|
<button
|
||||||
|
className = { btnClass }
|
||||||
|
data-i18n = 'suspendedoverlay.rejoinKeyTitle'
|
||||||
|
id = 'rejoin'
|
||||||
|
onClick = { this._reconnectNow } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
/* global interfaceConfig */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AbstractOverlay from './AbstractOverlay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React Component for overlay with guidance how to proceed with
|
||||||
|
* gUM prompt.
|
||||||
|
*/
|
||||||
|
export default class UserMediaPermissionsOverlay extends AbstractOverlay {
|
||||||
|
/**
|
||||||
|
* UserMediaPermissionsOverlay component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The browser which is used currently. The text is different for every
|
||||||
|
* browser.
|
||||||
|
* @public
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
browser: React.PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new SuspendedOverlay instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
/**
|
||||||
|
* The src value of the image for the policy logo.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
policyLogoSrc: interfaceConfig.POLICY_LOGO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs overlay body with the message with guidance how to proceed
|
||||||
|
* with gUM prompt.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @override
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_renderOverlayContent() {
|
||||||
|
const textKey = `userMedia.${this.props.browser}GrantPermissions`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className = 'inlay'>
|
||||||
|
<span className = 'inlay__icon icon-microphone' />
|
||||||
|
<span className = 'inlay__icon icon-camera' />
|
||||||
|
<h3
|
||||||
|
className = 'inlay__title'
|
||||||
|
data-i18n = 'startupoverlay.title'
|
||||||
|
data-i18n-options
|
||||||
|
= '{"postProcess": "resolveAppName"}' />
|
||||||
|
<span
|
||||||
|
className = 'inlay__text'
|
||||||
|
data-i18n = { `[html]${textKey}` } />
|
||||||
|
</div>
|
||||||
|
<div className = 'policy overlay__policy'>
|
||||||
|
<p
|
||||||
|
className = 'policy__text'
|
||||||
|
data-i18n = '[html]startupoverlay.policyText' />
|
||||||
|
{ this._renderPolicyLogo() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the policy logo.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderPolicyLogo() {
|
||||||
|
if (this.state.policyLogoSrc) {
|
||||||
|
return (
|
||||||
|
<div className = 'policy__logo'>
|
||||||
|
<img src = { this.state.policyLogoSrc } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as OverlayContainer } from './OverlayContainer';
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './components';
|
||||||
|
export * from './actions';
|
|
@ -0,0 +1,145 @@
|
||||||
|
/* global JitsiMeetJS */
|
||||||
|
|
||||||
|
import { CONFERENCE_FAILED } from '../base/conference';
|
||||||
|
import {
|
||||||
|
CONNECTION_ESTABLISHED,
|
||||||
|
CONNECTION_FAILED
|
||||||
|
} from '../base/connection';
|
||||||
|
import {
|
||||||
|
ReducerRegistry,
|
||||||
|
setStateProperty,
|
||||||
|
setStateProperties
|
||||||
|
} from '../base/redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||||
|
SUSPEND_DETECTED
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the Redux actions of the feature overlay.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/overlay', (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case CONFERENCE_FAILED:
|
||||||
|
return _conferenceFailed(state, action);
|
||||||
|
|
||||||
|
case CONNECTION_ESTABLISHED:
|
||||||
|
return _connectionEstablished(state, action);
|
||||||
|
|
||||||
|
case CONNECTION_FAILED:
|
||||||
|
return _connectionFailed(state, action);
|
||||||
|
|
||||||
|
case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED:
|
||||||
|
return _mediaPermissionPromptVisibilityChanged(state, action);
|
||||||
|
|
||||||
|
case SUSPEND_DETECTED:
|
||||||
|
return _suspendDetected(state, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action CONFERENCE_FAILED of the feature
|
||||||
|
* overlay.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature overlay.
|
||||||
|
* @param {Action} action - The Redux action CONFERENCE_FAILED to reduce.
|
||||||
|
* @returns {Object} The new state of the feature base/connection after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _conferenceFailed(state, action) {
|
||||||
|
const ConferenceErrors = JitsiMeetJS.errors.conference;
|
||||||
|
|
||||||
|
if (action.error === ConferenceErrors.FOCUS_LEFT
|
||||||
|
|| action.error === ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE) {
|
||||||
|
return setStateProperties(state, {
|
||||||
|
haveToReload: true,
|
||||||
|
isNetworkFailure: false,
|
||||||
|
reason: action.errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action CONNECTION_ESTABLISHED of the feature
|
||||||
|
* overlay.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature overlay.
|
||||||
|
* @returns {Object} The new state of the feature overlay after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _connectionEstablished(state) {
|
||||||
|
return setStateProperty(state, 'connectionEstablished', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action CONNECTION_FAILED of the feature
|
||||||
|
* overlay.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature overlay.
|
||||||
|
* @param {Action} action - The Redux action CONNECTION_FAILED to reduce.
|
||||||
|
* @returns {Object} The new state of the feature overlay after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _connectionFailed(state, action) {
|
||||||
|
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||||
|
|
||||||
|
switch (action.error) {
|
||||||
|
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||||
|
case ConnectionErrors.OTHER_ERROR:
|
||||||
|
case ConnectionErrors.SERVER_ERROR: {
|
||||||
|
logger.error(`XMPP connection error: ${action.errorMessage}`);
|
||||||
|
|
||||||
|
// From all of the cases above only CONNECTION_DROPPED_ERROR
|
||||||
|
// is considered a network type of failure
|
||||||
|
return setStateProperties(state, {
|
||||||
|
haveToReload: true,
|
||||||
|
isNetworkFailure:
|
||||||
|
action.error === ConnectionErrors.CONNECTION_DROPPED_ERROR,
|
||||||
|
reason: `xmpp-conn-dropped: ${action.errorMessage}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||||
|
* of the feature overlay.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature overlay.
|
||||||
|
* @param {Action} action - The Redux action to reduce.
|
||||||
|
* @returns {Object} The new state of the feature overlay after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _mediaPermissionPromptVisibilityChanged(state, action) {
|
||||||
|
return setStateProperties(state, {
|
||||||
|
mediaPermissionPromptVisible: action.isVisible,
|
||||||
|
browser: action.browser
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action SUSPEND_DETECTED of the feature
|
||||||
|
* overlay.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature overlay.
|
||||||
|
* @returns {Object} The new state of the feature overlay after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _suspendDetected(state) {
|
||||||
|
return setStateProperty(state, 'suspendDetected', true);
|
||||||
|
}
|
Loading…
Reference in New Issue