瀏覽代碼

feat(device-errors): move device error dialogs to notifications

- Create a notification component for displaying a toggle.
- Create an action for showing the component if allowed by
  the local storage setting and for saving the setting to
  local storage.
- Remove all notifications having a timeout by default so the
  device error notification must be dismissed manually.
- Split the camera and mic error dialog into two separate
  notifications.
master
Leonard Kim 7 年之前
父節點
當前提交
74ddae4a6a

+ 7
- 7
conference.js 查看文件

@@ -625,13 +625,13 @@ export default {
625 625
                         // If both requests for 'audio' + 'video' and 'audio'
626 626
                         // only failed, we assume that there are some problems
627 627
                         // with user's microphone and show corresponding dialog.
628
-                        APP.UI.showDeviceErrorDialog(
629
-                            audioOnlyError, videoOnlyError);
628
+                        APP.UI.showMicErrorNotification(audioOnlyError);
629
+                        APP.UI.showCameraErrorNotification(videoOnlyError);
630 630
                     } else {
631 631
                         // If request for 'audio' + 'video' failed, but request
632 632
                         // for 'audio' only was OK, we assume that we had
633 633
                         // problems with camera and show corresponding dialog.
634
-                        APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
634
+                        APP.UI.showCameraErrorNotification(audioAndVideoError);
635 635
                     }
636 636
                 }
637 637
 
@@ -770,7 +770,7 @@ export default {
770 770
 
771 771
         const maybeShowErrorDialog = (error) => {
772 772
             if (showUI) {
773
-                APP.UI.showDeviceErrorDialog(error, null);
773
+                APP.UI.showMicErrorNotification(error);
774 774
             }
775 775
         };
776 776
 
@@ -830,7 +830,7 @@ export default {
830 830
 
831 831
         const maybeShowErrorDialog = (error) => {
832 832
             if (showUI) {
833
-                APP.UI.showDeviceErrorDialog(null, error);
833
+                APP.UI.showCameraErrorNotification(error);
834 834
             }
835 835
         };
836 836
 
@@ -2028,7 +2028,7 @@ export default {
2028 2028
                     APP.settings.setCameraDeviceId(cameraDeviceId, true);
2029 2029
                 })
2030 2030
                 .catch((err) => {
2031
-                    APP.UI.showDeviceErrorDialog(null, err);
2031
+                    APP.UI.showCameraErrorNotification(err);
2032 2032
                 });
2033 2033
             }
2034 2034
         );
@@ -2049,7 +2049,7 @@ export default {
2049 2049
                     APP.settings.setMicDeviceId(micDeviceId, true);
2050 2050
                 })
2051 2051
                 .catch((err) => {
2052
-                    APP.UI.showDeviceErrorDialog(err, null);
2052
+                    APP.UI.showMicErrorNotification(err);
2053 2053
                 });
2054 2054
             }
2055 2055
         );

+ 4
- 0
lang/main.json 查看文件

@@ -474,5 +474,9 @@
474 474
         "retry": "Try again",
475 475
         "support": "Support",
476 476
         "supportMsg": "If this keeps happening, reach out to"
477
+    },
478
+    "deviceError": {
479
+        "cameraPermission": "Error obtaining camera permission",
480
+        "microphonePermission": "Error obtaining microphone permission"
477 481
     }
478 482
 }

+ 59
- 99
modules/UI/UI.js 查看文件

@@ -39,6 +39,9 @@ import {
39 39
     showDialOutButton,
40 40
     showToolbox
41 41
 } from '../../react/features/toolbox';
42
+import {
43
+    maybeShowNotificationWithDoNotDisplay
44
+} from '../../react/features/notifications';
42 45
 
43 46
 var EventEmitter = require("events");
44 47
 UI.messageHandler = messageHandler;
@@ -64,8 +67,6 @@ let sharedVideoManager;
64 67
 
65 68
 let followMeHandler;
66 69
 
67
-let deviceErrorDialog;
68
-
69 70
 const TrackErrors = JitsiMeetJS.errors.track;
70 71
 
71 72
 const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
@@ -1189,115 +1190,74 @@ UI.showExtensionInlineInstallationDialog = function (callback) {
1189 1190
     });
1190 1191
 };
