Browse Source

ref(overlay): The overlays to use React

j8
hristoterezov 8 years ago
parent
commit
92d0589a37

+ 36
- 50
conference.js View File

@@ -20,6 +20,12 @@ import analytics from './modules/analytics/analytics';
20 20
 
21 21
 import EventEmitter from "events";
22 22
 
23
+import { conferenceFailed } from './react/features/base/conference';
24
+import {
25
+    mediaPermissionPromptVisibilityChanged,
26
+    suspendDetected
27
+} from './react/features/overlay';
28
+
23 29
 const ConnectionEvents = JitsiMeetJS.events.connection;
24 30
 const ConnectionErrors = JitsiMeetJS.errors.connection;
25 31
 
@@ -91,7 +97,10 @@ function createInitialLocalTracksAndConnect(roomName) {
91 97
 
92 98
     JitsiMeetJS.mediaDevices.addEventListener(
93 99
         JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
94
-        browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
100
+        browser =>
101
+            APP.store.dispatch(
102
+                mediaPermissionPromptVisibilityChanged(true, browser))
103
+    );
95 104
 
96 105
     // First try to retrieve both audio and video.
97 106
     let tryCreateLocalTracks = createLocalTracks(
@@ -109,8 +118,7 @@ function createInitialLocalTracksAndConnect(roomName) {
109 118
 
110 119
     return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
111 120
         .then(([tracks, con]) => {
112
-            APP.UI.hideUserMediaPermissionsGuidanceOverlay();
113
-
121
+            APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
114 122
             if (audioAndVideoError) {
115 123
                 if (audioOnlyError) {
116 124
                     // If both requests for 'audio' + 'video' and 'audio' only
@@ -334,6 +342,7 @@ class ConferenceConnector {
334 342
         this._reject(err);
335 343
     }
336 344
     _onConferenceFailed(err, ...params) {
345
+        APP.store.dispatch(conferenceFailed(room, err, ...params));
337 346
         logger.error('CONFERENCE FAILED:', err, ...params);
338 347
         APP.UI.hideRingOverLay();
339 348
         switch (err) {
@@ -408,8 +417,6 @@ class ConferenceConnector {
408 417
             // the app. Both the errors above are unrecoverable from the library
409 418
             // perspective.
410 419
             room.leave().then(() => connection.disconnect());
411
-            APP.UI.showPageReloadOverlay(
412
-                false /* not a network type of failure */, err);
413 420
             break;
414 421
 
415 422
         case ConferenceErrors.CONFERENCE_MAX_USERS:
@@ -466,6 +473,26 @@ function disconnect() {
466 473
     return Promise.resolve();
467 474
 }
468 475
 
476
+/**
477
+ * Handles CONNECTION_FAILED events from lib-jitsi-meet.
478
+ * @param {JitsiMeetJS.connection.error} error the error reported.
479
+ * @returns {void}
480
+ * @private
481
+ */
482
+function _connectionFailedHandler (error) {
483
+    switch (error) {
484
+    case ConnectionErrors.CONNECTION_DROPPED_ERROR:
485
+    case ConnectionErrors.OTHER_ERROR:
486
+    case ConnectionErrors.SERVER_ERROR: {
487
+        APP.connection.removeEventListener( ConnectionEvents.CONNECTION_FAILED,
488
+            _connectionFailedHandler);
489
+        if (room)
490
+            room.leave();
491
+        break;
492
+    }
493
+    }
494
+}
495
+
469 496
 export default {
470 497
     isModerator: false,
471 498
     audioMuted: false,
@@ -518,11 +545,13 @@ export default {
518 545
                 return createInitialLocalTracksAndConnect(options.roomName);
519 546
             }).then(([tracks, con]) => {
520 547
                 logger.log('initialized with %s local tracks', tracks.length);
548
+                con.addEventListener(
549
+                    ConnectionEvents.CONNECTION_FAILED,
550
+                    _connectionFailedHandler);
521 551
                 APP.connection = connection = con;
522 552
                 this.isDesktopSharingEnabled =
523 553
                     JitsiMeetJS.isDesktopSharingEnabled();
524 554
                 APP.remoteControl.init();
525
-                this._bindConnectionFailedHandler(con);
526 555
                 this._createRoom(tracks);
527 556
 
528 557
                 if (UIUtil.isButtonEnabled('contacts')
@@ -561,47 +590,6 @@ export default {
561 590
     isLocalId (id) {
562 591
         return this.getMyUserId() === id;
563 592
     },
564
-    /**
565
-     * Binds a handler that will handle the case when the connection is dropped
566
-     * in the middle of the conference.
567
-     * @param {JitsiConnection} connection the connection to which the handler
568
-     * will be bound to.
569
-     * @private
570
-     */
571
-    _bindConnectionFailedHandler (connection) {
572
-        const handler = function (error, errMsg) {
573
-            /* eslint-disable no-case-declarations */
574
-            switch (error) {
575
-                case ConnectionErrors.CONNECTION_DROPPED_ERROR:
576
-                case ConnectionErrors.OTHER_ERROR:
577
-                case ConnectionErrors.SERVER_ERROR:
578
-
579
-                    logger.error("XMPP connection error: " + errMsg);
580
-
581
-                    // From all of the cases above only CONNECTION_DROPPED_ERROR
582
-                    // is considered a network type of failure
583
-                    const isNetworkFailure
584
-                        = error === ConnectionErrors.CONNECTION_DROPPED_ERROR;
585
-
586
-                    APP.UI.showPageReloadOverlay(
587
-                        isNetworkFailure,
588
-                        "xmpp-conn-dropped:" + errMsg);
589
-
590
-                    connection.removeEventListener(
591
-                        ConnectionEvents.CONNECTION_FAILED, handler);
592
-
593
-                    // FIXME it feels like the conference should be stopped
594
-                    // by lib-jitsi-meet
595
-                    if (room)
596
-                        room.leave();
597
-
598
-                    break;
599
-            }
600
-            /* eslint-enable no-case-declarations */
601
-        };
602
-        connection.addEventListener(
603
-            ConnectionEvents.CONNECTION_FAILED, handler);
604
-    },
605 593
     /**
606 594
      * Simulates toolbar button click for audio mute. Used by shortcuts and API.
607 595
      * @param mute true for mute and false for unmute.
@@ -1365,6 +1353,7 @@ export default {
1365 1353
         });
1366 1354
 
1367 1355
         room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
1356
+            APP.store.dispatch(suspendDetected());
1368 1357
             // After wake up, we will be in a state where conference is left
1369 1358
             // there will be dialog shown to user.
1370 1359
             // We do not want video/audio as we show an overlay and after it
@@ -1385,9 +1374,6 @@ export default {
1385 1374
             if (localAudio) {
1386 1375
                 localAudio.dispose();
1387 1376
             }
1388
-
1389
-            // show overlay
1390
-            APP.UI.showSuspendedOverlay();
1391 1377
         });
1392 1378
 
1393 1379
         room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {

+ 23
- 0
connection.js View File

@@ -4,6 +4,11 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
4 4
 import AuthHandler from './modules/UI/authentication/AuthHandler';
5 5
 import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
6 6
 
7
+import {
8
+    connectionEstablished,
9
+    connectionFailed
10
+} from './react/features/base/connection';
11
+
7 12
 const ConnectionEvents = JitsiMeetJS.events.connection;
8 13
 const ConnectionErrors = JitsiMeetJS.errors.connection;
9 14
 
@@ -67,6 +72,23 @@ function connect(id, password, roomName) {
67 72
         connection.addEventListener(
68 73
             ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
69 74
         );
75
+        connection.addEventListener(
76
+            ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
77
+
78
+        function connectionFailedHandler (error, errMsg) {
79
+            APP.store.dispatch(connectionFailed(connection, error, errMsg));
80
+
81
+            switch (error) {
82
+            case ConnectionErrors.CONNECTION_DROPPED_ERROR:
83
+            case ConnectionErrors.OTHER_ERROR:
84
+            case ConnectionErrors.SERVER_ERROR: {
85
+                connection.removeEventListener(
86
+                    ConnectionEvents.CONNECTION_FAILED,
87
+                    connectionFailedHandler);
88
+                break;
89
+            }
90
+            }
91
+        }
70 92
 
71 93
         function unsubscribe() {
72 94
             connection.removeEventListener(
@@ -80,6 +102,7 @@ function connect(id, password, roomName) {
80 102
         }
81 103
 
82 104
         function handleConnectionEstablished() {
105
+            APP.store.dispatch(connectionEstablished(connection));
83 106
             unsubscribe();
84 107
             resolve(connection);
85 108
         }

+ 2
- 2
css/reload_overlay/_reload_overlay.scss View File

@@ -4,7 +4,7 @@
4 4
     line-height: 20px;
5 5
 }
6 6
 
7
-.reload_overlay_msg {
7
+.reload_overlay_text {
8 8
     display: block;
9 9
     font-size: 12px;
10 10
     line-height: 30px;
@@ -13,4 +13,4 @@
13 13
 #reloadProgressBar {
14 14
     width: 180px;
15 15
     margin: 5px auto;
16
-}
16
+}

+ 12
- 46
modules/UI/UI.js View File

@@ -15,18 +15,13 @@ import UIEvents from "../../service/UI/UIEvents";
15 15
 import EtherpadManager from './etherpad/Etherpad';
16 16
 import SharedVideoManager from './shared_video/SharedVideo';
17 17
 import Recording from "./recording/Recording";
18
-import GumPermissionsOverlay
19
-    from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
20 18
 
21
-import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
22
-import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
23 19
 import VideoLayout from "./videolayout/VideoLayout";
24 20
 import FilmStrip from "./videolayout/FilmStrip";
25 21
 import SettingsMenu from "./side_pannels/settings/SettingsMenu";
26 22
 import Profile from "./side_pannels/profile/Profile";
27 23
 import Settings from "./../settings/Settings";
28 24
 import RingOverlay from "./ring_overlay/RingOverlay";
29
-import { randomInt } from "../../react/features/base/util/randomUtil";
30 25
 import UIErrors from './UIErrors';
31 26
 import { debounce } from "../util/helpers";
32 27
 
@@ -40,6 +35,17 @@ import FollowMe from "../FollowMe";
40 35
 var eventEmitter = new EventEmitter();
41 36
 UI.eventEmitter = eventEmitter;
42 37
 
38
+/**
39
+ * Whether an overlay is visible or not.
40
+ *
41
+ * FIXME: This is temporary solution. Don't use this variable!
42
+ * Should be removed when all the code is move to react.
43
+ *
44
+ * @type {boolean}
45
+ * @public
46
+ */
47
+UI.overlayVisible = false;
48
+
43 49
 let etherpadManager;
44 50
 let sharedVideoManager;
45 51
 
@@ -1087,20 +1093,6 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
1087 1093
     );
1088 1094
 };
1089 1095
 
1090
-/**
1091
- * Notify the user that the video conferencing service is badly broken and
1092
- * the page should be reloaded.
1093
- *
1094
- * @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's caused by
1095
- * network related failure or <tt>false</tt> when it's the infrastructure.
1096
- * @param {string} a label string identifying the reason for the page reload
1097
- * which will be included in details of the log event.
1098
- */
1099
-UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
1100
-    // Reload the page after 10 - 30 seconds
1101
-    PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
1102
-};
1103
-
1104 1096
 /**
1105 1097
  * Updates auth info on the UI.
1106 1098
  * @param {boolean} isAuthEnabled if authentication is enabled
@@ -1414,10 +1406,7 @@ UI.hideRingOverLay = function () {
1414 1406
  * @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
1415 1407
  */
1416 1408
 UI.isOverlayVisible = function () {
1417
-    return RingOverlay.isVisible()
1418
-        || SuspendedOverlay.isVisible()
1419
-        || PageReloadOverlay.isVisible()
1420
-        || GumPermissionsOverlay.isVisible();
1409
+    return RingOverlay.isVisible() || this.overlayVisible;
1421 1410
 };
1422 1411
 
1423 1412
 /**
@@ -1429,29 +1418,6 @@ UI.isRingOverlayVisible = function () {
1429 1418
     return RingOverlay.isVisible();
1430 1419
 };
1431 1420
 
1432
-/**
1433
- * Shows browser-specific overlay with guidance how to proceed with gUM prompt.
1434
- * @param {string} browser - name of browser for which to show the guidance
1435
- *      overlay.
1436
- */
1437
-UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
1438
-    GumPermissionsOverlay.show(browser);
1439
-};
1440
-
1441
-/**
1442
- * Shows suspended overlay with a button to rejoin conference.
1443
- */
1444
-UI.showSuspendedOverlay = function () {
1445
-    SuspendedOverlay.show();
1446
-};
1447
-
1448
-/**
1449
- * Hides browser-specific overlay with guidance how to proceed with gUM prompt.
1450
- */
1451
-UI.hideUserMediaPermissionsGuidanceOverlay = function () {
1452
-    GumPermissionsOverlay.hide();
1453
-};
1454
-
1455 1421
 /**
1456 1422
  * Handles user's features changes.
1457 1423
  */

+ 0
- 90
modules/UI/gum_overlay/UserMediaPermissionsGuidanceOverlay.js View File

@@ -1,90 +0,0 @@
1
-/* global interfaceConfig */
2
-
3
-import Overlay from '../overlay/Overlay';
4
-
5
-/**
6
- * An overlay with guidance how to proceed with gUM prompt.
7
- */
8
-class GUMOverlayImpl extends Overlay {
9
-
10
-    /**
11
-     * Constructs overlay with guidance how to proceed with gUM prompt.
12
-     * @param {string} browser - name of browser for which to construct the
13
-     *     guidance overlay.
14
-     * @override
15
-     */
16
-    constructor(browser) {
17
-        super();
18
-        this.browser = browser;
19
-    }
20
-
21
-    /**
22
-     * @inheritDoc
23
-     */
24
-    _buildOverlayContent() {
25
-        let textKey = `userMedia.${this.browser}GrantPermissions`;
26
-        let titleKey = 'startupoverlay.title';
27
-        let titleOptions = '{ "postProcess": "resolveAppName" }';
28
-        let policyTextKey = 'startupoverlay.policyText';
29
-        let policyLogo = '';
30
-        let policyLogoSrc = interfaceConfig.POLICY_LOGO;
31
-        if (policyLogoSrc) {
32
-            policyLogo += (
33
-                `<div class="policy__logo">
34
-                    <img src="${policyLogoSrc}"/>
35
-                </div>`
36
-            );
37
-        }
38
-
39
-        return (
40
-            `<div class="inlay">
41
-                <span class="inlay__icon icon-microphone"></span>
42
-                <span class="inlay__icon icon-camera"></span>
43
-                <h3 class="inlay__title" data-i18n="${titleKey}"
44
-                    data-i18n-options='${titleOptions}'></h3>
45
-                <span class='inlay__text'data-i18n='[html]${textKey}'></span>
46
-            </div>
47
-            <div class="policy overlay__policy">
48
-                <p class="policy__text" data-i18n="[html]${policyTextKey}"></p>
49
-                ${policyLogo}
50
-            </div>`
51
-        );
52
-    }
53
-}
54
-
55
-/**
56
- * Stores GUM overlay instance.
57
- * @type {GUMOverlayImpl}
58
- */
59
-let overlay;
60
-
61
-export default {
62
-    /**
63
-     * Checks whether the overlay is currently visible.
64
-     * @return {boolean} <tt>true</tt> if the overlay is visible
65
-     * or <tt>false</tt> otherwise.
66
-     */
67
-    isVisible () {
68
-        return overlay && overlay.isVisible();
69
-    },
70
-    /**
71
-     * Shows browser-specific overlay with guidance how to proceed with
72
-     * gUM prompt.
73
-     * @param {string} browser - name of browser for which to show the
74
-     *      guidance overlay.
75
-     */
76
-    show(browser) {
77
-        if (!overlay) {
78
-            overlay = new GUMOverlayImpl(browser);
79
-        }
80
-        overlay.show();
81
-    },
82
-
83
-    /**
84
-     * Hides browser-specific overlay with guidance how to proceed with
85
-     * gUM prompt.
86
-     */
87
-    hide() {
88
-        overlay && overlay.hide();
89
-    }
90
-};

+ 0
- 94
modules/UI/overlay/Overlay.js View File

@@ -1,94 +0,0 @@
1
-/* global $, APP */
2
-
3
-/**
4
- * Base class for overlay components - the components which are displayed on
5
- * top of the application with semi-transparent background covering the whole
6
- * screen.
7
- */
8
-export default class Overlay{
9
-    /**
10
-     * Creates new <tt>Overlay</tt> instance.
11
-     */
12
-    constructor() {
13
-        /**
14
-         *
15
-         * @type {jQuery}
16
-         */
17
-        this.$overlay = null;
18
-
19
-        /**
20
-         * Indicates if this overlay should use the light look & feel or the
21
-         * standard one.
22
-         * @type {boolean}
23
-         */
24
-        this.isLightOverlay = false;
25
-    }
26
-    /**
27
-     * Template method which should be used by subclasses to provide the overlay
28
-     * content. The contents provided by this method are later subject to
29
-     * the translation using {@link APP.translation.translateElement}.
30
-     * @return {string} HTML representation of the overlay dialog contents.
31
-     * @protected
32
-     */
33
-    _buildOverlayContent() {
34
-        return '';
35
-    }
36
-    /**
37
-     * Constructs the HTML body of the overlay dialog.
38
-     *
39
-     * @private
40
-     */
41
-    _buildOverlayHtml() {
42
-
43
-        let overlayContent = this._buildOverlayContent();
44
-
45
-        let containerClass = this.isLightOverlay    ? "overlay__container-light"
46
-                                                    : "overlay__container";
47
-
48
-        this.$overlay = $(`
49
-            <div class=${containerClass}>
50
-                <div class='overlay__content'>
51
-                    ${overlayContent}
52
-                </div>
53
-            </div>`);
54
-
55
-        APP.translation.translateElement(this.$overlay);
56
-    }
57
-    /**
58
-     * Checks whether the page reload overlay has been displayed.
59
-     * @return {boolean} <tt>true</tt> if the page reload overlay is currently
60
-     * visible or <tt>false</tt> otherwise.
61
-     */
62
-    isVisible() {
63
-        return this.$overlay && this.$overlay.parents('body').length > 0;
64
-    }
65
-    /**
66
-     * Template method called just after the overlay is displayed for the first
67
-     * time.
68
-     * @protected
69
-     */
70
-    _onShow() {
71
-        // To be overridden by subclasses.
72
-    }
73
-    /**
74
-     * Shows the overlay dialog and attaches the underlying HTML representation
75
-     * to the DOM.
76
-     */
77
-    show() {
78
-
79
-        !this.$overlay && this._buildOverlayHtml();
80
-
81
-        if (!this.isVisible()) {
82
-            this.$overlay.appendTo('body');
83
-            this._onShow();
84
-        }
85
-    }
86
-
87
-    /**
88
-     * Hides the overlay dialog and detaches it's HTML representation from
89
-     * the DOM.
90
-     */
91
-    hide() {
92
-        this.$overlay && this.$overlay.detach();
93
-    }
94
-}

+ 0
- 175
modules/UI/reload_overlay/PageReloadOverlay.js View File

@@ -1,175 +0,0 @@
1
-/* global $, APP, AJS */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3
-
4
-import Overlay from "../overlay/Overlay";
5
-
6
-/**
7
- * An overlay dialog which is shown before the conference is reloaded. Shows
8
- * a warning message and counts down towards the reload.
9
- */
10
-class PageReloadOverlayImpl extends Overlay{
11
-    /**
12
-     * Creates new <tt>PageReloadOverlayImpl</tt>
13
-     * @param {number} timeoutSeconds how long the overlay dialog will be
14
-     * displayed, before the conference will be reloaded.
15
-     * @param {string} title the title of the overlay message
16
-     * @param {string} message the message of the overlay
17
-     * @param {string} buttonHtml the button html or an empty string if there's
18
-     * no button
19
-     * @param {boolean} isLightOverlay indicates if the overlay should be a
20
-     * light overlay or a standard one
21
-     */
22
-    constructor(timeoutSeconds, title, message, buttonHtml, isLightOverlay) {
23
-        super();
24
-        /**
25
-         * Conference reload counter in seconds.
26
-         * @type {number}
27
-         */
28
-        this.timeLeft = timeoutSeconds;
29
-        /**
30
-         * Conference reload timeout in seconds.
31
-         * @type {number}
32
-         */
33
-        this.timeout = timeoutSeconds;
34
-
35
-        this.title = title;
36
-        this.message = message;
37
-        this.buttonHtml = buttonHtml;
38
-        this.isLightOverlay = isLightOverlay;
39
-    }
40
-    /**
41
-     * Constructs overlay body with the warning message and count down towards
42
-     * the conference reload.
43
-     * @override
44
-     */
45
-    _buildOverlayContent() {
46
-        return `<div class="inlay">
47
-                    <span data-i18n=${this.title}
48
-                          class='reload_overlay_title'></span>
49
-                    <span data-i18n=${this.message}
50
-                          class='reload_overlay_msg'></span>
51
-                    <div>
52
-                        <div id='reloadProgressBar'
53
-                            class="aui-progress-indicator">
54
-                            <span class="aui-progress-indicator-value"></span>
55
-                        </div>
56
-                        <span id='reloadSecRemaining'
57
-                              data-i18n="dialog.conferenceReloadTimeLeft"
58
-                            class='reload_overlay_msg'>
59
-                        </span>
60
-                    </div>
61
-                    ${this.buttonHtml}
62
-                </div>`;
63
-    }
64
-
65
-    /**
66
-     * Updates the progress indicator position and the label with the time left.
67
-     */
68
-    updateDisplay() {
69
-
70
-        APP.translation.translateElement(
71
-            $("#reloadSecRemaining"), { seconds: this.timeLeft });
72
-
73
-        const ratio = (this.timeout - this.timeLeft) / this.timeout;
74
-        AJS.progressBars.update("#reloadProgressBar", ratio);
75
-    }
76
-
77
-    /**
78
-     * Starts the reload countdown with the animation.
79
-     * @override
80
-     */
81
-    _onShow() {
82
-        $("#reconnectNow").click(() => {
83
-            APP.ConferenceUrl.reload();
84
-        });
85
-
86
-        // Initialize displays
87
-        this.updateDisplay();
88
-
89
-        var intervalId = window.setInterval(function() {
90
-
91
-            if (this.timeLeft >= 1) {
92
-                this.timeLeft -= 1;
93
-            }
94
-
95
-            this.updateDisplay();
96
-
97
-            if (this.timeLeft === 0) {
98
-                window.clearInterval(intervalId);
99
-                APP.ConferenceUrl.reload();
100
-            }
101
-        }.bind(this), 1000);
102
-
103
-        logger.info(
104
-            "The conference will be reloaded after "
105
-                + this.timeLeft + " seconds.");
106
-    }
107
-}
108
-
109
-/**
110
- * Holds the page reload overlay instance.
111
- *
112
- * {@type PageReloadOverlayImpl}
113
- */
114
-let overlay;
115
-
116
-/**
117
- * Checks whether the page reload overlay has been displayed.
118
- * @return {boolean} <tt>true</tt> if the page reload overlay is currently
119
- * visible or <tt>false</tt> otherwise.
120
- */
121
-export function isVisible() {
122
-        return overlay && overlay.isVisible();
123
-}
124
-
125
-/**
126
- * Shows the page reload overlay which will do the conference reload after
127
- * the given amount of time.
128
- *
129
- * @param {number} timeoutSeconds how many seconds before the conference
130
- * reload will happen.
131
- * @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's
132
- * caused by network related failure or <tt>false</tt> when it's
133
- * the infrastructure.
134
- * @param {string} reason a label string identifying the reason for the page
135
- * reload which will be included in details of the log event
136
- */
137
-export function show(timeoutSeconds, isNetworkFailure, reason) {
138
-    let title;
139
-    let message;
140
-    let buttonHtml;
141
-    let isLightOverlay;
142
-
143
-    if (isNetworkFailure) {
144
-        title = "dialog.conferenceDisconnectTitle";
145
-        message = "dialog.conferenceDisconnectMsg";
146
-        buttonHtml
147
-            = `<button id="reconnectNow" data-i18n="dialog.reconnectNow"
148
-                    class="button-control button-control_primary
149
-                            button-control_center"></button>`;
150
-        isLightOverlay = true;
151
-    }
152
-    else {
153
-        title = "dialog.conferenceReloadTitle";
154
-        message = "dialog.conferenceReloadMsg";
155
-        buttonHtml = "";
156
-        isLightOverlay = false;
157
-    }
158
-
159
-    if (!overlay) {
160
-        overlay = new PageReloadOverlayImpl(timeoutSeconds,
161
-                                            title,
162
-                                            message,
163
-                                            buttonHtml,
164
-                                            isLightOverlay);
165
-    }
166
-    // Log the page reload event
167
-    if (!this.isVisible()) {
168
-        // FIXME (CallStats - issue) this event will not make it to
169
-        // the CallStats, because the log queue is not flushed, before
170
-        // "fabric terminated" is sent to the backed
171
-        APP.conference.logEvent(
172
-            'page.reload', undefined /* value */, reason /* label */);
173
-    }
174
-    overlay.show();
175
-}

+ 0
- 63
modules/UI/suspended_overlay/SuspendedOverlay.js View File

@@ -1,63 +0,0 @@
1
-/* global $, APP */
2
-
3
-import Overlay from '../overlay/Overlay';
4
-
5
-/**
6
- * An overlay dialog which is shown when a suspended event is detected.
7
- */
8
-class SuspendedOverlayImpl extends Overlay{
9
-    /**
10
-     * Creates new <tt>SuspendedOverlayImpl</tt>
11
-     */
12
-    constructor() {
13
-        super();
14
-
15
-        $(document).on('click', '#rejoin', () => {
16
-            APP.ConferenceUrl.reload();
17
-        });
18
-    }
19
-    /**
20
-     * Constructs overlay body with the message and a button to rejoin.
21
-     * @override
22
-     */
23
-    _buildOverlayContent() {
24
-        return (
25
-        `<div class="inlay">
26
-            <span class="inlay__icon icon-microphone"></span>
27
-            <span class="inlay__icon icon-camera"></span>
28
-            <h3 class="inlay__title" data-i18n="suspendedoverlay.title"></h3>
29
-            <button id="rejoin" 
30
-                 data-i18n="suspendedoverlay.rejoinKeyTitle" 
31
-                 class="inlay__button button-control button-control_primary">
32
-            </button>
33
-        </div>`);
34
-    }
35
-}
36
-
37
-/**
38
- * Holds the page suspended overlay instance.
39
- *
40
- * {@type SuspendedOverlayImpl}
41
- */
42
-let overlay;
43
-
44
-export default {
45
-    /**
46
-     * Checks whether the page suspended overlay has been displayed.
47
-     * @return {boolean} <tt>true</tt> if the page suspended overlay is
48
-     * currently visible or <tt>false</tt> otherwise.
49
-     */
50
-    isVisible() {
51
-        return overlay && overlay.isVisible();
52
-    },
53
-    /**
54
-     * Shows the page suspended overlay.
55
-     */
56
-    show() {
57
-
58
-        if (!overlay) {
59
-            overlay = new SuspendedOverlayImpl();
60
-        }
61
-        overlay.show();
62
-    }
63
-};

+ 8
- 0
react/features/app/components/AbstractApp.js View File

@@ -1,3 +1,5 @@
1
+/* global APP */
2
+
1 3
 import React, { Component } from 'react';
2 4
 import { Provider } from 'react-redux';
3 5
 import { compose, createStore } from 'redux';
@@ -300,6 +302,12 @@ export class AbstractApp extends Component {
300 302
 
301 303
         if (typeof store === 'undefined') {
302 304
             store = this._createStore();
305
+
306
+            // This is temporary workaround to be able to dispatch actions from
307
+            // non-reactified parts of the code (conference.js for example).
308
+            // Don't use in the react code!!!
309
+            // FIXME: remove when the reactification is finished!
310
+            APP.store = store;
303 311
         }
304 312
 
305 313
         return store;

+ 3
- 2
react/features/base/conference/actions.js View File

@@ -34,7 +34,7 @@ function _addConferenceListeners(conference, dispatch) {
34 34
 
35 35
     conference.on(
36 36
             JitsiConferenceEvents.CONFERENCE_FAILED,
37
-            (...args) => dispatch(_conferenceFailed(conference, ...args)));
37
+            (...args) => dispatch(conferenceFailed(conference, ...args)));
38 38
     conference.on(
39 39
             JitsiConferenceEvents.CONFERENCE_JOINED,
40 40
             (...args) => dispatch(_conferenceJoined(conference, ...args)));
@@ -87,8 +87,9 @@ function _addConferenceListeners(conference, dispatch) {
87 87
  *     conference: JitsiConference,
88 88
  *     error: string
89 89
  * }}
90
+ * @public
90 91
  */
91
-function _conferenceFailed(conference, error) {
92
+export function conferenceFailed(conference, error) {
92 93
     return {
93 94
         type: CONFERENCE_FAILED,
94 95
         conference,

+ 6
- 1
react/features/base/conference/middleware.js View File

@@ -1,3 +1,4 @@
1
+/* global APP */
1 2
 import { CONNECTION_ESTABLISHED } from '../connection';
2 3
 import {
3 4
     getLocalParticipant,
@@ -53,7 +54,11 @@ MiddlewareRegistry.register(store => next => action => {
53 54
 function _connectionEstablished(store, next, action) {
54 55
     const result = next(action);
55 56
 
56
-    store.dispatch(createConference());
57
+    // FIXME: workaround for the web version. Currently the creation of the
58
+    // conference is handled by /conference.js
59
+    if (!APP) {
60
+        store.dispatch(createConference());
61
+    }
57 62
 
58 63
     return result;
59 64
 }

+ 25
- 18
react/features/base/connection/actions.native.js View File

@@ -43,13 +43,13 @@ export function connect() {
43 43
 
44 44
         connection.addEventListener(
45 45
                 JitsiConnectionEvents.CONNECTION_DISCONNECTED,
46
-                connectionDisconnected);
46
+                _onConnectionDisconnected);
47 47
         connection.addEventListener(
48 48
                 JitsiConnectionEvents.CONNECTION_ESTABLISHED,
49
-                connectionEstablished);
49
+                _onConnectionEstablished);
50 50
         connection.addEventListener(
51 51
                 JitsiConnectionEvents.CONNECTION_FAILED,
52
-                connectionFailed);
52
+                _onConnectionFailed);
53 53
 
54 54
         connection.connect();
55 55
 
@@ -59,11 +59,12 @@ export function connect() {
59 59
          *
60 60
          * @param {string} message - Disconnect reason.
61 61
          * @returns {void}
62
+         * @private
62 63
          */
63
-        function connectionDisconnected(message: string) {
64
+        function _onConnectionDisconnected(message: string) {
64 65
             connection.removeEventListener(
65 66
                     JitsiConnectionEvents.CONNECTION_DISCONNECTED,
66
-                    connectionDisconnected);
67
+                    _onConnectionDisconnected);
67 68
 
68 69
             dispatch(_connectionDisconnected(connection, message));
69 70
         }
@@ -72,10 +73,11 @@ export function connect() {
72 73
          * Resolves external promise when connection is established.
73 74
          *
74 75
          * @returns {void}
76
+         * @private
75 77
          */
76
-        function connectionEstablished() {
78
+        function _onConnectionEstablished() {
77 79
             unsubscribe();
78
-            dispatch(_connectionEstablished(connection));
80
+            dispatch(connectionEstablished(connection));
79 81
         }
80 82
 
81 83
         /**
@@ -83,11 +85,12 @@ export function connect() {
83 85
          *
84 86
          * @param {JitsiConnectionErrors} err - Connection error.
85 87
          * @returns {void}
88
+         * @private
86 89
          */
87
-        function connectionFailed(err) {
90
+        function _onConnectionFailed(err) {
88 91
             unsubscribe();
89 92
             console.error('CONNECTION FAILED:', err);
90
-            dispatch(_connectionFailed(connection, err));
93
+            dispatch(connectionFailed(connection, err, ''));
91 94
         }
92 95
 
93 96
         /**
@@ -99,10 +102,10 @@ export function connect() {
99 102
         function unsubscribe() {
100 103
             connection.removeEventListener(
101 104
                     JitsiConnectionEvents.CONNECTION_ESTABLISHED,
102
-                    connectionEstablished);
105
+                    _onConnectionEstablished);
103 106
             connection.removeEventListener(
104 107
                     JitsiConnectionEvents.CONNECTION_FAILED,
105
-                    connectionFailed);
108
+                    _onConnectionFailed);
106 109
         }
107 110
     };
108 111
 }
@@ -183,13 +186,13 @@ function _connectionDisconnected(connection, message: string) {
183 186
  *
184 187
  * @param {JitsiConnection} connection - The JitsiConnection which was
185 188
  * established.
186
- * @private
187 189
  * @returns {{
188 190
  *     type: CONNECTION_ESTABLISHED,
189 191
  *     connection: JitsiConnection
190 192
  * }}
193
+ * @public
191 194
  */
192
-function _connectionEstablished(connection) {
195
+export function connectionEstablished(connection: Object) {
193 196
     return {
194 197
         type: CONNECTION_ESTABLISHED,
195 198
         connection
@@ -200,18 +203,22 @@ function _connectionEstablished(connection) {
200 203
  * Create an action for when the signaling connection could not be created.
201 204
  *
202 205
  * @param {JitsiConnection} connection - The JitsiConnection which failed.
203
- * @param {string} error - Error message.
204
- * @private
206
+ * @param {string} error - Error.
207
+ * @param {string} errorMessage - Error message.
205 208
  * @returns {{
206 209
  *     type: CONNECTION_FAILED,
207 210
  *     connection: JitsiConnection,
208
- *     error: string
211
+ *     error: string,
212
+ *     errorMessage: string
209 213
  * }}
214
+ * @public
210 215
  */
211
-function _connectionFailed(connection, error: string) {
216
+export function connectionFailed(
217
+    connection: Object, error: string, errorMessage: string) {
212 218
     return {
213 219
         type: CONNECTION_FAILED,
214 220
         connection,
215
-        error
221
+        error,
222
+        errorMessage
216 223
     };
217 224
 }

+ 5
- 0
react/features/base/connection/actions.web.js View File

@@ -12,6 +12,11 @@ declare var JitsiMeetJS: Object;
12 12
 const JitsiConferenceEvents = JitsiMeetJS.events.conference;
13 13
 const logger = require('jitsi-meet-logger').getLogger(__filename);
14 14
 
15
+export {
16
+    connectionEstablished,
17
+    connectionFailed
18
+} from './actions.native.js';
19
+
15 20
 /**
16 21
  * Opens new connection.
17 22
  *

+ 3
- 0
react/features/conference/components/Conference.web.js View File

@@ -7,6 +7,8 @@ import { connect, disconnect } from '../../base/connection';
7 7
 import { Watermarks } from '../../base/react';
8 8
 import { FeedbackButton } from '../../feedback';
9 9
 
10
+import { OverlayContainer } from '../../overlay';
11
+
10 12
 /**
11 13
  * For legacy reasons, inline style for display none.
12 14
  *
@@ -162,6 +164,7 @@ class Conference extends Component {
162 164
                         </div>
163 165
                     </div>
164 166
                 </div>
167
+                <OverlayContainer />
165 168
             </div>
166 169
         );
167 170
     }

+ 25
- 0
react/features/overlay/actionTypes.js View File

@@ -0,0 +1,25 @@
1
+import { Symbol } from '../base/react';
2
+
3
+/**
4
+ * The type of the Redux action which signals that a suspend was detected.
5
+ *
6
+ * {
7
+ *     type: SUSPEND_DETECTED
8
+ * }
9
+ * @public
10
+ */
11
+export const SUSPEND_DETECTED = Symbol('SUSPEND_DETECTED');
12
+
13
+/**
14
+ * The type of the Redux action which signals that the prompt for media
15
+ * permission is visible or not.
16
+ *
17
+ * {
18
+ *     type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
19
+ *     isVisible: {boolean},
20
+ *     browser: {string}
21
+ * }
22
+ * @public
23
+ */
24
+export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
25
+    = Symbol('MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED');

+ 40
- 0
react/features/overlay/actions.js View File

@@ -0,0 +1,40 @@
1
+import {
2
+    SUSPEND_DETECTED,
3
+    MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
4
+} from './actionTypes';
5
+import './reducer';
6
+
7
+/**
8
+ * Signals that suspend was detected.
9
+ *
10
+ * @returns {{
11
+ *     type: SUSPEND_DETECTED
12
+ * }}
13
+ * @public
14
+ */
15
+export function suspendDetected() {
16
+    return {
17
+        type: SUSPEND_DETECTED
18
+    };
19
+}
20
+
21
+/**
22
+ * Signals that the prompt for media permission is visible or not.
23
+ *
24
+ * @param {boolean} isVisible - If the value is true - the prompt for media
25
+ * permission is visible otherwise the value is false/undefined.
26
+ * @param {string} browser - The name of the current browser.
27
+ * @returns {{
28
+ *     type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
29
+ *     isVisible: {boolean},
30
+ *     browser: {string}
31
+ * }}
32
+ * @public
33
+ */
34
+export function mediaPermissionPromptVisibilityChanged(isVisible, browser) {
35
+    return {
36
+        type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
37
+        isVisible,
38
+        browser
39
+    };
40
+}

+ 86
- 0
react/features/overlay/components/AbstractOverlay.js View File

@@ -0,0 +1,86 @@
1
+/* global $, APP */
2
+
3
+import React, { Component } from 'react';
4
+
5
+/**
6
+ * Implements an abstract React Component for overlay - the components which
7
+ * are displayed on top of the application covering the whole screen.
8
+ *
9
+ * @abstract
10
+ */
11
+export default class AbstractOverlay extends Component {
12
+    /**
13
+     * Initializes a new AbstractOverlay instance.
14
+     *
15
+     * @param {Object} props - The read-only properties with which the new
16
+     * instance is to be initialized.
17
+     * @public
18
+     */
19
+    constructor(props) {
20
+        super(props);
21
+
22
+        this.state = {
23
+            /**
24
+             * Indicates the css style of the overlay. if true - lighter  and
25
+             * darker otherwise.
26
+             * @type {boolean}
27
+             */
28
+            isLightOverlay: false
29
+        };
30
+    }
31
+
32
+    /**
33
+     * Abstract method which should be used by subclasses to provide the overlay
34
+     * content.
35
+     *
36
+     * @returns {ReactElement|null}
37
+     * @protected
38
+     */
39
+    _renderOverlayContent() {
40
+        return null;
41
+    }
42
+
43
+    /**
44
+     * This method is executed when comonent is mounted.
45
+     *
46
+     * @inheritdoc
47
+     * @returns {void}
48
+     */
49
+    componentDidMount() {
50
+        // XXX Temporary solution until we add React translation.
51
+        APP.translation.translateElement($('#overlay'));
52
+    }
53
+
54
+    /**
55
+     * Reloads the page.
56
+     *
57
+     * @returns {void}
58
+     * @protected
59
+     */
60
+    _reconnectNow() {
61
+        // FIXME: In future we should dispatch an action here that will result
62
+        // in reload.
63
+        APP.ConferenceUrl.reload();
64
+    }
65
+
66
+    /**
67
+     * Implements React's {@link Component#render()}.
68
+     *
69
+     * @inheritdoc
70
+     * @returns {ReactElement|null}
71
+     */
72
+    render() {
73
+        const containerClass = this.state.isLightOverlay
74
+            ? 'overlay__container-light' : 'overlay__container';
75
+
76
+        return (
77
+            <div
78
+                className = { containerClass }
79
+                id = 'overlay'>
80
+                <div className = 'overlay__content'>
81
+                    { this._renderOverlayContent() }
82
+                </div>
83
+            </div>
84
+        );
85
+    }
86
+}

+ 213
- 0
react/features/overlay/components/OverlayContainer.js View File

@@ -0,0 +1,213 @@
1
+/* global APP */
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import PageReloadOverlay from './PageReloadOverlay';
7
+import SuspendedOverlay from './SuspendedOverlay';
8
+import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
9
+
10
+/**
11
+ * Implements a React Component that will display the correct overlay when
12
+ * needed.
13
+ */
14
+class OverlayContainer extends Component {
15
+    /**
16
+     * OverlayContainer component's property types.
17
+     *
18
+     * @static
19
+     */
20
+    static propTypes = {
21
+        /**
22
+         * The browser which is used currently.
23
+         * NOTE: Used by UserMediaPermissionsOverlay only.
24
+         * @private
25
+         * @type {string}
26
+         */
27
+        _browser: React.PropTypes.string,
28
+
29
+        /**
30
+         * The indicator which determines whether the status of
31
+         * JitsiConnection object has been "established" or not.
32
+         * NOTE: Used by PageReloadOverlay only.
33
+         * @private
34
+         * @type {boolean}
35
+         */
36
+        _connectionEstablished: React.PropTypes.bool,
37
+
38
+        /**
39
+         * The indicator which determines whether a critical error for reload
40
+         * has been received.
41
+         * NOTE: Used by PageReloadOverlay only.
42
+         * @private
43
+         * @type {boolean}
44
+         */
45
+        _haveToReload: React.PropTypes.bool,
46
+
47
+        /**
48
+         * The indicator which determines whether the reload was caused by
49
+         * network failure.
50
+         * NOTE: Used by PageReloadOverlay only.
51
+         * @private
52
+         * @type {boolean}
53
+         */
54
+        _isNetworkFailure: React.PropTypes.bool,
55
+
56
+        /**
57
+         * The indicator which determines whether the GUM permissions prompt
58
+         * is displayed or not.
59
+         * NOTE: Used by UserMediaPermissionsOverlay only.
60
+         * @private
61
+         * @type {boolean}
62
+         */
63
+        _mediaPermissionPromptVisible: React.PropTypes.bool,
64
+
65
+        /**
66
+         * The reason for the error that will cause the reload.
67
+         * NOTE: Used by PageReloadOverlay only.
68
+         * @private
69
+         * @type {string}
70
+         */
71
+        _reason: React.PropTypes.string,
72
+
73
+        /**
74
+         * The indicator which determines whether the GUM permissions prompt
75
+         * is displayed or not.
76
+         * NOTE: Used by SuspendedOverlay only.
77
+         * @private
78
+         * @type {string}
79
+         */
80
+        _suspendDetected: React.PropTypes.bool
81
+    }
82
+
83
+    /**
84
+     * React Component method that executes once component is updated.
85
+     *
86
+     * @inheritdoc
87
+     * @returns {void}
88
+     * @protected
89
+     */
90
+    componentDidUpdate() {
91
+        // FIXME: Temporary workaround until everything is moved to react.
92
+        APP.UI.overlayVisible
93
+            = (this.props._connectionEstablished && this.props._haveToReload)
94
+            || this.props._suspendDetected
95
+            || this.props._mediaPermissionPromptVisible;
96
+    }
97
+
98
+    /**
99
+     * Implements React's {@link Component#render()}.
100
+     *
101
+     * @inheritdoc
102
+     * @returns {ReactElement|null}
103
+     * @public
104
+     */
105
+    render() {
106
+        if (this.props._connectionEstablished && this.props._haveToReload) {
107
+            return (
108
+                <PageReloadOverlay
109
+                    isNetworkFailure = { this.props._isNetworkFailure }
110
+                    reason = { this.props._reason } />
111
+            );
112
+        }
113
+
114
+        if (this.props._suspendDetected) {
115
+            return (
116
+                <SuspendedOverlay />
117
+            );
118
+        }
119
+
120
+        if (this.props._mediaPermissionPromptVisible) {
121
+            return (
122
+                <UserMediaPermissionsOverlay
123
+                    browser = { this.props._browser } />
124
+            );
125
+        }
126
+
127
+        return null;
128
+    }
129
+}
130
+
131
+/**
132
+ * Maps (parts of) the Redux state to the associated OverlayContainer's props.
133
+ *
134
+ * @param {Object} state - The Redux state.
135
+ * @returns {{
136
+ *      _browser: string,
137
+ *      _connectionEstablished: bool,
138
+ *      _haveToReload: bool,
139
+ *      _isNetworkFailure: bool,
140
+ *      _mediaPermissionPromptVisible: bool,
141
+ *      _reason: string,
142
+ *      _suspendDetected: bool
143
+ * }}
144
+ * @private
145
+ */
146
+function _mapStateToProps(state) {
147
+    return {
148
+        /**
149
+         * The browser which is used currently.
150
+         * NOTE: Used by UserMediaPermissionsOverlay only.
151
+         * @private
152
+         * @type {string}
153
+         */
154
+        _browser: state['features/overlay'].browser,
155
+
156
+        /**
157
+         * The indicator which determines whether the status of
158
+         * JitsiConnection object has been "established" or not.
159
+         * NOTE: Used by PageReloadOverlay only.
160
+         * @private
161
+         * @type {boolean}
162
+         */
163
+        _connectionEstablished:
164
+            state['features/overlay'].connectionEstablished,
165
+
166
+        /**
167
+         * The indicator which determines whether a critical error for reload
168
+         * has been received.
169
+         * NOTE: Used by PageReloadOverlay only.
170
+         * @private
171
+         * @type {boolean}
172
+         */
173
+        _haveToReload: state['features/overlay'].haveToReload,
174
+
175
+        /**
176
+         * The indicator which determines whether the reload was caused by
177
+         * network failure.
178
+         * NOTE: Used by PageReloadOverlay only.
179
+         * @private
180
+         * @type {boolean}
181
+         */
182
+        _isNetworkFailure: state['features/overlay'].isNetworkFailure,
183
+
184
+        /**
185
+         * The indicator which determines whether the GUM permissions prompt
186
+         * is displayed or not.
187
+         * NOTE: Used by UserMediaPermissionsOverlay only.
188
+         * @private
189
+         * @type {boolean}
190
+         */
191
+        _mediaPermissionPromptVisible:
192
+            state['features/overlay'].mediaPermissionPromptVisible,
193
+
194
+        /**
195
+         * The reason for the error that will cause the reload.
196
+         * NOTE: Used by PageReloadOverlay only.
197
+         * @private
198
+         * @type {string}
199
+         */
200
+        _reason: state['features/overlay'].reason,
201
+
202
+        /**
203
+         * The indicator which determines whether the GUM permissions prompt
204
+         * is displayed or not.
205
+         * NOTE: Used by SuspendedOverlay only.
206
+         * @private
207
+         * @type {string}
208
+         */
209
+        _suspendDetected: state['features/overlay'].suspendDetected
210
+    };
211
+}
212
+
213
+export default connect(_mapStateToProps)(OverlayContainer);

+ 298
- 0
react/features/overlay/components/PageReloadOverlay.js View File

@@ -0,0 +1,298 @@
1
+/* global APP, AJS */
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { randomInt } from '../../base/util/randomUtil';
6
+
7
+import AbstractOverlay from './AbstractOverlay';
8
+
9
+const logger = require('jitsi-meet-logger').getLogger(__filename);
10
+
11
+/**
12
+ * Implements a React Component for the reload timer. Starts counter from
13
+ * props.start, adds props.step to the current value on every props.interval
14
+ * seconds until the current value reaches props.end. Also displays progress
15
+ * bar.
16
+ */
17
+class ReloadTimer extends Component {
18
+    /**
19
+     * ReloadTimer component's property types.
20
+     *
21
+     * @static
22
+     */
23
+    static propTypes = {
24
+        /**
25
+         * The end of the timer. When this.state.current reaches this
26
+         * value the timer will stop and call onFinish function.
27
+         * @public
28
+         * @type {number}
29
+         */
30
+        end: React.PropTypes.number,
31
+
32
+        /**
33
+         * The interval in sec for adding this.state.step to this.state.current
34
+         * @public
35
+         * @type {number}
36
+         */
37
+        interval: React.PropTypes.number,
38
+
39
+        /**
40
+         * The function that will be executed when timer finish (when
41
+         * this.state.current === this.props.end)
42
+         */
43
+        onFinish: React.PropTypes.func,
44
+
45
+        /**
46
+         * The start of the timer. The initial value for this.state.current.
47
+         * @public
48
+         * @type {number}
49
+         */
50
+        start: React.PropTypes.number,
51
+
52
+        /**
53
+         * The value which will be added to this.state.current on every step.
54
+         * @public
55
+         * @type {number}
56
+         */
57
+        step: React.PropTypes.number
58
+    }
59
+
60
+    /**
61
+     * Initializes a new ReloadTimer instance.
62
+     *
63
+     * @param {Object} props - The read-only properties with which the new
64
+     * instance is to be initialized.
65
+     * @public
66
+     */
67
+    constructor(props) {
68
+        super(props);
69
+        this.state = {
70
+            current: this.props.start,
71
+            time: Math.abs(this.props.end - this.props.start)
72
+        };
73
+    }
74
+
75
+    /**
76
+     * React Component method that executes once component is mounted.
77
+     *
78
+     * @inheritdoc
79
+     * @returns {void}
80
+     * @protected
81
+     */
82
+    componentDidMount() {
83
+        AJS.progressBars.update('#reloadProgressBar', 0);
84
+        const intervalId = setInterval(() => {
85
+            if (this.state.current === this.props.end) {
86
+                clearInterval(intervalId);
87
+                this.props.onFinish();
88
+
89
+                return;
90
+            }
91
+            this.setState((prevState, props) => {
92
+                return { current: prevState.current + props.step };
93
+            });
94
+        }, Math.abs(this.props.interval) * 1000);
95
+    }
96
+
97
+    /**
98
+     * React Component method that executes once component is updated.
99
+     *
100
+     * @inheritdoc
101
+     * @returns {void}
102
+     * @protected
103
+     */
104
+    componentDidUpdate() {
105
+        AJS.progressBars.update('#reloadProgressBar',
106
+            Math.abs(this.state.current - this.props.start) / this.state.time);
107
+    }
108
+
109
+    /**
110
+     * Implements React's {@link Component#render()}.
111
+     *
112
+     * @inheritdoc
113
+     * @returns {ReactElement|null}
114
+     * @public
115
+     */
116
+    render() {
117
+        return (
118
+            <div>
119
+                <div
120
+                    className = 'aui-progress-indicator'
121
+                    id = 'reloadProgressBar'>
122
+                    <span className = 'aui-progress-indicator-value' />
123
+                </div>
124
+                <span
125
+                    className = 'reload_overlay_text'
126
+                    id = 'reloadSeconds'>
127
+                    { this.state.current }
128
+                    <span data-i18n = 'dialog.conferenceReloadTimeLeft' />
129
+                </span>
130
+            </div>
131
+        );
132
+    }
133
+}
134
+
135
+/**
136
+ * Implements a React Component for page reload overlay. Shown before
137
+ * the conference is reloaded. Shows a warning message and counts down towards
138
+ * the reload.
139
+ */
140
+export default class PageReloadOverlay extends AbstractOverlay {
141
+    /**
142
+     * PageReloadOverlay component's property types.
143
+     *
144
+     * @static
145
+     */
146
+    static propTypes = {
147
+        /**
148
+         * The indicator which determines whether the reload was caused by
149
+         * network failure.
150
+         * @public
151
+         * @type {boolean}
152
+         */
153
+        isNetworkFailure: React.PropTypes.bool,
154
+
155
+        /**
156
+         * The reason for the error that will cause the reload.
157
+         * NOTE: Used by PageReloadOverlay only.
158
+         * @public
159
+         * @type {string}
160
+         */
161
+        reason: React.PropTypes.string
162
+    }
163
+
164
+    /**
165
+     * Initializes a new PageReloadOverlay instance.
166
+     *
167
+     * @param {Object} props - The read-only properties with which the new
168
+     * instance is to be initialized.
169
+     * @public
170
+     */
171
+    constructor(props) {
172
+        super(props);
173
+
174
+        /**
175
+         * How long the overlay dialog will be
176
+         * displayed, before the conference will be reloaded.
177
+         * @type {number}
178
+         */
179
+        const timeoutSeconds = 10 + randomInt(0, 20);
180
+
181
+        let isLightOverlay, message, title;
182
+
183
+        if (this.props.isNetworkFailure) {
184
+            title = 'dialog.conferenceDisconnectTitle';
185
+            message = 'dialog.conferenceDisconnectMsg';
186
+            isLightOverlay = true;
187
+        } else {
188
+            title = 'dialog.conferenceReloadTitle';
189
+            message = 'dialog.conferenceReloadMsg';
190
+            isLightOverlay = false;
191
+        }
192
+
193
+        this.state = {
194
+            ...this.state,
195
+
196
+            /**
197
+             * Indicates the css style of the overlay. if true - lighter  and
198
+             * darker otherwise.
199
+             * @type {boolean}
200
+             */
201
+            isLightOverlay,
202
+
203
+            /**
204
+             * The translation key for the title of the overlay
205
+             * @type {string}
206
+             */
207
+            message,
208
+
209
+            /**
210
+             * How long the overlay dialog will be
211
+             * displayed, before the conference will be reloaded.
212
+             * @type {number}
213
+             */
214
+            timeoutSeconds,
215
+
216
+            /**
217
+             * The translation key for the title of the overlay
218
+             * @type {string}
219
+             */
220
+            title
221
+        };
222
+    }
223
+
224
+    /**
225
+     * Renders the button for relaod the page if necessary.
226
+     *
227
+     * @returns {ReactElement|null}
228
+     * @private
229
+     */
230
+    _renderButton() {
231
+        if (this.props.isNetworkFailure) {
232
+            const cName = 'button-control button-control_primary '
233
+                + 'button-control_center';
234
+
235
+            /* eslint-disable react/jsx-handler-names */
236
+
237
+            return (
238
+                <button
239
+                    className = { cName }
240
+                    data-i18n = 'dialog.reconnectNow'
241
+                    id = 'reconnectNow'
242
+                    onClick = { this._reconnectNow } />
243
+            );
244
+        }
245
+
246
+        return null;
247
+    }
248
+
249
+    /**
250
+     * Constructs overlay body with the warning message and count down towards
251
+     * the conference reload.
252
+     *
253
+     * @returns {ReactElement|null}
254
+     * @override
255
+     * @protected
256
+     */
257
+    _renderOverlayContent() {
258
+
259
+        /* eslint-disable react/jsx-handler-names */
260
+
261
+        return (
262
+            <div className = 'inlay'>
263
+                <span
264
+                    className = 'reload_overlay_title'
265
+                    data-i18n = { this.state.title } />
266
+                <span
267
+                    className = 'reload_overlay_text'
268
+                    data-i18n = { this.state.message } />
269
+                <ReloadTimer
270
+                    end = { 0 }
271
+                    interval = { 1 }
272
+                    onFinish = { this._reconnectNow }
273
+                    start = { this.state.timeoutSeconds }
274
+                    step = { -1 } />
275
+                { this._renderButton() }
276
+            </div>
277
+        );
278
+    }
279
+
280
+    /**
281
+     * This method is executed when comonent is mounted.
282
+     *
283
+     * @inheritdoc
284
+     * @returns {void}
285
+     */
286
+    componentDidMount() {
287
+        super.componentDidMount();
288
+
289
+        // FIXME (CallStats - issue) this event will not make it to
290
+        // the CallStats, because the log queue is not flushed, before
291
+        // "fabric terminated" is sent to the backed
292
+        // FIXME: We should dispatch action for this
293
+        APP.conference.logEvent('page.reload', undefined /* value */,
294
+            this.props.reason /* label */);
295
+        logger.info(`The conference will be reloaded after
296
+            ${this.state.timeoutSeconds} seconds.`);
297
+    }
298
+}

+ 37
- 0
react/features/overlay/components/SuspendedOverlay.js View File

@@ -0,0 +1,37 @@
1
+import React from 'react';
2
+
3
+import AbstractOverlay from './AbstractOverlay';
4
+
5
+/**
6
+ * Implements a React Component for suspended overlay. Shown when suspended
7
+ * is detected.
8
+ */
9
+export default class SuspendedOverlay extends AbstractOverlay {
10
+    /**
11
+     * Constructs overlay body with the message and a button to rejoin.
12
+     *
13
+     * @returns {ReactElement|null}
14
+     * @override
15
+     * @protected
16
+     */
17
+    _renderOverlayContent() {
18
+        const btnClass = 'inlay__button button-control button-control_primary';
19
+
20
+        /* eslint-disable react/jsx-handler-names */
21
+
22
+        return (
23
+            <div className = 'inlay'>
24
+                <span className = 'inlay__icon icon-microphone' />
25
+                <span className = 'inlay__icon icon-camera' />
26
+                <h3
27
+                    className = 'inlay__title'
28
+                    data-i18n = 'suspendedoverlay.title' />
29
+                <button
30
+                    className = { btnClass }
31
+                    data-i18n = 'suspendedoverlay.rejoinKeyTitle'
32
+                    id = 'rejoin'
33
+                    onClick = { this._reconnectNow } />
34
+            </div>
35
+        );
36
+    }
37
+}

+ 98
- 0
react/features/overlay/components/UserMediaPermissionsOverlay.js View File

@@ -0,0 +1,98 @@
1
+/* global interfaceConfig */
2
+
3
+import React from 'react';
4
+
5
+import AbstractOverlay from './AbstractOverlay';
6
+
7
+/**
8
+ * Implements a React Component for overlay with guidance how to proceed with
9
+ * gUM prompt.
10
+ */
11
+export default class UserMediaPermissionsOverlay extends AbstractOverlay {
12
+    /**
13
+     * UserMediaPermissionsOverlay component's property types.
14
+     *
15
+     * @static
16
+     */
17
+    static propTypes = {
18
+        /**
19
+         * The browser which is used currently. The text is different for every
20
+         * browser.
21
+         * @public
22
+         * @type {string}
23
+         */
24
+        browser: React.PropTypes.string
25
+    }
26
+
27
+    /**
28
+     * Initializes a new SuspendedOverlay instance.
29
+     *
30
+     * @param {Object} props - The read-only properties with which the new
31
+     * instance is to be initialized.
32
+     * @public
33
+     */
34
+    constructor(props) {
35
+        super(props);
36
+
37
+        this.state = {
38
+            /**
39
+             * The src value of the image for the policy logo.
40
+             * @type {string}
41
+             */
42
+            policyLogoSrc: interfaceConfig.POLICY_LOGO
43
+        };
44
+    }
45
+
46
+    /**
47
+     * Constructs overlay body with the message with guidance how to proceed
48
+     * with gUM prompt.
49
+     *
50
+     * @returns {ReactElement|null}
51
+     * @override
52
+     * @protected
53
+     */
54
+    _renderOverlayContent() {
55
+        const textKey = `userMedia.${this.props.browser}GrantPermissions`;
56
+
57
+        return (
58
+            <div>
59
+                <div className = 'inlay'>
60
+                    <span className = 'inlay__icon icon-microphone' />
61
+                    <span className = 'inlay__icon icon-camera' />
62
+                    <h3
63
+                        className = 'inlay__title'
64
+                        data-i18n = 'startupoverlay.title'
65
+                        data-i18n-options
66
+                            = '{"postProcess": "resolveAppName"}' />
67
+                    <span
68
+                        className = 'inlay__text'
69
+                        data-i18n = { `[html]${textKey}` } />
70
+                </div>
71
+                <div className = 'policy overlay__policy'>
72
+                    <p
73
+                        className = 'policy__text'
74
+                        data-i18n = '[html]startupoverlay.policyText' />
75
+                    { this._renderPolicyLogo() }
76
+                </div>
77
+            </div>
78
+        );
79
+    }
80
+
81
+    /**
82
+     * Renders the policy logo.
83
+     *
84
+     * @returns {ReactElement|null}
85
+     * @private
86
+     */
87
+    _renderPolicyLogo() {
88
+        if (this.state.policyLogoSrc) {
89
+            return (
90
+                <div className = 'policy__logo'>
91
+                    <img src = { this.state.policyLogoSrc } />
92
+                </div>
93
+            );
94
+        }
95
+
96
+        return null;
97
+    }
98
+}

+ 1
- 0
react/features/overlay/components/index.js View File

@@ -0,0 +1 @@
1
+export { default as OverlayContainer } from './OverlayContainer';

+ 2
- 0
react/features/overlay/index.js View File

@@ -0,0 +1,2 @@
1
+export * from './components';
2
+export * from './actions';

+ 145
- 0
react/features/overlay/reducer.js View File

@@ -0,0 +1,145 @@
1
+/* global JitsiMeetJS */
2
+
3
+import { CONFERENCE_FAILED } from '../base/conference';
4
+import {
5
+    CONNECTION_ESTABLISHED,
6
+    CONNECTION_FAILED
7
+} from '../base/connection';
8
+import {
9
+    ReducerRegistry,
10
+    setStateProperty,
11
+    setStateProperties
12
+} from '../base/redux';
13
+
14
+import {
15
+    MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
16
+    SUSPEND_DETECTED
17
+} from './actionTypes';
18
+
19
+const logger = require('jitsi-meet-logger').getLogger(__filename);
20
+
21
+/**
22
+ * Reduces the Redux actions of the feature overlay.
23
+ */
24
+ReducerRegistry.register('features/overlay', (state = {}, action) => {
25
+    switch (action.type) {
26
+    case CONFERENCE_FAILED:
27
+        return _conferenceFailed(state, action);
28
+
29
+    case CONNECTION_ESTABLISHED:
30
+        return _connectionEstablished(state, action);
31
+
32
+    case CONNECTION_FAILED:
33
+        return _connectionFailed(state, action);
34
+
35
+    case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED:
36
+        return _mediaPermissionPromptVisibilityChanged(state, action);
37
+
38
+    case SUSPEND_DETECTED:
39
+        return _suspendDetected(state, action);
40
+    }
41
+
42
+    return state;
43
+});
44
+
45
+/**
46
+ * Reduces a specific Redux action CONFERENCE_FAILED of the feature
47
+ * overlay.
48
+ *
49
+ * @param {Object} state - The Redux state of the feature overlay.
50
+ * @param {Action} action - The Redux action CONFERENCE_FAILED to reduce.
51
+ * @returns {Object} The new state of the feature base/connection after the
52
+ * reduction of the specified action.
53
+ * @private
54
+ */
55
+function _conferenceFailed(state, action) {
56
+    const ConferenceErrors = JitsiMeetJS.errors.conference;
57
+
58
+    if (action.error === ConferenceErrors.FOCUS_LEFT
59
+        || action.error === ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE) {
60
+        return setStateProperties(state, {
61
+            haveToReload: true,
62
+            isNetworkFailure: false,
63
+            reason: action.errorMessage
64
+        });
65
+    }
66
+
67
+    return state;
68
+}
69
+
70
+/**
71
+ * Reduces a specific Redux action CONNECTION_ESTABLISHED of the feature
72
+ * overlay.
73
+ *
74
+ * @param {Object} state - The Redux state of the feature overlay.
75
+ * @returns {Object} The new state of the feature overlay after the
76
+ * reduction of the specified action.
77
+ * @private
78
+ */
79
+function _connectionEstablished(state) {
80
+    return setStateProperty(state, 'connectionEstablished', true);
81
+}
82
+
83
+/**
84
+ * Reduces a specific Redux action CONNECTION_FAILED of the feature
85
+ * overlay.
86
+ *
87
+ * @param {Object} state - The Redux state of the feature overlay.
88
+ * @param {Action} action - The Redux action CONNECTION_FAILED to reduce.
89
+ * @returns {Object} The new state of the feature overlay after the
90
+ * reduction of the specified action.
91
+ * @private
92
+ */
93
+function _connectionFailed(state, action) {
94
+    const ConnectionErrors = JitsiMeetJS.errors.connection;
95
+
96
+    switch (action.error) {
97
+    case ConnectionErrors.CONNECTION_DROPPED_ERROR:
98
+    case ConnectionErrors.OTHER_ERROR:
99
+    case ConnectionErrors.SERVER_ERROR: {
100
+        logger.error(`XMPP connection error: ${action.errorMessage}`);
101
+
102
+        // From all of the cases above only CONNECTION_DROPPED_ERROR
103
+        // is considered a network type of failure
104
+        return setStateProperties(state, {
105
+            haveToReload: true,
106
+            isNetworkFailure:
107
+                action.error === ConnectionErrors.CONNECTION_DROPPED_ERROR,
108
+            reason: `xmpp-conn-dropped: ${action.errorMessage}`
109
+        });
110
+    }
111
+    }
112
+
113
+    return state;
114
+}
115
+
116
+
117
+/**
118
+ * Reduces a specific Redux action MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
119
+ * of the feature overlay.
120
+ *
121
+ * @param {Object} state - The Redux state of the feature overlay.
122
+ * @param {Action} action - The Redux action to reduce.
123
+ * @returns {Object} The new state of the feature overlay after the
124
+ * reduction of the specified action.
125
+ * @private
126
+ */
127
+function _mediaPermissionPromptVisibilityChanged(state, action) {
128
+    return setStateProperties(state, {
129
+        mediaPermissionPromptVisible: action.isVisible,
130
+        browser: action.browser
131
+    });
132
+}
133
+
134
+/**
135
+ * Reduces a specific Redux action SUSPEND_DETECTED of the feature
136
+ * overlay.
137
+ *
138
+ * @param {Object} state - The Redux state of the feature overlay.
139
+ * @returns {Object} The new state of the feature overlay after the
140
+ * reduction of the specified action.
141
+ * @private
142
+ */
143
+function _suspendDetected(state) {
144
+    return setStateProperty(state, 'suspendDetected', true);
145
+}

Loading…
Cancel
Save