Browse Source

feat(notifications): implement a react/redux notification system (#1786)

* feat(notifications): implement a react/redux notification system

* squash into impl explicit timeout, style

* ref(notifications): convert toastr notifications to use react

* ref(toastr): remove library

* squash into conversion: pass timeout

* squash into clean remove from debian patch
j8
virtuacoplenny 7 years ago
parent
commit
da1c760abf

+ 0
- 2
app.js View File

@@ -13,8 +13,6 @@ import 'aui-experimental';
13 13
 import 'aui-css';
14 14
 import 'aui-experimental-css';
15 15
 
16
-window.toastr = require('toastr');
17
-
18 16
 import conference from './conference';
19 17
 import API from './modules/API';
20 18
 import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';

+ 0
- 107
css/_toastr.scss View File

@@ -1,107 +0,0 @@
1
-/*
2
- * Toastr
3
- * Copyright 2012-2014 John Papa and Hans Fjällemark.
4
- * All Rights Reserved.
5
- * Use, reproduction, distribution, and modification of this code is subject to the terms and
6
- * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
7
- *
8
- * Author: John Papa and Hans Fjällemark
9
- * Project: https://github.com/CodeSeven/toastr
10
- *
11
- * Last updated: October 13, 2016
12
- */
13
-
14
-.toast-title,
15
-.toast-message .title {
16
-  font-weight: bold;
17
-  margin: 0 0 3px;
18
-  @include text-truncate;
19
-}
20
-
21
-.toast-message {
22
-  -ms-word-wrap: break-word;
23
-  word-wrap: break-word;
24
-}
25
-
26
-.toast-message a,
27
-.toast-message label,
28
-.toast-message .connected,
29
-.toast-message .disconnected {
30
-  color: $notificationLinkColor;
31
-  text-decoration: none;
32
-}
33
-
34
-.toast-message a:hover {
35
-  text-decoration: underline;
36
-}
37
-
38
-.toast-message br {
39
-  display: none;
40
-}
41
-
42
-// close button
43
-.toast-close-button {
44
-  color: $notificationColor;
45
-  background: transparent;
46
-
47
-  font-size: 15px;
48
-  line-height: 1.2;
49
-
50
-  height: 20px;
51
-  width: 20px;
52
-  padding: 0;
53
-  border: 0;
54
-  margin: -6px -10px 0 0;
55
-  float: right;
56
-
57
-  cursor: pointer;
58
-  @include opacity(0.4);
59
-  /* Additional properties for button version
60
-  iOS requires the button element instead of an anchor tag.
61
-  If you want the anchor version, it requires `href="#"`. */
62
-  -webkit-appearance: none;
63
-}
64
-
65
-.toast-close-button:hover,
66
-.toast-close-button:focus {
67
-  @include opacity(1);
68
-}
69
-
70
-
71
-.toast {
72
-  color: $notificationColor;
73
-  background-color: $notificationBackground;
74
-
75
-  font-size: $notificationFontSize;
76
-
77
-  padding: $notificationPadding;
78
-  border: 1px solid lighten($notificationBackground, 10%);
79
-
80
-  @include border-radius($notificationBorderRadius);
81
-  @include box-shadow(1px, 1px, 2px, rgba(0,0,0,0.3));
82
-  @include opacity($notificationOpacity);
83
-}
84
-
85
-.toast:hover {
86
-  @include opacity(1);
87
-}
88
-
89
-#toast-container {
90
-  position: fixed;
91
-  z-index: $notificationZ;
92
-}
93
-
94
-#toast-container.notification-bottom-right {
95
-  $videoOffset: 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
96
-  bottom: 135px;
97
-  right: $filmstripToggleButtonWidth + $videoOffset;
98
-}
99
-
100
-#toast-container * {
101
-  @include box-sizing(border-box);
102
-}
103
-
104
-#toast-container .toast {
105
-  width: $notificationWidth;
106
-  margin: 0 0 8px;
107
-}

+ 0
- 15
css/_variables.scss View File

@@ -85,20 +85,6 @@ $modalMockAKInputBackground: #fafbfc;
85 85
 $modalMockAKInputBorder: 1px solid #f4f5f7;
86 86
 $modalTextColor: #333;
87 87
 