1191 1192
 
1192
-
1193 1193
 /**
1194
- * Shows dialog with combined information about camera and microphone errors.
1195
- * @param {JitsiTrackError} micError
1196
- * @param {JitsiTrackError} cameraError
1194
+ * Shows a notifications about the passed in microphone error.
1195
+ *
1196
+ * @param {JitsiTrackError} micError - An error object related to using or
1197
+ * acquiring an audio stream.
1198
+ * @returns {void}
1197 1199
  */
1198
-UI.showDeviceErrorDialog = function (micError, cameraError) {
1199
-    let dontShowAgain = {
1200
-        id: "doNotShowWarningAgain",
1201
-        localStorageKey: "doNotShowErrorAgain",
1202
-        textKey: "dialog.doNotShowWarningAgain"
1203
-    };
1204
-    let isMicJitsiTrackErrorAndHasName = micError && micError.name &&
1205
-        micError instanceof JitsiMeetJS.errorTypes.JitsiTrackError;
1206
-    let isCameraJitsiTrackErrorAndHasName = cameraError && cameraError.name &&
1207
-        cameraError instanceof JitsiMeetJS.errorTypes.JitsiTrackError;
1208
-    let showDoNotShowWarning = false;
1209
-
1210
-    if (micError && cameraError && isMicJitsiTrackErrorAndHasName &&
1211
-        isCameraJitsiTrackErrorAndHasName) {
1212
-        showDoNotShowWarning =  true;
1213
-    } else if (micError && isMicJitsiTrackErrorAndHasName && !cameraError) {
1214
-        showDoNotShowWarning =  true;
1215
-    } else if (cameraError && isCameraJitsiTrackErrorAndHasName && !micError) {
1216
-        showDoNotShowWarning =  true;
1200
+UI.showMicErrorNotification = function (micError) {
1201
+    if (!micError) {
1202
+        return;
1217 1203
     }
1218 1204
 
1219
-    if (micError) {
1220
-        dontShowAgain.localStorageKey += "-mic-" + micError.name;
1221
-    }
1205
+    const { message, name } = micError;
1222 1206
 
1223
-    if (cameraError) {
1224
-        dontShowAgain.localStorageKey += "-camera-" + cameraError.name;
1225
-    }
1207
+    const persistenceKey = `doNotShowErrorAgain-mic-${name}`;
1226 1208
 
1227
-    let cameraJitsiTrackErrorMsg = cameraError
1228
-        ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name]
1229
-        : undefined;
1230
-    let micJitsiTrackErrorMsg = micError
1231
-        ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[micError.name]
1232
-        : undefined;
1233
-    let cameraErrorMsg = cameraError
1234
-        ? cameraJitsiTrackErrorMsg ||
1235
-            JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
1236
-        : "";
1237
-    let micErrorMsg = micError
1238
-        ? micJitsiTrackErrorMsg ||
1239
-            JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
1240
-        : "";
1241
-    let additionalCameraErrorMsg = !cameraJitsiTrackErrorMsg && cameraError &&
1242
-        cameraError.message
1243
-            ? `<div>${cameraError.message}</div>`
1244
-            : ``;
1245
-    let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError &&
1246
-        micError.message
1247
-            ? `<div>${micError.message}</div>`
1248
-            : ``;
1249
-    let message = '';
1250
-
1251
-    if (micError) {
1252
-        message = `
1253
-            ${message}
1254
-            <h3 data-i18n='dialog.micErrorPresent'></h3>
1255
-            <h4 data-i18n='${micErrorMsg}'></h4>
1256
-            ${additionalMicErrorMsg}`;
1257
-    }
1209
+    const micJitsiTrackErrorMsg
1210
+        = JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[name];
1211
+    const micErrorMsg = micJitsiTrackErrorMsg
1212
+        || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL];
1213
+    const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
1258 1214
 
1259
-    if (cameraError) {
1260
-        message = `
1261
-            ${message}
1262
-            <h3 data-i18n='dialog.cameraErrorPresent'></h3>
1263
-            <h4 data-i18n='${cameraErrorMsg}'></h4>
1264
-            ${additionalCameraErrorMsg}`;
1265
-    }
1215
+    APP.store.dispatch(maybeShowNotificationWithDoNotDisplay(
1216
+        persistenceKey,
1217
+        {
1218
+            additionalMessage: additionalMicErrorMsg,
1219
+            messageKey: micErrorMsg,
1220
+            showToggle: Boolean(micJitsiTrackErrorMsg),
1221
+            subtitleKey: 'dialog.micErrorPresent',
1222
+            titleKey: name === TrackErrors.PERMISSION_DENIED
1223
+                ? 'deviceError.microphonePermission' : 'dialog.error',
1224
+            toggleLabelKey: 'dialog.doNotShowWarningAgain'
1225
+        }));
1226
+};
1266 1227
 
