Kaynağa Gözat

ref(notifications): convert some dialogs to error or warning notifica… (#1991)

* ref(notifications): convert some dialogs to error or warning notifications

- Expand the configurability of the Notification component so warnings
  and errors can be displayed.
- Allow Notification to take in arbitrary text for the body.
- Rename defaultTitleKey to titleKey for consistency with descriptionKey.

* ref(notifications): remove openReportDialog method

openReportDialog is a wrapper around showError that adds
a logger statement. It is being called in one place only
so remove the method and have that one place call logger.

* ref(notifications): UI.showTrackNotWorkingDialog takes a boolean

Change UI.showTrackNotWorkingDialog so it takes a boolean
arguments instead of the entire track. A small refactor so
the method needs to know less.

* [squash] Fixes eslint errors

* WiP: Fixes desktop sharing error strings and adds support button

* [squash] Fix icons appearances

* [squash] Fix translate titles and messages

* [squash] fix(translation): Fixes incorrect password string

* [squash] fix(recording): Fixes recording message

* [squash] fix(warning): Turns some warnings to errors and makes support link optional.

* [squash] fix(translation): Addressing language comments

* [squash] Fixes jsdoc and formatting

* [squash] fix(noopener): Fixes window.open noopener

* [squash] fix(constants): Extract constants and refactor NotificationWithToggle

* [squash] fix(lang): Fixes camera and mic error titles

* [squash] fix(supportLink): Renames addSupportLink to hideErrorSupportLink
master
virtuacoplenny 7 yıl önce
ebeveyn
işleme
510334fa7f

+ 15
- 11
conference.js Dosyayı Görüntüle

@@ -319,9 +319,12 @@ class ConferenceConnector {
319 319
             APP.UI.notifyGracefulShutdown();
320 320
             break;
321 321
 
322
-        case JitsiConferenceErrors.JINGLE_FATAL_ERROR:
323
-            APP.UI.notifyInternalError();
322
+        case JitsiConferenceErrors.JINGLE_FATAL_ERROR: {
323
+            const [ error ] = params;
324
+
325
+            APP.UI.notifyInternalError(error);
324 326
             break;
327
+        }
325 328
 
326 329
         case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
327 330
             const [ reason ] = params;
@@ -1680,20 +1683,21 @@ export default {
1680 1683
         // JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
1681 1684
         // JitsiTrackErrors.GENERAL
1682 1685
         // and any other
1683
-        let dialogTxt;
1684
-        let dialogTitleKey;
1686
+        let descriptionKey;
1687
+        let titleKey;
1685 1688
 
1686 1689
         if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
1687
-            dialogTxt = APP.translation.generateTranslationHTML(
1688
-                'dialog.screenSharingPermissionDeniedError');
1689
-            dialogTitleKey = 'dialog.error';
1690
+            descriptionKey = 'dialog.screenSharingPermissionDeniedError';
1691
+            titleKey = 'dialog.screenSharingFailedToInstallTitle';
1690 1692
         } else {
1691
-            dialogTxt = APP.translation.generateTranslationHTML(
1692
-                'dialog.failtoinstall');
1693
-            dialogTitleKey = 'dialog.permissionDenied';
1693
+            descriptionKey = 'dialog.screenSharingFailedToInstall';
1694
+            titleKey = 'dialog.screenSharingFailedToInstallTitle';
1694 1695
         }
1695 1696
 
1696
-        APP.UI.messageHandler.openDialog(dialogTitleKey, dialogTxt, false);
1697
+        APP.UI.messageHandler.showError({
1698
+            descriptionKey,
1699
+            titleKey
1700
+        });
1697 1701
     },
1698 1702
 
1699 1703
     /**

+ 1
- 1
interface_config.js Dosyayı Görüntüle

@@ -117,7 +117,7 @@ var interfaceConfig = {
117 117
      * If indicated some of the error dialogs may point to the support URL for
118 118
      * help.
119 119
      */
120
-    // SUPPORT_URL: "",
120
+    SUPPORT_URL: 'https://github.com/jitsi/jitsi-meet/issues/new',
121 121
 
122 122
     /**
123 123
      * Whether the connection indicator icon should hide itself based on

+ 41
- 25
lang/main.json Dosyayı Görüntüle

@@ -226,32 +226,36 @@
226 226
         "add": "Add",
227 227
         "allow": "Allow",
228 228
         "kickMessage": "Ouch! You have been kicked out of the meet!",
229
-        "popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
229
+        "popupErrorTitle": "Pop-up blocked",
230
+        "popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
230 231
         "passwordErrorTitle": "Password Error",
231 232
         "passwordError": "This conversation is currently protected by a password. Only the owner of the conference can set a password.",
232 233
         "passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference can set a password.",
233 234
         "connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
234 235
         "connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__",
235
-        "incorrectPassword": "Password is incorrect",
236
+        "incorrectPassword": "Incorrect username or password",
236 237
         "connecting": "Connecting",
237 238
         "copy": "Copy",
239
+        "contactSupport": "Contact support",
238 240
         "error": "Error",
239 241
         "createPassword": "Create password",
240 242
         "detectext": "Error when trying to detect desktopsharing extension.",
241
-        "failtoinstall": "Failed to install desktop sharing extension",
242 243
         "failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
243 244
         "conferenceReloadTitle": "Unfortunately, something went wrong.",
244 245
         "conferenceReloadMsg": "We're trying to fix this. Reconnecting in __seconds__ sec...",
245 246
         "conferenceDisconnectTitle": "You have been disconnected.",
246 247
         "conferenceDisconnectMsg": "You may want to check your network connection. Reconnecting in __seconds__ sec...",
248
+        "dismiss": "Dismiss",
247 249
         "rejoinNow": "Rejoin now",
248
-        "maxUsersLimitReached": "The limit for maximum number of members in the conference has been reached. The conference is full. Please try again later!",
250
+        "maxUsersLimitReachedTitle": "Maximum members limit reached",
251
+        "maxUsersLimitReached": "The limit for maximum number of members has been reached. The conference is full. Please contact the meeting owner or try again later!",
249 252
         "lockTitle": "Lock failed",
250 253
         "lockMessage": "Failed to lock the conference.",
251 254
         "warning": "Warning",
252
-        "passwordNotSupported": "Room passwords are currently not supported.",
255
+        "passwordNotSupportedTitle": "Password not supported",
256
+        "passwordNotSupported": "Setting a meeting password is not supported.",
253 257
         "internalErrorTitle": "Internal error",
254
-        "internalError": "Oups! Something went wrong. The following error occurred: [setRemoteDescription]",
258
+        "internalError": "Oops! Something went wrong. The following error occurred: __error__",
255 259
         "unableToSwitch": "Unable to switch video stream.",
256 260
         "SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
257 261
         "SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
@@ -268,7 +272,8 @@
268 272
         "shareVideoLinkError": "Please provide a correct youtube link.",
269 273
         "removeSharedVideoTitle": "Remove shared video",
270 274
         "removeSharedVideoMsg": "Are you sure you would like to remove your shared video?",
271
-        "alreadySharedVideoMsg": "Another member is already sharing video. This conference allows only one shared video at a time.",
275
+        "alreadySharedVideoMsg": "Another member is already sharing a video. This conference allows only one shared video at a time.",
276
+        "alreadySharedVideoTitle": "Only one shared video is allowed at a time",
272 277
         "WaitingForHost": "Waiting for the host ...",
273 278
         "WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
274 279
         "IamHost": "I am the host",
@@ -277,7 +282,7 @@
277 282
         "retry": "Retry",
278 283
         "logoutTitle" : "Logout",
279 284
         "logoutQuestion" : "Are you sure you want to logout and stop the conference?",
280
-        "sessTerminated": "Session Terminated",
285
+        "sessTerminated": "Call terminated",
281 286
         "hungUp": "You hung up",
282 287
         "joinAgain": "Join again",
283 288
         "Share": "Share",
@@ -297,7 +302,7 @@
297 302
         "password": "Enter password",
298 303
         "userPassword": "user password",
299 304
         "token": "token",
300
-        "tokenAuthFailedTitle": "Authentication problem",
305
+        "tokenAuthFailedTitle": "Authentication failed",
301 306
         "tokenAuthFailed": "Sorry, you're not allowed to join this call.",
302 307
         "displayNameRequired": "Display name is required",
303 308
         "enterDisplayName": "Please enter your display name",
@@ -317,7 +322,9 @@
317 322
         "doNotShowWarningAgain": "Don't show this warning again",
318 323
         "doNotShowMessageAgain": "Don't show this message again",
319 324
         "permissionDenied": "Permission Denied",
320
-        "screenSharingPermissionDeniedError": "You have not granted permission to share your screen.",
325
+        "screenSharingFailedToInstall": "Oops! Your screen sharing extension failed to install.",
326
+        "screenSharingFailedToInstallTitle": "Screen sharing extension failed to install",
327
+        "screenSharingPermissionDeniedError": "Oops! Something went wrong with your screen sharing extension permissions. Please reload and try again.",
321 328
         "micErrorPresent": "There was an error connecting to your microphone.",
322 329
         "cameraErrorPresent": "There was an error connecting to your camera.",
323 330
         "cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
@@ -329,8 +336,10 @@
329 336
         "micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
330 337
         "micNotFoundError": "Microphone was not found.",
331 338
         "micConstraintFailedError": "Your microphone does not satisfy some of the required constraints.",
332
-        "micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
333
-        "cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to restart the application.",
339
+        "micNotSendingDataTitle": "Unable to access microphone",
340
+        "micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to reload the application.",
341
+        "cameraNotSendingDataTitle": "Unable to access camera",
342
+        "cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to reload the application.",
334 343
         "goToStore": "Go to the webstore",
335 344
         "externalInstallationTitle": "Extension required",
336 345
         "externalInstallationMsg": "You need to install our desktop sharing extension.",
@@ -401,26 +410,31 @@
401 410
     },
402 411
     "recording":
403 412
     {
404
-        "pending": "Recording waiting for a member to join...",
405
-        "on": "Recording",
406
-        "off": "Recording stopped",
407
-        "failedToStart": "Recording failed to start",
413
+        "busy": "We're working on freeing recording resources. Please try again in a few minutes.",
414
+        "busyTitle": "All recorders are currently busy",
408 415
         "buttonTooltip": "Start / Stop recording",
409 416
         "error": "Recording failed. Please try again.",
410
-        "unavailable": "The recording service is currently unavailable. Please try again later."
417
+        "failedToStart": "Recording failed to start",
418
+        "off": "Recording stopped",
419
+        "on": "Recording",
420
+        "pending": "Recording waiting for a member to join...",
421
+        "unavailable": "Oops! The recording service is currently unavailable. We're working on resolving the issue. Please try again later.",
422
+        "unavailableTitle": "Recording unavailable"
411 423
     },
412 424
     "liveStreaming":
413 425
     {
414
-        "pending": "Starting Live Stream...",
426
+        "busy": "We're working on freeing streaming resources. Please try again in a few minutes.",
427
+        "busyTitle": "All streamers are currently busy",
428
+        "buttonTooltip": "Start / Stop Live Stream",
429
+        "error": "Live Streaming failed. Please try again.",
430
+        "failedToStart": "Live Streaming failed to start",
431
+        "off": "Live Streaming stopped",
415 432
         "on": "Live Streaming",
416
-        "off": "Live Streaming Stopped",
417
-        "unavailable": "The live streaming service is currently unavailable. Please try again later.",
418
-        "failedToStart": "Live streaming failed to start",
419
-        "buttonTooltip": "Start / Stop live stream",
420
-        "streamIdRequired": "Please fill in the stream id in order to launch the live streaming.",
433
+        "pending": "Starting Live Stream...",
434
+        "streamIdRequired": "Please fill in the stream id in order to launch the Live Streaming.",
421 435
         "streamIdHelp": "Where do I find this?",
422
-        "error": "Live streaming failed. Please try again.",
423
-        "busy": "All recorders are currently busy. Please try again later."
436
+        "unavailable": "Oops! The Live Streaming service is currently unavailable. We're working on resolving the issue. Please try again later.",
437
+        "unavailableTitle": "Live Streaming unavailable"
424 438
     },
425 439
     "speakerStats":
426 440
     {
@@ -487,6 +501,8 @@
487 501
         "supportMsg": "If this keeps happening, reach out to"
488 502
     },
489 503
     "deviceError": {
504
+        "cameraError": "Failed to access your camera",
505
+        "microphoneError": "Failed to access your microphone",
490 506
         "cameraPermission": "Error obtaining camera permission",
491 507
         "microphonePermission": "Error obtaining microphone permission"
492 508
     },

+ 61
- 41
modules/UI/UI.js Dosyayı Görüntüle

@@ -144,35 +144,35 @@ UI.toggleFullScreen = function() {
144 144
  * Notify user that server has shut down.
145 145
  */
146 146
 UI.notifyGracefulShutdown = function() {
147
-    messageHandler.openMessageDialog(
148
-        'dialog.serviceUnavailable',
149
-        'dialog.gracefulShutdown'
150
-    );
147
+    messageHandler.showError({
148
+        descriptionKey: 'dialog.gracefulShutdown',
149
+        titleKey: 'dialog.serviceUnavailable'
150
+    });
151 151
 };
152 152
 
153 153
 /**
154 154
  * Notify user that reservation error happened.
155 155
  */
156 156
 UI.notifyReservationError = function(code, msg) {
157
-    const message
158
-        = APP.translation.generateTranslationHTML(
159
-            'dialog.reservationErrorMsg',
160
-            {
161
-                code,
162
-                msg
163
-            });
164
-
165
-    messageHandler.openDialog(
166
-        'dialog.reservationError', message, true, {}, () => false);
157
+    messageHandler.showError({
158
+        descriptionArguments: {
159
+            code,
160
+            msg
161
+        },
162
+        descriptionKey: 'dialog.reservationErrorMsg',
163
+        titleKey: 'dialog.reservationError'
164
+    });
167 165
 };
168 166
 
169 167
 /**
170 168
  * Notify user that he has been kicked from the server.
171 169
  */
172 170
 UI.notifyKicked = function() {
173
-    messageHandler.openMessageDialog(
174
-        'dialog.sessTerminated',
175
-        'dialog.kickMessage');
171
+    messageHandler.showError({
172
+        hideErrorSupportLink: true,
173
+        descriptionKey: 'dialog.kickMessage',
174
+        titleKey: 'dialog.sessTerminated'
175
+    });
176 176
 };
177 177
 
178 178
 /**
@@ -182,8 +182,10 @@ UI.notifyKicked = function() {
182 182
 UI.notifyConferenceDestroyed = function(reason) {
183 183
     // FIXME: use Session Terminated from translation, but
184 184
     // 'reason' text comes from XMPP packet and is not translated
185
-    messageHandler.openDialog(
186
-        'dialog.sessTerminated', reason, true, {}, () => false);
185
+    messageHandler.showError({
186
+        description: reason,
187
+        titleKey: 'dialog.sessTerminated'
188
+    });
187 189
 };
188 190
 
189 191
 /**
@@ -833,17 +835,21 @@ UI.setUserAvatarUrl = function(id, url) {
833 835
  * @param {string} stropheErrorMsg raw Strophe error message
834 836
  */
835 837
 UI.notifyConnectionFailed = function(stropheErrorMsg) {
836
-    let message;
838
+    let descriptionKey;
839
+    let descriptionArguments;
837 840
 
838 841
     if (stropheErrorMsg) {
839
-        message = APP.translation.generateTranslationHTML(
840
-            'dialog.connectErrorWithMsg', { msg: stropheErrorMsg });
842
+        descriptionKey = 'dialog.connectErrorWithMsg';
843
+        descriptionArguments = { msg: stropheErrorMsg };
841 844
     } else {
842
-        message = APP.translation.generateTranslationHTML(
843
-            'dialog.connectError');
845
+        descriptionKey = 'dialog.connectError';
844 846
     }
845 847
 
846
-    messageHandler.openDialog('dialog.error', message, true, {}, () => false);
848
+    messageHandler.showError({
849
+        descriptionArguments,
850
+        descriptionKey,
851
+        titleKey: 'connection.CONNFAIL'
852
+    });
847 853
 };
848 854
 
849 855
 
@@ -851,10 +857,11 @@ UI.notifyConnectionFailed = function(stropheErrorMsg) {
851 857
  * Notify user that maximum users limit has been reached.
852 858
  */
853 859
 UI.notifyMaxUsersLimitReached = function() {
854
-    const message = APP.translation.generateTranslationHTML(
855
-            'dialog.maxUsersLimitReached');
856
-
857
-    messageHandler.openDialog('dialog.error', message, true, {}, () => false);
860
+    messageHandler.showError({
861
+        hideErrorSupportLink: true,
862
+        descriptionKey: 'dialog.maxUsersLimitReached',
863
+        titleKey: 'dialog.maxUsersLimitReachedTitle'
864
+    });
858 865
 };
859 866
 
860 867
 /**
@@ -955,13 +962,18 @@ UI.updateRecordingState = function(state) {
955 962
 };
956 963
 
957 964
 UI.notifyTokenAuthFailed = function() {
958
-    messageHandler.showError('dialog.tokenAuthFailedTitle',
959
-                                'dialog.tokenAuthFailed');
965
+    messageHandler.showError({
966
+        descriptionKey: 'dialog.tokenAuthFailed',
967
+        titleKey: 'dialog.tokenAuthFailedTitle'
968
+    });
960 969
 };
961 970
 
962
-UI.notifyInternalError = function() {
963
-    messageHandler.showError('dialog.internalErrorTitle',
964
-                                'dialog.internalError');
971
+UI.notifyInternalError = function(error) {
972
+    messageHandler.showError({
973
+        descriptionArguments: { error },
974
+        descriptionKey: 'dialog.internalError',
975
+        titleKey: 'dialog.internalErrorTitle'
976
+    });
965 977
 };
966 978
 
967 979
 UI.notifyFocusDisconnected = function(focus, retrySec) {
@@ -1158,7 +1170,8 @@ UI.showMicErrorNotification = function(micError) {
1158 1170
             showToggle: Boolean(micJitsiTrackErrorMsg),
1159 1171
             subtitleKey: 'dialog.micErrorPresent',
1160 1172
             titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
1161
-                ? 'deviceError.microphonePermission' : 'dialog.error',
1173
+                ? 'deviceError.microphonePermission'
1174
+                : 'deviceError.microphoneError',
1162 1175
             toggleLabelKey: 'dialog.doNotShowWarningAgain'
1163 1176
         }));
1164 1177
 };
@@ -1194,7 +1207,7 @@ UI.showCameraErrorNotification = function(cameraError) {
1194 1207
             showToggle: Boolean(cameraJitsiTrackErrorMsg),
1195 1208
             subtitleKey: 'dialog.cameraErrorPresent',
1196 1209
             titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
1197
-                ? 'deviceError.cameraPermission' : 'dialog.error',
1210
+                ? 'deviceError.cameraPermission' : 'deviceError.cameraError',
1198 1211
             toggleLabelKey: 'dialog.doNotShowWarningAgain'
1199 1212
         }));
1200 1213
 };
@@ -1202,12 +1215,19 @@ UI.showCameraErrorNotification = function(cameraError) {
1202 1215
 /**
1203 1216
  * Shows error dialog that informs the user that no data is received from the
1204 1217
  * device.
1218
+ *
1219
+ * @param {boolean} isAudioTrack - Whether or not the dialog is for an audio
1220
+ * track error.
1221
+ * @returns {void}
1205 1222
  */
1206
-UI.showTrackNotWorkingDialog = function(stream) {
1207
-    messageHandler.openMessageDialog(
1208
-        'dialog.error',
1209
-        stream.isAudioTrack() ? 'dialog.micNotSendingData'
1210
-            : 'dialog.cameraNotSendingData');
1223
+UI.showTrackNotWorkingDialog = function(isAudioTrack) {
1224
+    messageHandler.showError({
1225
+        descriptionKey: isAudioTrack
1226
+            ? 'dialog.micNotSendingData' : 'dialog.cameraNotSendingData',
1227
+        titleKey: isAudioTrack
1228
+            ? 'dialog.micNotSendingDataTitle'
1229
+            : 'dialog.cameraNotSendingDataTitle'
1230
+    });
1211 1231
 };
1212 1232
 
1213 1233
 UI.updateDevicesAvailability = function(id, devices) {

+ 4
- 1
modules/UI/authentication/LoginDialog.js Dosyayı Görüntüle

@@ -203,7 +203,10 @@ export default {
203 203
         );
204 204
 
205 205
         if (!dialog) {
206
-            APP.UI.messageHandler.openMessageDialog(null, 'dialog.popupError');
206
+            APP.UI.messageHandler.showWarning({
207
+                descriptionKey: 'dialog.popupError',
208
+                titleKey: 'dialog.popupErrorTitle'
209
+            });
207 210
         }
208 211
 
209 212
         return dialog;

+ 15
- 19
modules/UI/recording/Recording.js Dosyayı Görüntüle

@@ -42,14 +42,16 @@ import {
42 42
  */
43 43
 export const RECORDING_TRANSLATION_KEYS = {
44 44
     failedToStartKey: 'recording.failedToStart',
45
-    recordingBusy: 'liveStreaming.busy',
45
+    recordingBusy: 'recording.busy',
46
+    recordingBusyTitle: 'recording.busyTitle',
46 47
     recordingButtonTooltip: 'recording.buttonTooltip',
47 48
     recordingErrorKey: 'recording.error',
48 49
     recordingOffKey: 'recording.off',
49 50
     recordingOnKey: 'recording.on',
50 51
     recordingPendingKey: 'recording.pending',
51 52
     recordingTitle: 'dialog.recording',
52
-    recordingUnavailable: 'recording.unavailable'
53
+    recordingUnavailable: 'recording.unavailable',
54
+    recordingUnavailableTitle: 'recording.unavailableTitle'
53 55
 };
54 56
 
55 57
 /**
@@ -62,13 +64,15 @@ export const RECORDING_TRANSLATION_KEYS = {
62 64
 export const STREAMING_TRANSLATION_KEYS = {
63 65
     failedToStartKey: 'liveStreaming.failedToStart',
64 66
     recordingBusy: 'liveStreaming.busy',
67
+    recordingBusyTitle: 'liveStreaming.busyTitle',
65 68
     recordingButtonTooltip: 'liveStreaming.buttonTooltip',
66 69
     recordingErrorKey: 'liveStreaming.error',
67 70
     recordingOffKey: 'liveStreaming.off',
68 71
     recordingOnKey: 'liveStreaming.on',
69 72
     recordingPendingKey: 'liveStreaming.pending',
70 73
     recordingTitle: 'dialog.liveStreaming',
71
-    recordingUnavailable: 'liveStreaming.unavailable'
74
+    recordingUnavailable: 'liveStreaming.unavailable',
75
+    recordingUnavailableTitle: 'liveStreaming.unavailableTitle'
72 76
 };
73 77
 
74 78
 /**
@@ -513,25 +517,17 @@ const Recording = {
513 517
             break;
514 518
         }
515 519
         case JitsiRecordingStatus.BUSY: {
516
-            dialog = APP.UI.messageHandler.openMessageDialog(
517
-                this.recordingTitle,
518
-                this.recordingBusy,
519
-                null,
520
-                () => {
521
-                    dialog = null;
522
-                }
523
-            );
520
+            APP.UI.messageHandler.showWarning({
521
+                descriptionKey: this.recordingBusy,
522
+                titleKey: this.recordingBusyTitle
523
+            });
524 524
             break;
525 525
         }
526 526
         default: {
527
-            dialog = APP.UI.messageHandler.openMessageDialog(
528
-                this.recordingTitle,
529
-                this.recordingUnavailable,
530
-                null,
531
-                () => {
532
-                    dialog = null;
533
-                }
534
-            );
527
+            APP.UI.messageHandler.showError({
528
+                descriptionKey: this.recordingUnavailable,
529
+                titleKey: this.recordingUnavailableTitle
530
+            });
535 531
         }
536 532
         }
537 533
     },

+ 4
- 8
modules/UI/shared_video/SharedVideo.js Dosyayı Görüntüle

@@ -111,14 +111,10 @@ export default class SharedVideoManager {
111 111
                 },
112 112
                 () => {}); // eslint-disable-line no-empty-function
113 113
         } else {
114
-            dialog = APP.UI.messageHandler.openMessageDialog(
115
-                'dialog.shareVideoTitle',
116
-                'dialog.alreadySharedVideoMsg',
117
-                null,
118
-                () => {
119
-                    dialog = null;
120
-                }
121
-            );
114
+            APP.UI.messageHandler.showWarning({
115
+                descriptionKey: 'dialog.alreadySharedVideoMsg',
116
+                titleKey: 'dialog.alreadySharedVideoTitle'
117
+            });
122 118
             sendAnalyticsEvent('sharedvideo.alreadyshared');
123 119
         }
124 120
     }

+ 15
- 16
modules/UI/util/MessageHandler.js Dosyayı Görüntüle

@@ -5,7 +5,9 @@ import jitsiLocalStorage from '../../util/JitsiLocalStorage';
5 5
 
6 6
 import {
7 7
     Notification,
8
-    showNotification
8
+    showErrorNotification,
9
+    showNotification,
10
+    showWarningNotification
9 11
 } from '../../../react/features/notifications';
10 12
 
11 13
 /**
@@ -453,26 +455,23 @@ const messageHandler = {
453 455
     },
454 456
 
455 457
     /**
456
-     * Shows a dialog prompting the user to send an error report.
458
+     * Shows an error dialog to the user.
457 459
      *
458
-     * @param titleKey the title of the message
459
-     * @param msgKey the text of the message
460
-     * @param error the error that is being reported
460
+     * @param {object} props - The properties to pass to the
461
+     * showErrorNotification action.
461 462
      */
462
-    openReportDialog(titleKey, msgKey, error) {
463
-        this.openMessageDialog(titleKey, msgKey);
464
-        logger.log(error);
465
-
466
-        // FIXME send the error to the server
463
+    showError(props) {
464
+        APP.store.dispatch(showErrorNotification(props));
467 465
     },
468 466
 
469 467
     /**
470
-     *  Shows an error dialog to the user.
471
-     * @param titleKey the title of the message.
472
-     * @param msgKey the text of the message.
468
+     * Shows a warning dialog to the user.
469
+     *
470
+     * @param {object} props - The properties to pass to the
471
+     * showWarningNotification action.
473 472
      */
474
-    showError(titleKey = 'dialog.oops', msgKey = 'dialog.defaultError') {
475
-        messageHandler.openMessageDialog(titleKey, msgKey);
473
+    showWarning(props) {
474
+        APP.store.dispatch(showWarningNotification(props));
476 475
     },
477 476
 
478 477
     /**
@@ -498,9 +497,9 @@ const messageHandler = {
498 497
             showNotification(
499 498
                 Notification,
500 499
                 {
501
-                    defaultTitleKey: displayNameKey,
502 500
                     descriptionArguments: messageArguments,
503 501
                     descriptionKey: messageKey,
502
+                    titleKey: displayNameKey,
504 503
                     title: displayName
505 504
                 },
506 505
                 timeout));

+ 2
- 1
react/features/base/tracks/functions.js Dosyayı Görüntüle

@@ -75,7 +75,8 @@ export function createLocalTracksF(
75 75
                 tracks.forEach(track =>
76 76
                     track.on(
77 77
                         JitsiTrackEvents.NO_DATA_FROM_SOURCE,
78
-                        APP.UI.showTrackNotWorkingDialog.bind(null, track)));
78
+                        APP.UI.showTrackNotWorkingDialog.bind(
79
+                            null, track.isAudioTrack())));
79 80
             }
80 81
 
81 82
             return tracks;

+ 6
- 4
react/features/conference/route.js Dosyayı Görüntüle

@@ -77,11 +77,13 @@ function _obtainConfigAndInit() {
77 77
                     _initConference();
78 78
                 })
79 79
                 .catch(err => {
80
+                    logger.log(err);
81
+
80 82
                     // Show obtain config error.
81
-                    APP.UI.messageHandler.openReportDialog(
82
-                        null,
83
-                        'dialog.connectError',
84
-                        err);
83
+                    APP.UI.messageHandler.showError({
84
+                        titleKey: 'connection.CONNFAIL',
85
+                        descriptionKey: 'dialog.connectError'
86
+                    });
85 87
                 });
86 88
         } else {
87 89
             chooseBOSHAddress(config, room);

+ 32
- 1
react/features/notifications/actions.js Dosyayı Görüntüle

@@ -5,7 +5,12 @@ import {
5 5
     SET_NOTIFICATIONS_ENABLED,
6 6
     SHOW_NOTIFICATION
7 7
 } from './actionTypes';
8
-import { NotificationWithToggle } from './components';
8
+import {
9
+    Notification,
10
+    NotificationWithToggle
11
+} from './components';
12
+
13
+import { NOTIFICATION_TYPE } from './constants';
9 14
 
10 15
 /**
11 16
  * Removes the notification with the passed in id.
@@ -40,6 +45,19 @@ export function setNotificationsEnabled(enabled) {
40 45
     };
41 46
 }
42 47
 
48
+/**
49
+ * Queues an error notification for display.
50
+ *
51
+ * @param {Object} props - The props needed to show the notification component.
52
+ * @returns {Object}
53
+ */
54
+export function showErrorNotification(props) {
55
+    return showNotification(Notification, {
56
+        ...props,
57
+        appearance: NOTIFICATION_TYPE.ERROR
58
+    });
59
+}
60
+
43 61
 /**
44 62
  * Queues a notification for display.
45 63
  *
@@ -66,6 +84,19 @@ export function showNotification(component, props = {}, timeout) {
66 84
     };
67 85
 }
68 86
 
87
+/**
88
+ * Queues a warning notification for display.
89
+ *
90
+ * @param {Object} props - The props needed to show the notification component.
91
+ * @returns {Object}
92
+ */
93
+export function showWarningNotification(props) {
94
+    return showNotification(Notification, {
95
+        ...props,
96
+        appearance: NOTIFICATION_TYPE.WARNING
97
+    });
98
+}
99
+
69 100
 /**
70 101
  * Displays a notification unless the passed in persistenceKey value exists in
71 102
  * local storage and has been set to "true".

+ 178
- 12
react/features/notifications/components/Notification.web.js Dosyayı Görüntüle

@@ -1,16 +1,45 @@
1 1
 import Flag from '@atlaskit/flag';
2 2
 import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
3 3
 import PropTypes from 'prop-types';
4
+import ErrorIcon from '@atlaskit/icon/glyph/error';
5
+import WarningIcon from '@atlaskit/icon/glyph/warning';
6
+import { colors } from '@atlaskit/theme';
4 7
 import React, { Component } from 'react';
5 8
 
6 9
 import { translate } from '../../base/i18n';
7 10
 
11
+import { NOTIFICATION_TYPE } from '../constants';
12
+
13
+declare var interfaceConfig: Object;
14
+
15
+/**
16
+ * Secondary colors for notification icons.
17
+ *
18
+ * @type {{error, info, normal, success, warning}}
19
+ */
20
+const ICON_COLOR = {
21
+    error: colors.R400,
22
+    info: colors.N500,
23
+    normal: colors.N0,
24
+    success: colors.G400,
25
+    warning: colors.Y200
26
+};
27
+
8 28
 /**
9 29
  * Implements a React {@link Component} to display a notification.
10 30
  *
11 31
  * @extends Component
12 32
  */
13 33
 class Notification extends Component {
34
+    /**
35
+     * Default values for {@code Notification} component's properties.
36
+     *
37
+     * @static
38
+     */
39
+    static defaultProps = {
40
+        appearance: NOTIFICATION_TYPE.NORMAL
41
+    };
42
+
14 43
     /**
15 44
      * {@code Notification} component's property types.
16 45
      *
@@ -18,11 +47,22 @@ class Notification extends Component {
18 47
      */
19 48
     static propTypes = {
20 49
         /**
21
-         * The translation key to display as the title of the notification if
22
-         * no title is provided.
50
+         * Display appearance for the component, passed directly to
51
+         * {@code Flag}.
52
+         */
53
+        appearance: PropTypes.string,
54
+
55
+        /**
56
+         * The text to display in the body of the notification. If not passed
57
+         * in, the passed in descriptionKey will be used.
23 58
          */
24 59
         defaultTitleKey: PropTypes.string,
25 60
 
61
+        /**
62
+         * The description string.
63
+         */
64
+        description: PropTypes.string,
65
+
26 66
         /**
27 67
          * The translation arguments that may be necessary for the description.
28 68
          */
@@ -33,6 +73,12 @@ class Notification extends Component {
33 73
          */
34 74
         descriptionKey: PropTypes.string,
35 75
 
76
+        /**
77
+         * Whether the support link should be hidden in the case of an error
78
+         * message.
79
+         */
80
+        hideErrorSupportLink: PropTypes.bool,
81
+
36 82
         /**
37 83
          * Whether or not the dismiss button should be displayed. This is passed
38 84
          * in by {@code FlagGroup}.
@@ -52,10 +98,16 @@ class Notification extends Component {
52 98
 
53 99
         /**
54 100
          * The text to display at the top of the notification. If not passed in,
55
-         * the passed in defaultTitleKey will be used.
101
+         * the passed in titleKey will be used.
56 102
          */
57 103
         title: PropTypes.string,
58 104
 
105
+        /**
106
+         * The translation key to display as the title of the notification if
107
+         * no title is provided.
108
+         */
109
+        titleKey: PropTypes.string,
110
+
59 111
         /**
60 112
          * The unique identifier for the notification. Passed back by the
61 113
          * {@code Flag} component in the onDismissed callback.
@@ -63,6 +115,19 @@ class Notification extends Component {
63 115
         uid: PropTypes.number
64 116
     };
65 117
 
118
+    /**
119
+     * Initializes a new {@code Notification} instance.
120
+     *
121
+     * @param {Object} props - The read-only properties with which the new
122
+     * instance is to be initialized.
123
+     */
124
+    constructor(props) {
125
+        super(props);
126
+
127
+        // Bind event handler so it is only bound once for every instance.
128
+        this._onDismissed = this._onDismissed.bind(this);
129
+    }
130
+
66 131
     /**
67 132
      * Implements React's {@link Component#render()}.
68 133
      *
@@ -71,9 +136,12 @@ class Notification extends Component {
71 136
      */
72 137
     render() {
73 138
         const {
74
-            defaultTitleKey,
139
+            hideErrorSupportLink,
140
+            appearance,
141
+            titleKey,
75 142
             descriptionArguments,
76 143
             descriptionKey,
144
+            description,
77 145
             isDismissAllowed,
78 146
             onDismissed,
79 147
             t,
@@ -83,19 +151,117 @@ class Notification extends Component {
83 151
 
84 152
         return (
85 153
             <Flag
86
-                appearance = 'normal'
87
-                description = { t(descriptionKey, descriptionArguments) }
88
-                icon = { (
89
-                    <EditorInfoIcon
90
-                        label = 'info'
91
-                        size = 'medium' />
92
-                ) }
154
+                actions = { this._mapAppearanceToButtons(hideErrorSupportLink) }
155
+                appearance = { appearance }
156
+                description = { description
157
+                    || t(descriptionKey, descriptionArguments) }
158
+                icon = { this._mapAppearanceToIcon() }
93 159
                 id = { uid }
94 160
                 isDismissAllowed = { isDismissAllowed }
95 161
                 onDismissed = { onDismissed }
96
-                title = { title || t(defaultTitleKey) } />
162
+                title = { title || t(titleKey) } />
97 163
         );
98 164
     }
165
+
166
+    /**
167
+     * Calls back into {@code FlagGroup} to dismiss the notification.
168
+     *
169
+     * @private
170
+     * @returns {void}
171
+     */
172
+    _onDismissed() {
173
+        this.props.onDismissed(this.props.uid);
174
+    }
175
+
176
+    /**
177
+     * Opens the support page.
178
+     *
179
+     * @returns {void}
180
+     * @private
181
+     */
182
+    _onOpenSupportLink() {
183
+        window.open(interfaceConfig.SUPPORT_URL, '_blank', 'noopener');
184
+    }
185
+
186
+    /**
187
+     * Creates action button configurations for the notification based on
188
+     * notification appearance.
189
+     *
190
+     * @param {boolean} hideErrorSupportLink - Indicates if the support link
191
+     * should be hidden in the error messages.
192
+     * @private
193
+     * @returns {Object[]}
194
+     */
195
+    _mapAppearanceToButtons(hideErrorSupportLink) {
196
+        switch (this.props.appearance) {
197
+        case NOTIFICATION_TYPE.ERROR: {
198
+            const buttons = [
199
+                {
200
+                    content: this.props.t('dialog.dismiss'),
201
+                    onClick: this._onDismissed
202
+                }
203
+            ];
204
+
205
+            if (!hideErrorSupportLink) {
206
+                buttons.push({
207
+                    content: this.props.t('dialog.contactSupport'),
208
+                    onClick: this._onOpenSupportLink
209
+                });
210
+            }
211
+
212
+            return buttons;
213
+        }
214
+        case NOTIFICATION_TYPE.WARNING:
215
+            return [
216
+                {
217
+                    content: this.props.t('dialog.Ok'),
218
+                    onClick: this._onDismissed
219
+                }
220
+            ];
221
+
222
+        default:
223
+            return [];
224
+        }
225
+    }
226
+
227
+    /**
228
+     * Creates an icon component depending on the configured notification
229
+     * appearance.
230
+     *
231
+     * @private
232
+     * @returns {ReactElement}
233
+     */
234
+    _mapAppearanceToIcon() {
235
+        const appearance = this.props.appearance;
236
+        const secIconColor = ICON_COLOR[this.props.appearance];
237
+        const iconSize = 'medium';
238
+
239
+        switch (appearance) {
240
+        case NOTIFICATION_TYPE.ERROR:
241
+            return (
242
+                <ErrorIcon
243
+                    label = { appearance }
244
+                    secondaryColor = { secIconColor }
245
+                    size = { iconSize } />
246
+            );
247
+
248
+        case NOTIFICATION_TYPE.WARNING:
249
+            return (
250
+                <WarningIcon
251
+                    label = { appearance }
252
+                    secondaryColor = { secIconColor }
253
+                    size = { iconSize } />
254
+            );
255
+
256
+        default:
257
+            return (
258
+                <EditorInfoIcon
259
+                    label = { appearance }
260
+                    secondaryColor = { secIconColor }
261
+                    size = { iconSize } />
262
+            );
263
+        }
264
+    }
99 265
 }
100 266
 
101 267
 export default translate(Notification);

+ 12
- 63
react/features/notifications/components/NotificationWithToggle.web.js Dosyayı Görüntüle

@@ -1,11 +1,12 @@
1
-import Flag from '@atlaskit/flag';
2
-import WarningIcon from '@atlaskit/icon/glyph/warning';
3 1
 import { ToggleStateless } from '@atlaskit/toggle';
4 2
 import PropTypes from 'prop-types';
5 3
 import React, { Component } from 'react';
6 4
 
7 5
 import { translate } from '../../base/i18n';
8 6
 
7
+import { default as Notification } from './Notification';
8
+import { NOTIFICATION_TYPE } from '../constants';
9
+
9 10
 /**
10 11
  * React {@code Component} for displaying a notification with a toggle element.
11 12
  *
@@ -18,29 +19,14 @@ class NotificationWithToggle extends Component {
18 19
      * @static
19 20
      */
20 21
     static propTypes = {
22
+        ...Notification.propTypes,
23
+
21 24
         /**
22 25
          * Any additional text to display at the end of the notification message
23 26
          * body.
24 27
          */
25 28
         additionalMessage: PropTypes.string,
26 29
 
27
-        /**
28
-         * Whether or not the dismiss button should be displayed. This is passed
29
-         * in by {@code FlagGroup}.
30
-         */
31
-        isDismissAllowed: PropTypes.bool,
32
-
33
-        /**
34
-         * The translation key to be used as the main body of the notification.
35
-         */
36
-        messageKey: PropTypes.string,
37
-
38
-        /**
39
-         * Callback invoked when the user clicks to dismiss the notification.
40
-         * This is passed in by {@code FlagGroup}.
41
-         */
42
-        onDismissed: PropTypes.func,
43
-
44 30
         /**
45 31
          * Optional callback to invoke when the notification is dismissed. The
46 32
          * current value of the toggle element will be passed in.
@@ -58,27 +44,11 @@ class NotificationWithToggle extends Component {
58 44
          */
59 45
         subtitleKey: PropTypes.string,
60 46
 
61
-        /**
62
-         * Invoked to obtain translated strings.
63
-         */
64
-        t: PropTypes.func,
65
-
66
-        /**
67
-         * The translation key to be used as the title of the notification.
68
-         */
69
-        titleKey: PropTypes.string,
70
-
71 47
         /*
72 48
          * The translation key to be used as a label describing what setting the
73 49
          * toggle will change.
74 50
          */
75
-        toggleLabelKey: PropTypes.string,
76
-
77
-        /**
78
-         * The unique identifier for the notification. Passed back by the
79
-         * {@code Flag} component in the onDismissed callback.
80
-         */
81
-        uid: PropTypes.number
51
+        toggleLabelKey: PropTypes.string
82 52
     };
83 53
 
84 54
     /**
@@ -111,32 +81,11 @@ class NotificationWithToggle extends Component {
111 81
      * @returns {ReactElement}
112 82
      */
113 83
     render() {
114
-        const {
115
-            isDismissAllowed,
116
-            t,
117
-            titleKey,
118
-            uid
119
-        } = this.props;
120
-
121 84
         return (
122
-            <Flag
123
-                actions = { [
124
-                    {
125
-                        content: t('dialog.Ok'),
126
-                        onClick: this._onDismissed
127
-                    }
128
-                ] }
129
-                appearance = 'warning'
130
-                description = { this._renderDescription() }
131
-                icon = { (
132
-                    <WarningIcon
133
-                        label = 'Warning'
134
-                        size = 'medium' />
135
-                ) }
136
-                id = { uid }
137
-                isDismissAllowed = { isDismissAllowed }
138
-                onDismissed = { this._onDismissed }
139
-                title = { t(titleKey) } />
85
+            <Notification
86
+                appearance = { NOTIFICATION_TYPE.WARNING }
87
+                { ...this.props }
88
+                description = { this._renderDescription() } />
140 89
         );
141 90
     }
142 91
 
@@ -181,7 +130,7 @@ class NotificationWithToggle extends Component {
181 130
     _renderDescription() {
182 131
         const {
183 132
             additionalMessage,
184
-            messageKey,
133
+            descriptionKey,
185 134
             showToggle,
186 135
             subtitleKey,
187 136
             t,
@@ -191,7 +140,7 @@ class NotificationWithToggle extends Component {
191 140
         return (
192 141
             <div className = 'notification-with-toggle'>
193 142
                 <div>{ t(subtitleKey) }</div>
194
-                { messageKey ? <div>{ t(messageKey) }</div> : null }
143
+                { descriptionKey ? <div>{ t(descriptionKey) }</div> : null }
195 144
                 { additionalMessage ? <div>{ additionalMessage }</div>
196 145
                     : null }
197 146
                 { showToggle

+ 12
- 0
react/features/notifications/constants.js Dosyayı Görüntüle

@@ -0,0 +1,12 @@
1
+/**
2
+ * The set of possible notification types.
3
+ *
4
+ * @enum {string}
5
+ */
6
+export const NOTIFICATION_TYPE = {
7
+    ERROR: 'error',
8
+    INFO: 'info',
9
+    NORMAL: 'normal',
10
+    SUCCESS: 'success',
11
+    WARNING: 'warning'
12
+};

+ 10
- 7
react/features/room-lock/middleware.js Dosyayı Görüntüle

@@ -75,19 +75,22 @@ function _setPasswordFailed(store, next, action) {
75 75
         // TODO Remove this logic when displaying of error messages on web is
76 76
         // handled through react/redux.
77 77
         const { error } = action;
78
-        let title;
79
-        let message;
78
+        let descriptionKey;
79
+        let titleKey;
80 80
 
81 81
         if (error === JitsiConferenceErrors.PASSWORD_NOT_SUPPORTED) {
82 82
             logger.warn('room passwords not supported');
83
-            title = 'dialog.warning';
84
-            message = 'dialog.passwordNotSupported';
83
+            descriptionKey = 'dialog.passwordNotSupported';
84
+            titleKey = 'dialog.passwordNotSupportedTitle';
85 85
         } else {
86 86
             logger.warn('setting password failed', error);
87
-            title = 'dialog.lockTitle';
88
-            message = 'dialog.lockMessage';
87
+            descriptionKey = 'dialog.lockMessage';
88
+            titleKey = 'dialog.lockTitle';
89 89
         }
90
-        APP.UI.messageHandler.showError(title, message);
90
+        APP.UI.messageHandler.showError({
91
+            descriptionKey,
92
+            titleKey
93
+        });
91 94
     }
92 95
 
93 96
     return next(action);

Loading…
İptal
Kaydet