88
-/**
89
- * Notifications
90
- */
91
-$notificationFontSize: 13px;
92
-$notificationColor: #FFFFFF;
93
-$notificationBackground: $tooltipBg;
94
-$notificationTitleColor: $notificationColor;
95
-$notificationMessageColor: $notificationColor;
96
-$notificationLinkColor: $notificationColor;
97
-$notificationOpacity: 0.9;
98
-$notificationPadding: 15px 20px;
99
-$notificationBorderRadius: 4px;
100
-$notificationWidth: 215px;
101
-
102 88
 /**
103 89
  * Misc.
104 90
  */
@@ -126,7 +112,6 @@ $tooltipsZ: 401;
126 112
 $dropdownMaskZ: 900;
127 113
 $dropdownZ: 901;
128 114
 $centeredVideoLabelZ: 1010;
129
-$notificationZ: 1011;
130 115
 $jitsipopoverZ: 1012;
131 116
 $popoverZ: 1015;
132 117
 $overlayZ: 1016;

+ 0
- 11
css/_vertical_filmstrip_overrides.scss View File

@@ -159,15 +159,4 @@
159 159
             transition-delay: 0.1s;
160 160
         }
161 161
     }
162
-
163
-    /**
164
-     * Move toastr closer to the bottom of the screen and move left to avoid
165
-     * overlapping of videos when they are configured at default height.
166
-     */
167
-    #toast-container {
168
-        &.notification-bottom-right {
169
-            bottom: 25px;
170
-            right: 130 + 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
171
-        }
172
-    }
173 162
 }

+ 0
- 1
css/main.scss View File

@@ -33,7 +33,6 @@
33 33
 /* Modules BEGIN */
34 34
 
35 35
 @import 'dial-out';
36
-@import 'toastr';
37 36
 @import 'base';
38 37
 @import 'utils';
39 38
 @import 'overlay/overlay';

+ 2
- 2
debian/patches/jquery-package View File

@@ -3,7 +3,7 @@ Index: jitsi-meet/index.html
3 3
 ===================================================================
4 4
 --- jitsi-meet.orig/index.html
5 5
 +++ jitsi-meet/index.html
6
-@@ -10,14 +10,14 @@
6
+@@ -10,13 +10,13 @@
7 7
      <meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
8 8
      <meta itemprop="image" content="/images/jitsilogo.png"/>
9 9
      <script src="https://api.callstats.io/static/callstats.min.js"></script>
@@ -19,4 +19,4 @@ Index: jitsi-meet/index.html
19 19
 +    <script src="libs/jquery-ui.min.js"></script>
20 20
      <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
21 21
      <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
22
-     <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
22
+-    <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->

+ 2
- 22
modules/UI/UI.js View File

@@ -1,4 +1,4 @@
1
-/* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
1
+/* global APP, JitsiMeetJS, $, config, interfaceConfig */
2 2
 
3 3
 const logger = require("jitsi-meet-logger").getLogger(__filename);
4 4
 
@@ -343,26 +343,6 @@ UI.start = function () {
343 343
     }
344 344
 
345 345
     document.title = interfaceConfig.APP_NAME;
346
-
347
-    if (!interfaceConfig.filmStripOnly) {
348
-        toastr.options = {
349
-            "closeButton": true,
350
-            "debug": false,
351
-            "positionClass": "notification-bottom-right",
352
-            "onclick": null,
353
-            "showDuration": "300",
354
-            "hideDuration": "1000",
355
-            "timeOut": "2000",
356
-            "extendedTimeOut": "1000",
357
-            "showEasing": "swing",
358
-            "hideEasing": "linear",
359
-            "showMethod": "fadeIn",
360
-            "hideMethod": "fadeOut",
361
-            "newestOnTop": false,
362
-            // this is the default toastr close button html, just adds tabIndex
363
-            "closeHtml": '<button type="button" tabIndex="-1">&times;</button>'
364
-        };
365
-    }
366 346
 };
367 347
 
368 348
 /**
@@ -868,7 +848,7 @@ UI.notifyInitiallyMuted = function () {
868 848
         "connected",
869 849
         "notify.muted",
870 850
         null,
871
-        { timeOut: 120000 });
851
+        120000);
872 852
 };
873 853
 
874 854
 /**

+ 24
- 42
modules/UI/util/MessageHandler.js View File

@@ -1,9 +1,13 @@
1
-/* global $, APP, toastr */
1
+/* global $, APP */
2 2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3 3
 