1267
-    // To make sure we don't have multiple error dialogs open at the same time,
1268
-    // we will just close the previous one if we are going to show a new one.
1269
-    deviceErrorDialog && deviceErrorDialog.close();
1228
+/**
1229
+ * Shows a notifications about the passed in camera error.
1230
+ *
1231
+ * @param {JitsiTrackError} cameraError - An error object related to using or
1232
+ * acquiring a video stream.
1233
+ * @returns {void}
1234
+ */
1235
+UI.showCameraErrorNotification = function (cameraError) {
1236
+    if (!cameraError) {
1237
+        return;
1238
+    }
1270 1239
 
1271
-    deviceErrorDialog = messageHandler.openDialog(
1272
-        getTitleKey(),
1273
-        message,
1274
-        false,
1275
-        {Ok: true},
1276
-        function () {},
1277
-        null,
1278
-        function () {
1279
-            // Reset dialog reference to null to avoid memory leaks when
1280
-            // user closed the dialog manually.
1281
-            deviceErrorDialog = null;
1282
-        },
1283
-        showDoNotShowWarning ? dontShowAgain : undefined
1284
-    );
1240
+    const { message, name } = cameraError;
1285 1241
 
1286
-    function getTitleKey() {
1287
-        let title = "dialog.error";
1242
+    const persistenceKey = `doNotShowErrorAgain-camera-${name}`;
1288 1243
 
1289
-        if (micError && micError.name === TrackErrors.PERMISSION_DENIED) {
1290
-            if (!cameraError
1291
-                    || cameraError.name === TrackErrors.PERMISSION_DENIED) {
1292
-                title = "dialog.permissionDenied";
1293
-            }
1294
-        } else if (cameraError
1295
-                && cameraError.name === TrackErrors.PERMISSION_DENIED) {
1296
-            title = "dialog.permissionDenied";
1297
-        }
1244
+    const cameraJitsiTrackErrorMsg =
1245
+        JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
1246
+    const cameraErrorMsg = cameraJitsiTrackErrorMsg
1247
+        || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL];
1248
+    const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
1298 1249
 
1299
-        return title;
1300
-    }
1250
+    APP.store.dispatch(maybeShowNotificationWithDoNotDisplay(
1251
+        persistenceKey,
1252
+        {
1253
+            additionalMessage: additionalCameraErrorMsg,
1254
+            messageKey: cameraErrorMsg,
1255
+            showToggle: Boolean(cameraJitsiTrackErrorMsg),
1256
+            subtitleKey: 'dialog.cameraErrorPresent',
1257
+            titleKey: name === TrackErrors.PERMISSION_DENIED
1258
+                ? 'deviceError.cameraPermission' : 'dialog.error',
1259
+            toggleLabelKey: 'dialog.doNotShowWarningAgain'
1260
+        }));
1301 1261
 };
1302 1262
 
1303 1263
 /**

+ 1
- 1
modules/UI/util/MessageHandler.js 查看文件

@@ -455,7 +455,7 @@ var messageHandler = {
455 455
      * @param optional configurations for the notification (e.g. timeout)
456 456
      */
457 457
     participantNotification: function(displayName, displayNameKey, cls,
458
-                    messageKey, messageArguments, timeout) {
458
+                    messageKey, messageArguments, timeout = 2500) {
459 459
         // If we're in ringing state we skip all notifications.
460 460
         if (!notificationsEnabled || APP.UI.isOverlayVisible()) {
461 461
             return;

+ 8
- 5
modules/devices/mediaDeviceHelper.js 查看文件

@@ -192,9 +192,12 @@ export default {
192 192
                         createVideoTrack(false).then(([stream]) => stream)
193 193
                     ]))
194 194
                     .then(tracks => {
195
-                        if (audioTrackError || videoTrackError) {
196
-                            APP.UI.showDeviceErrorDialog(
197
-                                audioTrackError, videoTrackError);
195
+                        if (audioTrackError) {
196
+                            APP.UI.showMicErrorNotification(audioTrackError);
197
+                        }
198
+
199
+                        if (videoTrackError) {
200
+                            APP.UI.showCameraErrorNotification(videoTrackError);
198 201
                         }
199 202
 
200 203
                         return tracks.filter(t => typeof t !== 'undefined');
@@ -215,7 +218,7 @@ export default {
215 218
                 })
216 219
                 .catch(err => {
217 220
                     audioTrackError = err;
218
-                    showError && APP.UI.showDeviceErrorDialog(err, null);
221
+                    showError && APP.UI.showMicErrorNotification(err);
219 222
                     return [];
220 223
                 });
221 224
         }
@@ -228,7 +231,7 @@ export default {
228 231
                 })
229 232
                 .catch(err => {
230 233
                     videoTrackError = err;
231
-                    showError && APP.UI.showDeviceErrorDialog(null, err);
234
+                    showError && APP.UI.showCameraErrorNotification(err);
232 235
                     return [];
233 236
                 });
234 237
         }

+ 1
- 0
package.json 查看文件

@@ -29,6 +29,7 @@
29 29
     "@atlaskit/multi-select": "6.2.0",
30 30
     "@atlaskit/spinner": "2.2.3",
31 31
     "@atlaskit/tabs": "2.0.0",
32
+    "@atlaskit/toggle": "2.0.4",
32 33
     "@atlassian/aui": "6.0.6",
33 34
     "async": "0.9.0",
34 35
     "autosize": "1.18.13",

+ 1
- 1
react/features/notifications/actionTypes.js 查看文件

@@ -4,7 +4,7 @@
4 4
  *
5 5
  * {
6 6
  *     type: HIDE_NOTIFICATION,
7
- *     uid: string
7
+ *     uid: number
8 8
  * }
9 9
  */
10 10
 export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');

+ 34
- 1
react/features/notifications/actions.js 查看文件

@@ -1,7 +1,10 @@
1
+import jitsiLocalStorage from '../../../modules/util/JitsiLocalStorage';
2
+
1 3
 import {
2 4
     HIDE_NOTIFICATION,
3 5
     SHOW_NOTIFICATION
4 6
 } from './actionTypes';
7
+import { NotificationWithToggle } from './components';
5 8
 
6 9
 /**
7 10
  * Removes the notification with the passed in id.
@@ -10,7 +13,7 @@ import {
10 13
  * removed.
11 14
  * @returns {{
12 15
  *     type: HIDE_NOTIFICATION,
13
- *     uid: string
16
+ *     uid: number
14 17
  * }}
15 18
  */
16 19
 export function hideNotification(uid) {
@@ -45,3 +48,33 @@ export function showNotification(component, props = {}, timeout) {
45 48
         uid: window.Date.now()
46 49
     };
47 50
 }
51
+
52
+/**
53
+ * Displays a notification unless the passed in persistenceKey value exists in
54
+ * local storage and has been set to "true".
55
+ *
56
+ * @param {string} persistenceKey - The local storage key to look up for whether
57
+ * or not the notification should display.
58
+ * @param {Object} props - The props needed to show the notification component.
59
+ * @returns {Function}
60
+ */
61
+export function maybeShowNotificationWithDoNotDisplay(persistenceKey, props) {
62
+    return dispatch => {
63
+        if (jitsiLocalStorage.getItem(persistenceKey) === 'true') {
64
+            return;
65
+        }
66
+
67
+        const newProps = Object.assign({}, props, {
68
+            onToggleSubmit: isToggled => {
69
+                jitsiLocalStorage.setItem(persistenceKey, isToggled);
70
+            }
71
+        });
72
+
73
+        dispatch({
74
+            type: SHOW_NOTIFICATION,
75
+            component: NotificationWithToggle,
76
+            props: newProps,
77
+            uid: window.Date.now()
78
+        });
79
+    };
80
+}