4
-import UIUtil from './UIUtil';
5 4
 import jitsiLocalStorage from '../../util/JitsiLocalStorage';
6 5
 
6
+import {
7
+    Notification,
8
+    showNotification
9
+} from '../../../react/features/notifications';
10
+
7 11
 /**
8 12
  * Flag for enable/disable of the notifications.
9 13
  * @type {boolean}
@@ -448,31 +452,25 @@ var messageHandler = {
448 452
      * @param messageKey the key from the language file for the text of the
449 453
      * message.
450 454
      * @param messageArguments object with the arguments for the message.
451
-     * @param options passed to toastr (e.g. timeOut)
455
+     * @param optional configurations for the notification (e.g. timeout)
452 456
      */
453 457
     participantNotification: function(displayName, displayNameKey, cls,
454
-                    messageKey, messageArguments, options) {
455
-
456
-        // If we're in ringing state we skip all toaster notifications.
457
-        if(!notificationsEnabled || APP.UI.isOverlayVisible())
458
+                    messageKey, messageArguments, timeout) {
459
+        // If we're in ringing state we skip all notifications.
460
+        if (!notificationsEnabled || APP.UI.isOverlayVisible()) {
458 461
             return;
459
-
460
-        var displayNameSpan = '<span class="title" ';
461
-        if (displayName) {
462
-            displayNameSpan += ">" + UIUtil.escapeHtml(displayName);
463
-        } else {
464
-            displayNameSpan += "data-i18n='" + displayNameKey + "'>";
465 462
         }
466
-        displayNameSpan += "</span>";
467
-        let element = toastr.info(
468
-            displayNameSpan + '<br>' +
469
-            '<span class=' + cls + ' data-i18n="' + messageKey + '"' +
470
-                (messageArguments?
471
-                    " data-i18n-options='"
472
-                        + JSON.stringify(messageArguments) + "'"
473
-                    : "") + "></span>", null, options);
474
-        APP.translation.translateElement(element);
475
-        return element;
463
+
464
+        APP.store.dispatch(
465
+            showNotification(
466
+                Notification,
467
+                {
468
+                    defaultTitleKey: displayNameKey,
469
+                    descriptionArguments: messageArguments,
470
+                    descriptionKey: messageKey,
471
+                    title: displayName
472
+                },
473
+                timeout));
476 474
     },
477 475
 
478 476
     /**
@@ -488,28 +486,12 @@ var messageHandler = {
488 486
      */
489 487
     notify: function(titleKey, messageKey, messageArguments) {
490 488
 
491
-        // If we're in ringing state we skip all toaster notifications.
489
+        // If we're in ringing state we skip all notifications.
492 490
         if(!notificationsEnabled || APP.UI.isOverlayVisible())
493 491
             return;
494 492
 
495
-        const options = messageArguments
496
-            ? `data-i18n-options='${JSON.stringify(messageArguments)}'` : "";
497
-        let element = toastr.info(`
498
-            <span class="title" data-i18n="${titleKey}"></span>
499
-            <br>
500
-            <span data-i18n="${messageKey}" ${options}></span>
501
-            `
502
-        );
503
-        APP.translation.translateElement(element);
504
-        return element;
505
-    },
506
-
507
-    /**
508
-     * Removes the toaster.
509
-     * @param toasterElement
510
-     */
511
-    remove: function(toasterElement) {
512
-        toasterElement.remove();
493
+        this.participantNotification(
494
+            null, titleKey, null, messageKey, messageArguments);
513 495
     },
514 496
 
515 497
     /**

+ 1
- 1
package.json View File

@@ -21,6 +21,7 @@
21 21
     "@atlaskit/button-group": "1.1.3",
22 22
     "@atlaskit/dropdown-menu": "1.4.0",
23 23
     "@atlaskit/field-text": "2.7.0",
24
+    "@atlaskit/flag": "3.0.0",
24 25
     "@atlaskit/icon": "7.0.0",
25 26
     "@atlaskit/inline-dialog": "3.2.0",
26 27
     "@atlaskit/inline-message": "2.1.1",
@@ -67,7 +68,6 @@
67 68
     "strophe": "1.2.4",
68 69
     "strophejs-plugins": "0.0.7",
69 70
     "styled-components": "1.3.0",
70
-    "toastr": "2.1.2",
71 71
     "url-polyfill": "github/url-polyfill",
72 72
     "xmldom": "0.1.27"
73 73
   },

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

@@ -7,6 +7,7 @@ import { connect, disconnect } from '../../base/connection';
7 7
 import { DialogContainer } from '../../base/dialog';
8 8
 import { Filmstrip } from '../../filmstrip';
9 9
 import { LargeVideo } from '../../large-video';
10
+import { NotificationsContainer } from '../../notifications';
10 11
 import { OverlayContainer } from '../../overlay';
11 12
 import { Toolbox } from '../../toolbox';
12 13
 import { HideNotificationBarStyle } from '../../unsupported-browser';
@@ -78,6 +79,7 @@ class Conference extends Component {
78 79
                 { filmStripOnly ? null : <Toolbox /> }
79 80
 
80 81
                 <DialogContainer />
82
+                { filmStripOnly ? null : <NotificationsContainer /> }
81 83
                 <OverlayContainer />
82 84
 
83 85
                 {/*

+ 24
- 0
react/features/notifications/actionTypes.js View File

@@ -0,0 +1,24 @@
1
+/*
2
+ * The type of (redux) action which signals that a specific notification should
3
+ * not be displayed anymore.
4
+ *
5
+ * {
6
+ *     type: HIDE_NOTIFICATION,
7
+ *     uid: string
8
+ * }
9
+ */
10
+export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');
11
+
12
+/*
13
+ * The type of (redux) action which signals that a notification component should
14
+ * be displayed.
15
+ *
16
+ * {
17
+ *     type: SHOW_NOTIFICATION,
18
+ *     component: ReactComponent,
19
+ *     props: Object,
20
+ *     timeout: number,
21
+ *     uid: number
22
+ * }
23
+ */
24
+export const SHOW_NOTIFICATION = Symbol('SHOW_NOTIFICATION');

+ 47
- 0
react/features/notifications/actions.js View File

@@ -0,0 +1,47 @@
1
+import {
2
+    HIDE_NOTIFICATION,
3
+    SHOW_NOTIFICATION
4
+} from './actionTypes';
5
+
6
+/**
7
+ * Removes the notification with the passed in id.
8
+ *
9
+ * @param {string} uid - The unique identifier for the notification to be
10
+ * removed.
11
+ * @returns {{
12
+ *     type: HIDE_NOTIFICATION,
13
+ *     uid: string
14
+ * }}
15
+ */
16
+export function hideNotification(uid) {
17
+    return {
18
+        type: HIDE_NOTIFICATION,
19
+        uid
20
+    };
21
+}
22
+
23
+/**
24
+ * Queues a notification for display.
25
+ *
26
+ * @param {ReactComponent} component - The notification component to be
27
+ * displayed.
28
+ * @param {Object} props - The props needed to show the notification component.
29
+ * @param {number} timeout - How long the notification should display before
30
+ * automatically being hidden.
31
+ * @returns {{
32
+ *     type: SHOW_NOTIFICATION,
33
+ *     component: ReactComponent,
34
+ *     props: Object,
35
+ *     timeout: number,
36
+ *     uid: number
37
+ * }}
38
+ */
39
+export function showNotification(component, props = {}, timeout) {
40
+    return {
41
+        type: SHOW_NOTIFICATION,
42
+        component,
43
+        props,
44
+        timeout,
45
+        uid: window.Date.now()
46
+    };
47
+}

+ 0
- 0
react/features/notifications/components/Notification.native.js View File


+ 100
- 0
react/features/notifications/components/Notification.web.js View File

@@ -0,0 +1,100 @@
1
+import Flag from '@atlaskit/flag';
2
+import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
3
+import React, { Component } from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+
7
+/**
8
+ * Implements a React {@link Component} to display a notification.
9
+ *
10
+ * @extends Component
11
+ */
12
+class Notification extends Component {
13
+    /**
14
+     * {@code Notification} component's property types.
15
+     *
16
+     * @static
17
+     */
18
+    static propTypes = {
19
+        /**
20
+         * The translation key to display as the title of the notification if
21
+         * no title is provided.
22
+         */
23
+        defaultTitleKey: React.PropTypes.string,
24
+
25
+        /**
26
+         * The translation arguments that may be necessary for the description.
27
+         */
28
+        descriptionArguments: React.PropTypes.object,
29
+
30
+        /**
31
+         * The translation key to use as the body of the notification.
32
+         */
33
+        descriptionKey: React.PropTypes.string,
34
+
35
+        /**
36
+         * Whether or not the dismiss button should be displayed. This is passed
37
+         * in by {@code FlagGroup}.
38
+         */
39
+        isDismissAllowed: React.PropTypes.bool,
40
+
41
+        /**
42
+         * Callback invoked when the user clicks to dismiss the notification.
43
+         * this is passed in by {@code FlagGroup}.
44
+         */
45
+        onDismissed: React.PropTypes.func,
46
+
47
+        /**
48
+         * Invoked to obtain translated strings.
49
+         */
50
+        t: React.PropTypes.func,
51
+
52
+        /**
53
+         * The text to display at the top of the notification. If not passed in,
54
+         * the passed in defaultTitleKey will be used.
55
+         */
56
+        title: React.PropTypes.string,
57
+
58
+        /**
59
+         * The unique identifier for the notification. Passed back by the
60
+         * {@code Flag} component in the onDismissed callback.
61
+         */
62
+        uid: React.PropTypes.number
63
+    };
64
+
65
+    /**
66
+     * Implements React's {@link Component#render()}.
67
+     *
68
+     * @inheritdoc
69
+     * @returns {ReactElement}
70
+     */
71
+    render() {
72
+        const {
73
+            defaultTitleKey,
74
+            descriptionArguments,
75
+            descriptionKey,
76
+            isDismissAllowed,
77
+            onDismissed,
78
+            t,
79
+            title,
80
+            uid
81
+        } = this.props;
82
+
83
+        return (
84
+            <Flag
85
+                appearance = 'normal'
86
+                description = { t(descriptionKey, descriptionArguments) }
87
+                icon = { (
88
+                    <EditorInfoIcon
89
+                        label = 'info'
90
+                        size = 'medium' />
91
+                ) }
92
+                id = { uid }
93
+                isDismissAllowed = { isDismissAllowed }
94
+                onDismissed = { onDismissed }
95
+                title = { title || t(defaultTitleKey) } />
96
+        );
97
+    }
98
+}
99
+
100
+export default translate(Notification);

+ 0
- 0
react/features/notifications/components/NotificationsContainer.native.js View File


+ 157
- 0
react/features/notifications/components/NotificationsContainer.web.js View File

@@ -0,0 +1,157 @@
1
+import { FlagGroup } from '@atlaskit/flag';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { hideNotification } from '../actions';
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
+/**
16
+ * Implements a React {@link Component} which displays notifications and handles
17
+ * automatic dismissmal after a notification is shown for a defined timeout
18
+ * period.
19
+ *
20
+ * @extends {Component}
21
+ */
22
+class NotificationsContainer extends Component {
23
+    /**
24
+     * {@code NotificationsContainer} component's property types.
25
+     *
26
+     * @static
27
+     */
28
+    static propTypes = {
29
+        /**
30
+         * The notifications to be displayed, with the first index being the
31
+         * notification at the top and the rest shown below it in order.
32
+         */
33
+        _notifications: React.PropTypes.array,
34
+
35
+        /**
36
+         * Invoked to update the redux store in order to remove notifications.
37
+         */
38
+        dispatch: React.PropTypes.func
39
+    };
40
+
41
+    /**
42
+     * Initializes a new {@code NotificationsContainer} instance.
43
+     *
44
+     * @param {Object} props - The read-only React Component props with which
45
+     * the new instance is to be initialized.
46
+     */
47
+    constructor(props) {
48
+        super(props);
49
+
50
+        /**
51
+         * The timeout set for automatically dismissing a displayed
52
+         * notification. This value is set on the instance and not state to
53
+         * avoid additional re-renders.
54
+         *
55
+         * @type {number|null}
56
+         */
57
+        this._notificationDismissTimeout = null;
58
+
59
+        // Bind event handlers so they are only bound once for every instance.
60
+        this._onDismissed = this._onDismissed.bind(this);
61
+    }
62
+
63
+    /**
64
+     * Sets a timeout if the currently displayed notification has changed.
65
+     *
66
+     * @inheritdoc
67
+     * returns {void}
68
+     */
69
+    componentDidUpdate() {
70
+        const { _notifications } = this.props;
71
+
72
+        if (_notifications.length && !this._notificationDismissTimeout) {
73
+            const notification = _notifications[0];
74
+            const { timeout, uid } = notification;
75
+
76
+            this._notificationDismissTimeout = setTimeout(() => {
77
+                this._onDismissed(uid);
78
+            }, timeout || DEFAULT_NOTIFICATION_TIMEOUT);
79
+        }
80
+    }
81
+
82
+    /**
83
+     * Clear any dismissal timeout that is still active.
84
+     *
85
+     * @inheritdoc
86
+     * returns {void}
87
+     */
88
+    componentWillUnmount() {
89
+        clearTimeout(this._notificationDismissTimeout);
90
+    }
91
+
92
+    /**
93
+     * Implements React's {@link Component#render()}.
94
+     *
95
+     * @inheritdoc
96
+     * @returns {ReactElement}
97
+     */
98
+    render() {
99
+        const { _notifications } = this.props;
100
+
101
+        const flags = _notifications.map(notification => {
102
+            const Notification = notification.component;
103
+            const { props, uid } = notification;
104
+
105
+            // The id attribute is necessary as {@code FlagGroup} looks for
106
+            // either id or key to set a key on notifications, but accessing
107
+            // props.key will cause React to print an error.
108
+            return (
109
+                <Notification
110
+                    { ...props }
111
+                    id = { uid }
112
+                    key = { uid }
113
+                    uid = { uid } />
114
+
115
+            );
116
+        });
117
+
118
+        return (
119
+            <FlagGroup onDismissed = { this._onDismissed }>
120
+                { flags }
121
+            </FlagGroup>
122
+        );
123
+    }
124
+
125
+    /**
126
+     * Emits an action to remove the notification from the redux store so it
127
+     * stops displaying.
128
+     *
129
+     * @param {number} flagUid - The id of the notification to be removed.
130
+     * @private
131
+     * @returns {void}
132
+     */
133
+    _onDismissed(flagUid) {
134
+        clearTimeout(this._notificationDismissTimeout);
135
+        this._notificationDismissTimeout = null;
136
+
137
+        this.props.dispatch(hideNotification(flagUid));
138
+    }
139
+}
140
+
141
+/**
142
+ * Maps (parts of) the Redux state to the associated NotificationsContainer's
143
+ * props.
144
+ *
145
+ * @param {Object} state - The Redux state.
146
+ * @private
147
+ * @returns {{
148
+ *     _notifications: React.PropTypes.array
149
+ * }}
150
+ */
151
+function _mapStateToProps(state) {
152
+    return {
153
+        _notifications: state['features/notifications']
154
+    };
155
+}
156
+
157
+export default connect(_mapStateToProps)(NotificationsContainer);

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

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

+ 5
- 0
react/features/notifications/index.js View File

@@ -0,0 +1,5 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './components';
4
+
5
+import './reducer';

+ 43
- 0
react/features/notifications/reducer.js View File

@@ -0,0 +1,43 @@
1
+import { ReducerRegistry } from '../base/redux';
2
+
3
+import {
4
+    HIDE_NOTIFICATION,
5
+    SHOW_NOTIFICATION
6
+} from './actionTypes';
7
+
8
+/**
9
+ * The initial state of the feature notifications.
10
+ *
11
+ * @type {array}
12
+ */
13
+const DEFAULT_STATE = [];
14
+
15
+/**
16
+ * Reduces redux actions which affect the display of notifications.
17
+ *
18
+ * @param {Object} state - The current redux state.
19
+ * @param {Object} action - The redux action to reduce.
20
+ * @returns {Object} The next redux state which is the result of reducing the
21
+ * specified {@code action}.
22
+ */
23
+ReducerRegistry.register('features/notifications',
24
+    (state = DEFAULT_STATE, action) => {
25
+        switch (action.type) {
26
+        case HIDE_NOTIFICATION:
27
+            return state.filter(
28
+                notification => notification.uid !== action.uid);
29
+
30
+        case SHOW_NOTIFICATION:
31
+            return [
32
+                ...state,
33
+                {
34
+                    component: action.component,
35
+                    props: action.props,
36
+                    timeout: action.timeout,
37
+                    uid: action.uid
38
+                }
39
+            ];
40
+        }
41
+
42
+        return state;
43
+    });

Loading…
Cancel
Save