+ 0
- 0
react/features/notifications/components/NotificationWithToggle.native.js 查看文件


+ 210
- 0
react/features/notifications/components/NotificationWithToggle.web.js 查看文件

@@ -0,0 +1,210 @@
1
+import Flag from '@atlaskit/flag';
2
+import WarningIcon from '@atlaskit/icon/glyph/warning';
3
+import { ToggleStateless } from '@atlaskit/toggle';
4
+import React, { Component } from 'react';
5
+
6
+import { translate } from '../../base/i18n';
7
+
8
+/**
9
+ * React {@code Component} for displaying a notification with a toggle element.
10
+ *
11
+ * @extends Component
12
+ */
13
+class NotificationWithToggle extends Component {
14
+    /**
15
+     * {@code NotificationWithToggle} component's property types.
16
+     *
17
+     * @static
18
+     */
19
+    static propTypes = {
20
+        /**
21
+         * Any additional text to display at the end of the notification message
22
+         * body.
23
+         */
24
+        additionalMessage: React.PropTypes.string,
25
+
26
+        /**
27
+         * Whether or not the dismiss button should be displayed. This is passed
28
+         * in by {@code FlagGroup}.
29
+         */
30
+        isDismissAllowed: React.PropTypes.bool,
31
+
32
+        /**
33
+         * The translation key to be used as the main body of the notification.
34
+         */
35
+        messageKey: React.PropTypes.string,
36
+
37
+        /**
38
+         * Callback invoked when the user clicks to dismiss the notification.
39
+         * This is passed in by {@code FlagGroup}.
40
+         */
41
+        onDismissed: React.PropTypes.func,
42
+
43
+        /**
44
+         * Optional callback to invoke when the notification is dismissed. The
45
+         * current value of the toggle element will be passed in.
46
+         */
47
+        onToggleSubmit: React.PropTypes.func,
48
+
49
+        /**
50
+         * Whether or not the toggle element should be displayed.
51
+         */
52
+        showToggle: React.PropTypes.bool,
53
+
54
+        /**
55
+         * Translation key for a message to display at the top of the
56
+         * notification body.
57
+         */
58
+        subtitleKey: React.PropTypes.string,
59
+
60
+        /**
61
+         * Invoked to obtain translated strings.
62
+         */
63
+        t: React.PropTypes.func,
64
+
65
+        /**
66
+         * The translation key to be used as the title of the notification.
67
+         */
68
+        titleKey: React.PropTypes.string,
69
+
70
+        /*
71
+         * The translation key to be used as a label describing what setting the
72
+         * toggle will change.
73
+         */
74
+        toggleLabelKey: React.PropTypes.string,
75
+
76
+        /**
77
+         * The unique identifier for the notification. Passed back by the
78
+         * {@code Flag} component in the onDismissed callback.
79
+         */
80
+        uid: React.PropTypes.number
81
+    };
82
+
83
+    /**
84
+     * Initializes a new {@code NotificationWithToggle} instance.
85
+     *
86
+     * @param {Object} props - The read-only properties with which the new
87
+     * instance is to be initialized.
88
+     */
89
+    constructor(props) {
90
+        super(props);
91
+
92
+        this.state = {
93
+            /**
94
+             * Whether or not the toggle element is active/checked/selected.
95
+             *
96
+             * @type {boolean}
97
+             */
98
+            isToggleChecked: false
99
+        };
100
+
101
+        // Bind event handlers so they are only bound once for every instance.
102
+        this._onDismissed = this._onDismissed.bind(this);
103
+        this._onToggleChange = this._onToggleChange.bind(this);
104
+    }
105
+
106
+    /**
107
+     * Implements React's {@link Component#render()}.
108
+     *
109
+     * @inheritdoc
110
+     * @returns {ReactElement}
111
+     */
112
+    render() {
113
+        const {
114
+            isDismissAllowed,
115
+            t,
116
+            titleKey,
117
+            uid
118
+        } = this.props;
119
+
120
+        return (
121
+            <Flag
122
+                actions = { [
123
+                    {
124
+                        content: t('dialog.Ok'),
125
+                        onClick: this._onDismissed
126
+                    }
127
+                ] }
128
+                appearance = 'warning'
129
+                description = { this._renderDescription() }
130
+                icon = { (
131
+                    <WarningIcon
132
+                        label = 'Warning'
133
+                        size = 'medium' />
134
+                ) }
135
+                id = { uid }
136
+                isDismissAllowed = { isDismissAllowed }
137
+                onDismissed = { this._onDismissed }
138
+                title = { t(titleKey) } />
139
+        );
140
+    }
141
+
142
+    /**
143
+     * Calls back into {@code FlagGroup} to dismiss the notification. Optionally
144
+     * will execute a passed in onToggleSubmit callback with the current state
145
+     * of the toggle element.
146
+     *
147
+     * @private
148
+     * @returns {void}
149
+     */
150
+    _onDismissed() {
151
+        const { onDismissed, onToggleSubmit, showToggle, uid } = this.props;
152
+
153
+        if (showToggle && onToggleSubmit) {
154
+            onToggleSubmit(this.state.isToggleChecked);
155
+        }
156
+
157
+        onDismissed(uid);
158
+    }
159
+
160
+    /**
161
+     * Updates the current known state of the toggle selection.
162
+     *
163
+     * @param {Object} event - The DOM event from changing the toggle selection.
164
+     * @private
165
+     * @returns {void}
166
+     */
167
+    _onToggleChange(event) {
168
+        this.setState({
169
+            isToggleChecked: event.target.checked
170
+        });
171
+    }
172
+
173
+    /**
174
+     * Creates a React Element for displaying the notification message as well
175
+     * as a toggle.
176
+     *
177
+     * @private
178
+     * @returns {ReactElement}
179
+     */
180
+    _renderDescription() {
181
+        const {
182
+            additionalMessage,
183
+            messageKey,
184
+            showToggle,
185
+            subtitleKey,
186
+            t,
187
+            toggleLabelKey
188
+        } = this.props;
189
+
190
+        return (
191
+            <div className = 'notification-with-toggle'>
192
+                <div>{ t(subtitleKey) }</div>
193
+                { messageKey ? <div>{ t(messageKey) }</div> : null }
194
+                { additionalMessage ? <div>{ additionalMessage }</div>
195
+                    : null }
196
+                { showToggle
197
+                    ? <div>
198
+                        { t(toggleLabelKey) }
199
+                        <ToggleStateless
200
+                            isChecked
201
+                                = { this.state.isToggleChecked }
202
+                            onChange = { this._onToggleChange } />
203
+                    </div>
204
+                    : null }
205
+            </div>
206
+        );
207
+    }
208
+}
209
+
210
+export default translate(NotificationWithToggle);

+ 5
- 10
react/features/notifications/components/NotificationsContainer.web.js 查看文件

@@ -4,14 +4,6 @@ import { connect } from 'react-redux';
4 4
 
5 5
 import { hideNotification } from '../actions';
6 6
 
7
-/**
8
- * The duration for which a notification should be displayed before being
9
- * dismissed automatically.
10
- *
11
- * @type {number}
12
- */
13
-const DEFAULT_NOTIFICATION_TIMEOUT = 2500;
14
-
15 7
 /**
16 8
  * Implements a React {@link Component} which displays notifications and handles
17 9
  * automatic dismissmal after a notification is shown for a defined timeout
@@ -74,8 +66,11 @@ class NotificationsContainer extends Component {
74 66
             const { timeout, uid } = notification;
75 67
 
76 68
             this._notificationDismissTimeout = setTimeout(() => {
77
-                this._onDismissed(uid);
78
-            }, timeout || DEFAULT_NOTIFICATION_TIMEOUT);
69
+                // Perform a no-op if a timeout is not specified.
70
+                if (Number.isInteger(timeout)) {
71
+                    this._onDismissed(uid);
72
+                }
73
+            }, timeout);
79 74
         }
80 75
     }
81 76
 

+ 1
- 0
react/features/notifications/components/index.js 查看文件

@@ -1,2 +1,3 @@
1 1
 export { default as Notification } from './Notification';
2 2
 export { default as NotificationsContainer } from './NotificationsContainer';
3
+export { default as NotificationWithToggle } from './NotificationWithToggle';

Loading…
取消
儲存