Selaa lähdekoodia

feat(notifications) Changed notifications stack to be full height

This is a stop-gap approach to remove the AtlasKit notifications stack.

Instead of using a AK FlagGroup to render our notifications (Flag components)
in, create our own container and use a fake FlagGroupContext provider, which is
what FlagGroup uses to control what flags can be dismissed.

Since we now render all notifications, the web part has been refactored to make
sure all notifications get a timer.

Added animations

Renamed DrawerPortal to JitsiPortal

Redesigned notifications
Changed notification text and icons color and added collared ribbon
master
Saúl Ibarra Corretgé 3 vuotta sitten
vanhempi
commit
0b984ce5f9

+ 4
- 0
css/_drawer.scss Näytä tiedosto

5
     bottom: 0;
5
     bottom: 0;
6
     z-index: $drawerZ;
6
     z-index: $drawerZ;
7
     border-radius: 16px 16px 0 0;
7
     border-radius: 16px 16px 0 0;
8
+
9
+    &.notification-portal {
10
+        z-index: $dropdownZ;
11
+    }
8
 }
12
 }
9
 
13
 
10
 .drawer-portal::after {
14
 .drawer-portal::after {

+ 23
- 0
css/_notifications.scss Näytä tiedosto

1
+.notification-appear, .notification-enter {
2
+    opacity: 0;
3
+    position: relative;
4
+    left: -200px;
5
+    transition: all .2s !important; // !important needed to overwrite atlaskit default style
6
+
7
+    &-active {
8
+        opacity: 1;
9
+        left: 0;
10
+    }
11
+}
12
+
13
+.notification-exit {
14
+    opacity: 1;
15
+    position: relative;
16
+    left: 0;
17
+    transition: all .2s !important; // !important needed to overwrite atlaskit default style
18
+
19
+    &-active {
20
+        opacity: 0;
21
+        left: -200px;
22
+    }
23
+}

+ 1
- 0
css/main.scss Näytä tiedosto

106
 @import 'reactions-menu';
106
 @import 'reactions-menu';
107
 @import 'plan-limit';
107
 @import 'plan-limit';
108
 @import 'polls';
108
 @import 'polls';
109
+@import 'notifications';
109
 
110
 
110
 /* Modules END */
111
 /* Modules END */

+ 3
- 3
react/features/base/popover/components/Popover.web.js Näytä tiedosto

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 
4
 
5
-import { Drawer, DrawerPortal, DialogPortal } from '../../../toolbox/components/web';
5
+import { Drawer, JitsiPortal, DialogPortal } from '../../../toolbox/components/web';
6
 import { isMobileBrowser } from '../../environment/utils';
6
 import { isMobileBrowser } from '../../environment/utils';
7
 import { getContextMenuStyle } from '../functions.web';
7
 import { getContextMenuStyle } from '../functions.web';
8
 
8
 
173
                     id = { id }
173
                     id = { id }
174
                     onClick = { this._onShowDialog }>
174
                     onClick = { this._onShowDialog }>
175
                     { children }
175
                     { children }
176
-                    <DrawerPortal>
176
+                    <JitsiPortal>
177
                         <Drawer
177
                         <Drawer
178
                             isOpen = { this.state.showDialog }
178
                             isOpen = { this.state.showDialog }
179
                             onClose = { this._onHideDialog }>
179
                             onClose = { this._onHideDialog }>
180
                             { content }
180
                             { content }
181
                         </Drawer>
181
                         </Drawer>
182
-                    </DrawerPortal>
182
+                    </JitsiPortal>
183
                 </div>
183
                 </div>
184
             );
184
             );
185
         }
185
         }

+ 16
- 2
react/features/conference/components/web/Conference.js Näytä tiedosto

19
 import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
19
 import { getParticipantsPaneOpen } from '../../../participants-pane/functions';
20
 import { Prejoin, isPrejoinPageVisible, isPrejoinPageLoading } from '../../../prejoin';
20
 import { Prejoin, isPrejoinPageVisible, isPrejoinPageLoading } from '../../../prejoin';
21
 import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
21
 import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
22
-import { Toolbox } from '../../../toolbox/components/web';
22
+import { JitsiPortal, Toolbox } from '../../../toolbox/components/web';
23
 import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
23
 import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
24
 import { maybeShowSuboptimalExperienceNotification } from '../../functions';
24
 import { maybeShowSuboptimalExperienceNotification } from '../../functions';
25
 import {
25
 import {
86
      */
86
      */
87
     _mouseMoveCallbackInterval: number,
87
     _mouseMoveCallbackInterval: number,
88
 
88
 
89
+    /**
90
+     *Whether or not the notifications should be displayed in the overflow drawer.
91
+     */
92
+    _overflowDrawer: boolean,
93
+
89
     /**
94
     /**
90
      * Name for this conference room.
95
      * Name for this conference room.
91
      */
96
      */
209
         const {
214
         const {
210
             _isParticipantsPaneVisible,
215
             _isParticipantsPaneVisible,
211
             _layoutClassName,
216
             _layoutClassName,
217
+            _notificationsVisible,
218
+            _overflowDrawer,
212
             _showLobby,
219
             _showLobby,
213
             _showPrejoin
220
             _showPrejoin
214
         } = this.props;
221
         } = this.props;
239
                     { _showPrejoin || _showLobby || <Toolbox showDominantSpeakerName = { true } /> }
246
                     { _showPrejoin || _showLobby || <Toolbox showDominantSpeakerName = { true } /> }
240
                     <Chat />
247
                     <Chat />
241
 
248
 
242
-                    { this.renderNotificationsContainer() }
249
+                    {_notificationsVisible && (_overflowDrawer
250
+                        ? <JitsiPortal className = 'notification-portal'>
251
+                            {this.renderNotificationsContainer({ portal: true })}
252
+                        </JitsiPortal>
253
+                        : this.renderNotificationsContainer())
254
+                    }
243
 
255
 
244
                     <CalleeInfoContainer />
256
                     <CalleeInfoContainer />
245
 
257
 
368
  */
380
  */
369
 function _mapStateToProps(state) {
381
 function _mapStateToProps(state) {
370
     const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config'];
382
     const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config'];
383
+    const { overflowDrawer } = state['features/toolbox'];
371
 
384
 
372
     return {
385
     return {
373
         ...abstractMapStateToProps(state),
386
         ...abstractMapStateToProps(state),
375
         _isParticipantsPaneVisible: getParticipantsPaneOpen(state),
388
         _isParticipantsPaneVisible: getParticipantsPaneOpen(state),
376
         _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
389
         _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
377
         _mouseMoveCallbackInterval: mouseMoveCallbackInterval,
390
         _mouseMoveCallbackInterval: mouseMoveCallbackInterval,
391
+        _overflowDrawer: overflowDrawer,
378
         _roomName: getConferenceNameForTitle(state),
392
         _roomName: getConferenceNameForTitle(state),
379
         _showLobby: getIsLobbyVisible(state),
393
         _showLobby: getIsLobbyVisible(state),
380
         _showPrejoin: isPrejoinPageVisible(state) || isPrejoinPageLoading(state)
394
         _showPrejoin: isPrejoinPageVisible(state) || isPrejoinPageLoading(state)

+ 0
- 188
react/features/notifications/components/AbstractNotificationsContainer.js Näytä tiedosto

1
-// @flow
2
-
3
-import { Component } from 'react';
4
-
5
-import { hideNotification } from '../actions';
6
-import { areThereNotifications } from '../functions';
7
-
8
-export type Props = {
9
-
10
-    /**
11
-     * The notifications to be displayed, with the first index being the
12
-     * notification at the top and the rest shown below it in order.
13
-     */
14
-    _notifications: Array<Object>,
15
-
16
-    /**
17
-     * The length, in milliseconds, to use as a default timeout for all
18
-     * dismissable timeouts that do not have a timeout specified.
19
-     */
20
-    autoDismissTimeout: number,
21
-
22
-    /**
23
-     * Invoked to update the redux store in order to remove notifications.
24
-     */
25
-    dispatch: Function
26
-};
27
-
28
-declare var interfaceConfig: Object;
29
-
30
-/**
31
- * Abstract class for {@code NotificationsContainer} component.
32
- */
33
-export default class AbstractNotificationsContainer<P: Props>
34
-    extends Component<P> {
35
-    /**
36
-     * A timeout id returned by setTimeout.
37
-     */
38
-    _notificationDismissTimeout: ?TimeoutID;
39
-
40
-    /**
41
-     * Initializes a new {@code AbstractNotificationsContainer} instance.
42
-     *
43
-     * @inheritdoc
44
-     */
45
-    constructor(props: P) {
46
-        super(props);
47
-
48
-        /**
49
-         * The timeout set for automatically dismissing a displayed
50
-         * notification. This value is set on the instance and not state to
51
-         * avoid additional re-renders.
52
-         *
53
-         * @type {number|null}
54
-         */
55
-        this._notificationDismissTimeout = null;
56
-
57
-        // Bind event handlers so they are only bound once for every instance.
58
-        this._onDismissed = this._onDismissed.bind(this);
59
-    }
60
-
61
-    /**
62
-     * Sets a timeout for the first notification (if applicable).
63
-     *
64
-     * @inheritdoc
65
-     */
66
-    componentDidMount() {
67
-        // Set the initial dismiss timeout (if any)
68
-        this._manageDismissTimeout();
69
-    }
70
-
71
-    /**
72
-     * Sets a timeout if the currently displayed notification has changed.
73
-     *
74
-     * @inheritdoc
75
-     */
76
-    componentDidUpdate(prevProps: P) {
77
-        this._manageDismissTimeout(prevProps);
78
-    }
79
-
80
-    /**
81
-     * Sets/clears the dismiss timeout for the top notification.
82
-     *
83
-     * @param {P} [prevProps] - The previous properties (if called from
84
-     * {@code componentDidUpdate}).
85
-     * @returns {void}
86
-     * @private
87
-     */
88
-    _manageDismissTimeout(prevProps: ?P) {
89
-        const { _notifications, autoDismissTimeout } = this.props;
90
-
91
-        if (_notifications.length) {
92
-            const notification = _notifications[0];
93
-            const previousNotification
94
-                = prevProps && prevProps._notifications.length
95
-                    ? prevProps._notifications[0]
96
-                    : undefined;
97
-
98
-            if (notification !== previousNotification) {
99
-                this._clearNotificationDismissTimeout();
100
-
101
-                if (notification
102
-                        && (notification.timeout
103
-                            || typeof autoDismissTimeout === 'number')
104
-                        && notification.props.isDismissAllowed !== false) {
105
-                    const {
106
-                        timeout = autoDismissTimeout,
107
-                        uid
108
-                    } = notification;
109
-
110
-                    this._notificationDismissTimeout = setTimeout(() => {
111
-                        // Perform a no-op if a timeout is not specified.
112
-                        this._onDismissed(uid);
113
-                    }, timeout);
114
-                }
115
-            }
116
-        } else if (this._notificationDismissTimeout) {
117
-            // Clear timeout when all notifications are cleared (e.g external
118
-            // call to clear them)
119
-            this._clearNotificationDismissTimeout();
120
-        }
121
-    }
122
-
123
-    /**
124
-     * Clear any dismissal timeout that is still active.
125
-     *
126
-     * @inheritdoc
127
-     * returns {void}
128
-     */
129
-    componentWillUnmount() {
130
-        this._clearNotificationDismissTimeout();
131
-    }
132
-
133
-    _onDismissed: number => void;
134
-
135
-    /**
136
-     * Clears the running notification dismiss timeout, if any.
137
-     *
138
-     * @returns {void}
139
-     */
140
-    _clearNotificationDismissTimeout() {
141
-        this._notificationDismissTimeout
142
-            && clearTimeout(this._notificationDismissTimeout);
143
-
144
-        this._notificationDismissTimeout = null;
145
-    }
146
-
147
-    /**
148
-     * Emits an action to remove the notification from the redux store so it
149
-     * stops displaying.
150
-     *
151
-     * @param {number} uid - The id of the notification to be removed.
152
-     * @private
153
-     * @returns {void}
154
-     */
155
-    _onDismissed(uid) {
156
-        const { _notifications } = this.props;
157
-
158
-        // Clear the timeout only if it's the top notification that's being
159
-        // dismissed (the timeout is set only for the top one).
160
-        if (!_notifications.length || _notifications[0].uid === uid) {
161
-            this._clearNotificationDismissTimeout();
162
-        }
163
-
164
-        this.props.dispatch(hideNotification(uid));
165
-    }
166
-}
167
-
168
-/**
169
- * Maps (parts of) the Redux state to the associated NotificationsContainer's
170
- * props.
171
- *
172
- * @param {Object} state - The Redux state.
173
- * @private
174
- * @returns {{
175
- *     _notifications: Array
176
- * }}
177
- */
178
-export function _abstractMapStateToProps(state: Object) {
179
-    const { notifications } = state['features/notifications'];
180
-    const _visible = areThereNotifications(state);
181
-
182
-    return {
183
-        _notifications: _visible ? notifications : [],
184
-        autoDismissTimeout: typeof interfaceConfig === 'undefined'
185
-            ? undefined // Ignore for the case of mobile
186
-            : interfaceConfig.ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT
187
-    };
188
-}

+ 162
- 11
react/features/notifications/components/native/NotificationsContainer.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import React from 'react';
3
+import React, { Component } from 'react';
4
 import { View } from 'react-native';
4
 import { View } from 'react-native';
5
 
5
 
6
 import { connect } from '../../../base/redux';
6
 import { connect } from '../../../base/redux';
7
-import AbstractNotificationsContainer, {
8
-    _abstractMapStateToProps,
9
-    type Props as AbstractProps
10
-} from '../AbstractNotificationsContainer';
7
+import { hideNotification } from '../../actions';
8
+import { areThereNotifications } from '../../functions';
11
 
9
 
12
 import Notification from './Notification';
10
 import Notification from './Notification';
13
 import styles from './styles';
11
 import styles from './styles';
14
 
12
 
15
-type Props = AbstractProps & {
13
+type Props = {
14
+
15
+    /**
16
+     * The notifications to be displayed, with the first index being the
17
+     * notification at the top and the rest shown below it in order.
18
+     */
19
+    _notifications: Array<Object>,
20
+
21
+    /**
22
+     * Invoked to update the redux store in order to remove notifications.
23
+     */
24
+     dispatch: Function,
16
 
25
 
17
     /**
26
     /**
18
      * Any custom styling applied to the notifications container.
27
      * Any custom styling applied to the notifications container.
27
  *
36
  *
28
  * @extends {Component}
37
  * @extends {Component}
29
  */
38
  */
30
-class NotificationsContainer
31
-    extends AbstractNotificationsContainer<Props> {
39
+class NotificationsContainer extends Component<Props> {
40
+
41
+    /**
42
+     * A timeout id returned by setTimeout.
43
+     */
44
+    _notificationDismissTimeout: ?TimeoutID;
45
+
46
+    /**
47
+     * Initializes a new {@code NotificationsContainer} instance.
48
+     *
49
+     * @inheritdoc
50
+     */
51
+    constructor(props: Props) {
52
+        super(props);
53
+
54
+        /**
55
+         * The timeout set for automatically dismissing a displayed
56
+         * notification. This value is set on the instance and not state to
57
+         * avoid additional re-renders.
58
+         *
59
+         * @type {number|null}
60
+         */
61
+        this._notificationDismissTimeout = null;
62
+
63
+        // Bind event handlers so they are only bound once for every instance.
64
+        this._onDismissed = this._onDismissed.bind(this);
65
+    }
66
+
67
+    /**
68
+     * Sets a timeout for the first notification (if applicable).
69
+     *
70
+     * @inheritdoc
71
+     */
72
+    componentDidMount() {
73
+        // Set the initial dismiss timeout (if any)
74
+        this._manageDismissTimeout();
75
+    }
76
+
77
+    /**
78
+     * Sets a timeout if the currently displayed notification has changed.
79
+     *
80
+     * @inheritdoc
81
+     */
82
+    componentDidUpdate(prevProps: Props) {
83
+        this._manageDismissTimeout(prevProps);
84
+    }
85
+
86
+    /**
87
+     * Sets/clears the dismiss timeout for the top notification.
88
+     *
89
+     * @param {P} [prevProps] - The previous properties (if called from
90
+     * {@code componentDidUpdate}).
91
+     * @returns {void}
92
+     * @private
93
+     */
94
+    _manageDismissTimeout(prevProps: ?Props) {
95
+        const { _notifications } = this.props;
96
+
97
+        if (_notifications.length) {
98
+            const notification = _notifications[0];
99
+            const previousNotification
100
+                 = prevProps && prevProps._notifications.length
101
+                     ? prevProps._notifications[0]
102
+                     : undefined;
103
+
104
+            if (notification !== previousNotification) {
105
+                this._clearNotificationDismissTimeout();
106
+
107
+                if (notification && notification.timeout && notification.props.isDismissAllowed !== false) {
108
+                    const {
109
+                        timeout,
110
+                        uid
111
+                    } = notification;
112
+
113
+                    this._notificationDismissTimeout = setTimeout(() => {
114
+                        // Perform a no-op if a timeout is not specified.
115
+                        this._onDismissed(uid);
116
+                    }, timeout);
117
+                }
118
+            }
119
+        } else if (this._notificationDismissTimeout) {
120
+            // Clear timeout when all notifications are cleared (e.g external
121
+            // call to clear them)
122
+            this._clearNotificationDismissTimeout();
123
+        }
124
+    }
125
+
126
+    /**
127
+     * Clear any dismissal timeout that is still active.
128
+     *
129
+     * @inheritdoc
130
+     */
131
+    componentWillUnmount() {
132
+        this._clearNotificationDismissTimeout();
133
+    }
134
+
135
+    /**
136
+     * Clears the running notification dismiss timeout, if any.
137
+     *
138
+     * @returns {void}
139
+     */
140
+    _clearNotificationDismissTimeout() {
141
+        this._notificationDismissTimeout && clearTimeout(this._notificationDismissTimeout);
142
+
143
+        this._notificationDismissTimeout = null;
144
+    }
145
+
146
+    _onDismissed: number => void;
147
+
148
+    /**
149
+     * Emits an action to remove the notification from the redux store so it
150
+     * stops displaying.
151
+     *
152
+     * @param {number} uid - The id of the notification to be removed.
153
+     * @private
154
+     * @returns {void}
155
+     */
156
+    _onDismissed(uid) {
157
+        const { _notifications } = this.props;
158
+
159
+        // Clear the timeout only if it's the top notification that's being
160
+        // dismissed (the timeout is set only for the top one).
161
+        if (!_notifications.length || _notifications[0].uid === uid) {
162
+            this._clearNotificationDismissTimeout();
163
+        }
164
+
165
+        this.props.dispatch(hideNotification(uid));
166
+    }
32
 
167
 
33
     /**
168
     /**
34
      * Implements React's {@link Component#render()}.
169
      * Implements React's {@link Component#render()}.
39
         const { _notifications } = this.props;
174
         const { _notifications } = this.props;
40
 
175
 
41
         // Currently the native container displays only the topmost notification
176
         // Currently the native container displays only the topmost notification
42
-        const theNotification
43
-            = _notifications && _notifications.length && _notifications[0];
177
+        const theNotification = _notifications[0];
44
 
178
 
45
         if (!theNotification) {
179
         if (!theNotification) {
46
             return null;
180
             return null;
64
     _onDismissed: number => void;
198
     _onDismissed: number => void;
65
 }
199
 }
66
 
200
 
67
-export default connect(_abstractMapStateToProps)(NotificationsContainer);
201
+/**
202
+ * Maps (parts of) the Redux state to the associated NotificationsContainer's
203
+ * props.
204
+ *
205
+ * @param {Object} state - The Redux state.
206
+ * @private
207
+ * @returns {Props}
208
+ */
209
+export function mapStateToProps(state: Object) {
210
+    const { notifications } = state['features/notifications'];
211
+    const _visible = areThereNotifications(state);
212
+
213
+    return {
214
+        _notifications: _visible ? notifications : []
215
+    };
216
+}
217
+
218
+export default connect(mapStateToProps)(NotificationsContainer);

+ 11
- 35
react/features/notifications/components/web/Notification.js Näytä tiedosto

2
 
2
 
3
 import Flag from '@atlaskit/flag';
3
 import Flag from '@atlaskit/flag';
4
 import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
4
 import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
5
-import ErrorIcon from '@atlaskit/icon/glyph/error';
6
-import WarningIcon from '@atlaskit/icon/glyph/warning';
7
-import { colors } from '@atlaskit/theme';
8
 import React from 'react';
5
 import React from 'react';
9
 
6
 
10
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
8
+import { colors } from '../../../base/ui/Tokens';
11
 import { NOTIFICATION_TYPE } from '../../constants';
9
 import { NOTIFICATION_TYPE } from '../../constants';
12
 import AbstractNotification, {
10
 import AbstractNotification, {
13
     type Props
11
     type Props
21
  * @type {{error, info, normal, success, warning}}
19
  * @type {{error, info, normal, success, warning}}
22
  */
20
  */
23
 const ICON_COLOR = {
21
 const ICON_COLOR = {
24
-    error: colors.R400,
25
-    info: colors.N500,
26
-    normal: colors.N0,
27
-    success: colors.G400,
28
-    warning: colors.Y200
22
+    error: colors.error06,
23
+    normal: colors.primary06,
24
+    warning: colors.warning05
29
 };
25
 };
30
 
26
 
31
 /**
27
 /**
42
      */
38
      */
43
     render() {
39
     render() {
44
         const {
40
         const {
45
-            appearance,
46
             hideErrorSupportLink,
41
             hideErrorSupportLink,
47
             t,
42
             t,
48
             title,
43
             title,
54
         return (
49
         return (
55
             <Flag
50
             <Flag
56
                 actions = { this._mapAppearanceToButtons(hideErrorSupportLink) }
51
                 actions = { this._mapAppearanceToButtons(hideErrorSupportLink) }
57
-                appearance = { appearance }
58
                 description = { this._renderDescription() }
52
                 description = { this._renderDescription() }
59
                 icon = { this._mapAppearanceToIcon() }
53
                 icon = { this._mapAppearanceToIcon() }
60
                 id = { uid }
54
                 id = { uid }
163
         const secIconColor = ICON_COLOR[this.props.appearance];
157
         const secIconColor = ICON_COLOR[this.props.appearance];
164
         const iconSize = 'medium';
158
         const iconSize = 'medium';
165
 
159
 
166
-        switch (appearance) {
167
-        case NOTIFICATION_TYPE.ERROR:
168
-            return (
169
-                <ErrorIcon
170
-                    label = { appearance }
171
-                    secondaryColor = { secIconColor }
172
-                    size = { iconSize } />
173
-            );
174
-
175
-        case NOTIFICATION_TYPE.WARNING:
176
-            return (
177
-                <WarningIcon
178
-                    label = { appearance }
179
-                    secondaryColor = { secIconColor }
180
-                    size = { iconSize } />
181
-            );
182
-
183
-        default:
184
-            return (
185
-                <EditorInfoIcon
186
-                    label = { appearance }
187
-                    secondaryColor = { secIconColor }
188
-                    size = { iconSize } />
189
-            );
190
-        }
160
+        return <>
161
+            <div className = { `ribbon ${appearance}` } />
162
+            <EditorInfoIcon
163
+                label = { appearance }
164
+                secondaryColor = { secIconColor }
165
+                size = { iconSize } />
166
+        </>;
191
     }
167
     }
192
 }
168
 }
193
 
169
 

+ 226
- 27
react/features/notifications/components/web/NotificationsContainer.js Näytä tiedosto

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { FlagGroup } from '@atlaskit/flag';
4
-import React from 'react';
3
+import { FlagGroupContext } from '@atlaskit/flag/flag-group';
4
+import { AtlasKitThemeProvider } from '@atlaskit/theme';
5
+import { withStyles } from '@material-ui/styles';
6
+import React, { Component } from 'react';
7
+import { CSSTransition, TransitionGroup } from 'react-transition-group';
5
 
8
 
6
 import { translate } from '../../../base/i18n';
9
 import { translate } from '../../../base/i18n';
7
 import { connect } from '../../../base/redux';
10
 import { connect } from '../../../base/redux';
8
-import AbstractNotificationsContainer, {
9
-    _abstractMapStateToProps,
10
-    type Props as AbstractProps
11
-} from '../AbstractNotificationsContainer';
11
+import { hideNotification } from '../../actions';
12
+import { areThereNotifications } from '../../functions';
12
 
13
 
13
 import Notification from './Notification';
14
 import Notification from './Notification';
14
 
15
 
15
-type Props = AbstractProps & {
16
+declare var interfaceConfig: Object;
17
+
18
+type Props = {
16
 
19
 
17
     /**
20
     /**
18
      * Whether we are a SIP gateway or not.
21
      * Whether we are a SIP gateway or not.
19
      */
22
      */
20
-     _iAmSipGateway: boolean,
23
+    _iAmSipGateway: boolean,
24
+
25
+    /**
26
+     * Whether or not the chat is open.
27
+     */
28
+    _isChatOpen: boolean,
29
+
30
+    /**
31
+     * The notifications to be displayed, with the first index being the
32
+     * notification at the top and the rest shown below it in order.
33
+     */
34
+    _notifications: Array<Object>,
35
+
36
+    /**
37
+     * The length, in milliseconds, to use as a default timeout for all
38
+     * dismissible timeouts that do not have a timeout specified.
39
+     */
40
+    autoDismissTimeout: number,
41
+
42
+    /**
43
+     * JSS classes object.
44
+     */
45
+    classes: Object,
46
+
47
+    /**
48
+     * Invoked to update the redux store in order to remove notifications.
49
+     */
50
+    dispatch: Function,
51
+
52
+    /**
53
+     * Whether or not the notifications are displayed in a portal.
54
+     */
55
+    portal?: boolean,
21
 
56
 
22
-     /**
57
+    /**
23
      * Invoked to obtain translated strings.
58
      * Invoked to obtain translated strings.
24
      */
59
      */
25
-     t: Function
60
+    t: Function
61
+};
62
+
63
+const useStyles = theme => {
64
+    return {
65
+        container: {
66
+            position: 'absolute',
67
+            left: '16px',
68
+            bottom: '90px',
69
+            width: '400px',
70
+            maxWidth: '100%',
71
+            zIndex: 600
72
+        },
73
+
74
+        containerPortal: {
75
+            maxWidth: 'calc(100% - 32px)'
76
+        },
77
+
78
+        containerChatOpen: {
79
+            left: '331px'
80
+        },
81
+
82
+        transitionGroup: {
83
+            '& > *': {
84
+                marginBottom: '20px',
85
+                borderRadius: '6px!important', // !important used to overwrite atlaskit style
86
+                position: 'relative'
87
+            },
88
+
89
+            '& div > span > svg > path': {
90
+                fill: 'inherit'
91
+            },
92
+
93
+            '& div > span, & div > p': {
94
+                color: theme.palette.field01
95
+            },
96
+
97
+            '& .ribbon': {
98
+                width: '4px',
99
+                height: 'calc(100% - 16px)',
100
+                position: 'absolute',
101
+                left: 0,
102
+                top: '8px',
103
+                borderRadius: '4px',
104
+
105
+                '&.normal': {
106
+                    backgroundColor: theme.palette.link01Active
107
+                },
108
+
109
+                '&.error': {
110
+                    backgroundColor: theme.palette.iconError
111
+                },
112
+
113
+                '&.warning': {
114
+                    backgroundColor: theme.palette.warning01
115
+                }
116
+            }
117
+        }
118
+    };
26
 };
119
 };
27
 
120
 
28
 /**
121
 /**
29
  * Implements a React {@link Component} which displays notifications and handles
122
  * Implements a React {@link Component} which displays notifications and handles
30
- * automatic dismissmal after a notification is shown for a defined timeout
123
+ * automatic dismissal after a notification is shown for a defined timeout
31
  * period.
124
  * period.
32
  *
125
  *
33
  * @extends {Component}
126
  * @extends {Component}
34
  */
127
  */
35
-class NotificationsContainer extends AbstractNotificationsContainer<Props> {
128
+class NotificationsContainer extends Component<Props> {
129
+    _api: Object;
130
+    _timeouts: Map<string, TimeoutID>;
131
+
132
+    /**
133
+     * Initializes a new {@code NotificationsContainer} instance.
134
+     *
135
+     * @inheritdoc
136
+     */
137
+    constructor(props: Props) {
138
+        super(props);
139
+
140
+        this._timeouts = new Map();
141
+
142
+        // Bind event handlers so they are only bound once for every instance.
143
+        this._onDismissed = this._onDismissed.bind(this);
144
+
145
+        // HACK ALERT! We are rendering AtlasKit Flag elements outside of a FlagGroup container.
146
+        // In order to hook-up the dismiss action we'll a fake context provider,
147
+        // just like FlagGroup does.
148
+        this._api = {
149
+            onDismissed: this._onDismissed,
150
+            dismissAllowed: () => true
151
+        };
152
+    }
153
+
154
+    /**
155
+     * Sets a timeout for each notification, where applicable.
156
+     *
157
+     * @inheritdoc
158
+     */
159
+    componentDidMount() {
160
+        this._updateTimeouts();
161
+    }
162
+
163
+    /**
164
+     * Sets a timeout for each notification, where applicable.
165
+     *
166
+     * @inheritdoc
167
+     */
168
+    componentDidUpdate() {
169
+        this._updateTimeouts();
170
+    }
36
 
171
 
37
     /**
172
     /**
38
      * Implements React's {@link Component#render()}.
173
      * Implements React's {@link Component#render()}.
46
         }
181
         }
47
 
182
 
48
         return (
183
         return (
49
-            <FlagGroup
50
-                id = 'notifications-container'
51
-                label = { this.props.t('notify.groupTitle') }
52
-                onDismissed = { this._onDismissed }>
53
-                { this._renderFlags() }
54
-            </FlagGroup>
184
+            <AtlasKitThemeProvider mode = 'light'>
185
+                <FlagGroupContext.Provider value = { this._api }>
186
+                    <div
187
+                        className = { `${this.props.classes.container} ${this.props.portal
188
+                            ? this.props.classes.containerPortal
189
+                            : this.props._isChatOpen
190
+                                ? this.props.classes.containerChatOpen
191
+                                : ''}`
192
+                        }
193
+                        id = 'notifications-container'>
194
+                        <TransitionGroup className = { this.props.classes.transitionGroup }>
195
+                            {this._renderFlags()}
196
+                        </TransitionGroup>
197
+                    </div>
198
+                </FlagGroupContext.Provider>
199
+            </AtlasKitThemeProvider>
55
         );
200
         );
56
     }
201
     }
57
 
202
 
58
     _onDismissed: number => void;
203
     _onDismissed: number => void;
59
 
204
 
205
+    /**
206
+     * Emits an action to remove the notification from the redux store so it
207
+     * stops displaying.
208
+     *
209
+     * @param {number} uid - The id of the notification to be removed.
210
+     * @private
211
+     * @returns {void}
212
+     */
213
+    _onDismissed(uid) {
214
+        const timeout = this._timeouts.get(uid);
215
+
216
+        if (timeout) {
217
+            clearTimeout(timeout);
218
+            this._timeouts.delete(uid);
219
+        }
220
+
221
+        this.props.dispatch(hideNotification(uid));
222
+    }
223
+
60
     /**
224
     /**
61
      * Renders notifications to display as ReactElements. An empty array will
225
      * Renders notifications to display as ReactElements. An empty array will
62
      * be returned if notifications are disabled.
226
      * be returned if notifications are disabled.
74
             // either id or key to set a key on notifications, but accessing
238
             // either id or key to set a key on notifications, but accessing
75
             // props.key will cause React to print an error.
239
             // props.key will cause React to print an error.
76
             return (
240
             return (
77
-                <Notification
78
-                    { ...props }
79
-                    id = { uid }
241
+                <CSSTransition
242
+                    appear = { true }
243
+                    classNames = 'notification'
244
+                    in = { true }
80
                     key = { uid }
245
                     key = { uid }
81
-                    onDismissed = { this._onDismissed }
82
-                    uid = { uid } />
246
+                    timeout = { 200 }>
247
+                    <Notification
248
+                        { ...props }
249
+                        id = { uid }
250
+                        onDismissed = { this._onDismissed }
251
+                        uid = { uid } />
252
+                </CSSTransition>
83
             );
253
             );
84
         });
254
         });
85
     }
255
     }
256
+
257
+    /**
258
+     * Updates the timeouts for every notification.
259
+     *
260
+     * @returns {void}
261
+     */
262
+    _updateTimeouts() {
263
+        const { _notifications, autoDismissTimeout } = this.props;
264
+
265
+        for (const notification of _notifications) {
266
+            if ((notification.timeout || typeof autoDismissTimeout === 'number')
267
+                    && notification.props.isDismissAllowed !== false
268
+                    && !this._timeouts.has(notification.uid)) {
269
+                const {
270
+                    timeout = autoDismissTimeout,
271
+                    uid
272
+                } = notification;
273
+                const timerID = setTimeout(() => {
274
+                    this._onDismissed(uid);
275
+                }, timeout);
276
+
277
+                this._timeouts.set(uid, timerID);
278
+            }
279
+        }
280
+    }
86
 }
281
 }
87
 
282
 
88
 /**
283
 /**
93
  * @returns {Props}
288
  * @returns {Props}
94
  */
289
  */
95
 function _mapStateToProps(state) {
290
 function _mapStateToProps(state) {
291
+    const { notifications } = state['features/notifications'];
96
     const { iAmSipGateway } = state['features/base/config'];
292
     const { iAmSipGateway } = state['features/base/config'];
293
+    const { isOpen: isChatOpen } = state['features/chat'];
294
+    const _visible = areThereNotifications(state);
97
 
295
 
98
     return {
296
     return {
99
-        ..._abstractMapStateToProps(state),
100
-        _iAmSipGateway: Boolean(iAmSipGateway)
297
+        _iAmSipGateway: Boolean(iAmSipGateway),
298
+        _isChatOpen: isChatOpen,
299
+        _notifications: _visible ? notifications : [],
300
+        autoDismissTimeout: interfaceConfig.ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT
101
     };
301
     };
102
 }
302
 }
103
 
303
 
104
-
105
-export default translate(connect(_mapStateToProps)(NotificationsContainer));
304
+export default translate(connect(_mapStateToProps)(withStyles(useStyles)(NotificationsContainer)));

+ 3
- 3
react/features/participants-pane/components/web/LobbyParticipants.js Näytä tiedosto

10
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
10
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
11
 import { admitMultiple } from '../../../lobby/actions.web';
11
 import { admitMultiple } from '../../../lobby/actions.web';
12
 import { getLobbyEnabled, getKnockingParticipants } from '../../../lobby/functions';
12
 import { getLobbyEnabled, getKnockingParticipants } from '../../../lobby/functions';
13
-import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
13
+import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
14
 import { showOverflowDrawer } from '../../../toolbox/functions';
14
 import { showOverflowDrawer } from '../../../toolbox/functions';
15
 import { useLobbyActions, useParticipantDrawer } from '../../hooks';
15
 import { useLobbyActions, useParticipantDrawer } from '../../hooks';
16
 
16
 
96
             openDrawerForParticipant = { openDrawerForParticipant }
96
             openDrawerForParticipant = { openDrawerForParticipant }
97
             overflowDrawer = { overflowDrawer }
97
             overflowDrawer = { overflowDrawer }
98
             participants = { participants } />
98
             participants = { participants } />
99
-        <DrawerPortal>
99
+        <JitsiPortal>
100
             <Drawer
100
             <Drawer
101
                 isOpen = { Boolean(drawerParticipant && overflowDrawer) }
101
                 isOpen = { Boolean(drawerParticipant && overflowDrawer) }
102
                 onClose = { closeDrawer }>
102
                 onClose = { closeDrawer }>
128
                     </li>
128
                     </li>
129
                 </ul>
129
                 </ul>
130
             </Drawer>
130
             </Drawer>
131
-        </DrawerPortal>
131
+        </JitsiPortal>
132
     </>
132
     </>
133
     );
133
     );
134
 }
134
 }

+ 3
- 3
react/features/participants-pane/components/web/MeetingParticipantContextMenu.js Näytä tiedosto

30
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
30
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
31
 import { openChatById } from '../../../chat/actions';
31
 import { openChatById } from '../../../chat/actions';
32
 import { setVolume } from '../../../filmstrip/actions.web';
32
 import { setVolume } from '../../../filmstrip/actions.web';
33
-import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
33
+import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
34
 import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
34
 import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
35
 import { VolumeSlider } from '../../../video-menu/components/web';
35
 import { VolumeSlider } from '../../../video-menu/components/web';
36
 import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
36
 import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
533
                       { actions }
533
                       { actions }
534
                   </ContextMenu>}
534
                   </ContextMenu>}
535
 
535
 
536
-                <DrawerPortal>
536
+                <JitsiPortal>
537
                     <Drawer
537
                     <Drawer
538
                         isOpen = { drawerParticipant && overflowDrawer }
538
                         isOpen = { drawerParticipant && overflowDrawer }
539
                         onClose = { closeDrawer }>
539
                         onClose = { closeDrawer }>
549
                             { actions }
549
                             { actions }
550
                         </div>
550
                         </div>
551
                     </Drawer>
551
                     </Drawer>
552
-                </DrawerPortal>
552
+                </JitsiPortal>
553
             </>
553
             </>
554
         );
554
         );
555
     }
555
     }

+ 3
- 3
react/features/participants-pane/components/web/ParticipantsPane.js Näytä tiedosto

7
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
8
 import { isLocalParticipantModerator } from '../../../base/participants';
8
 import { isLocalParticipantModerator } from '../../../base/participants';
9
 import { connect } from '../../../base/redux';
9
 import { connect } from '../../../base/redux';
10
-import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
10
+import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
11
 import { showOverflowDrawer } from '../../../toolbox/functions';
11
 import { showOverflowDrawer } from '../../../toolbox/functions';
12
 import { MuteEveryoneDialog } from '../../../video-menu/components/';
12
 import { MuteEveryoneDialog } from '../../../video-menu/components/';
13
 import { close } from '../../actions';
13
 import { close } from '../../actions';
166
                             </Footer>
166
                             </Footer>
167
                         )}
167
                         )}
168
                     </div>
168
                     </div>
169
-                    <DrawerPortal>
169
+                    <JitsiPortal>
170
                         <Drawer
170
                         <Drawer
171
                             isOpen = { contextOpen && _overflowDrawer }
171
                             isOpen = { contextOpen && _overflowDrawer }
172
                             onClose = { this._onDrawerClose }>
172
                             onClose = { this._onDrawerClose }>
173
                             <FooterContextMenu inDrawer = { true } />
173
                             <FooterContextMenu inDrawer = { true } />
174
                         </Drawer>
174
                         </Drawer>
175
-                    </DrawerPortal>
175
+                    </JitsiPortal>
176
                 </div>
176
                 </div>
177
             </ThemeProvider>
177
             </ThemeProvider>
178
         );
178
         );

react/features/toolbox/components/web/DrawerPortal.js → react/features/toolbox/components/web/JitsiPortal.js Näytä tiedosto

8
     /**
8
     /**
9
      * The component(s) to be displayed within the drawer portal.
9
      * The component(s) to be displayed within the drawer portal.
10
      */
10
      */
11
-    children: React$Node
11
+    children: React$Node,
12
+
13
+    /**
14
+     * Class name used to add custom styles to the portal.
15
+     */
16
+    className?: string
12
 };
17
 };
13
 
18
 
14
 /**
19
 /**
17
  *
22
  *
18
  * @returns {ReactElement}
23
  * @returns {ReactElement}
19
  */
24
  */
20
-function DrawerPortal({ children }: Props) {
25
+function JitsiPortal({ children, className }: Props) {
21
     return (
26
     return (
22
-        <DialogPortal className = 'drawer-portal'>
27
+        <DialogPortal className = { `drawer-portal ${className ?? ''}` }>
23
             { children }
28
             { children }
24
         </DialogPortal>
29
         </DialogPortal>
25
     );
30
     );
26
 }
31
 }
27
 
32
 
28
-export default DrawerPortal;
33
+export default JitsiPortal;

+ 3
- 3
react/features/toolbox/components/web/OverflowMenuButton.js Näytä tiedosto

12
 import { getReactionsQueue } from '../../../reactions/functions.any';
12
 import { getReactionsQueue } from '../../../reactions/functions.any';
13
 
13
 
14
 import Drawer from './Drawer';
14
 import Drawer from './Drawer';
15
-import DrawerPortal from './DrawerPortal';
15
+import JitsiPortal from './JitsiPortal';
16
 import ToolbarButton from './ToolbarButton';
16
 import ToolbarButton from './ToolbarButton';
17
 
17
 
18
 /**
18
 /**
114
                     overflowDrawer ? (
114
                     overflowDrawer ? (
115
                         <>
115
                         <>
116
                             {this._renderToolbarButton()}
116
                             {this._renderToolbarButton()}
117
-                            <DrawerPortal>
117
+                            <JitsiPortal>
118
                                 <Drawer
118
                                 <Drawer
119
                                     isOpen = { isOpen }
119
                                     isOpen = { isOpen }
120
                                     onClose = { this._onCloseDialog }>
120
                                     onClose = { this._onCloseDialog }>
128
                                         reaction = { reaction }
128
                                         reaction = { reaction }
129
                                         uid = { uid } />))}
129
                                         uid = { uid } />))}
130
                                 </div>}
130
                                 </div>}
131
-                            </DrawerPortal>
131
+                            </JitsiPortal>
132
                         </>
132
                         </>
133
                     ) : (
133
                     ) : (
134
                         <InlineDialog
134
                         <InlineDialog

+ 1
- 1
react/features/toolbox/components/web/index.js Näytä tiedosto

3
 export { default as ToolbarButton } from './ToolbarButton';
3
 export { default as ToolbarButton } from './ToolbarButton';
4
 export { default as Toolbox } from './Toolbox';
4
 export { default as Toolbox } from './Toolbox';
5
 export { default as Drawer } from './Drawer';
5
 export { default as Drawer } from './Drawer';
6
-export { default as DrawerPortal } from './DrawerPortal';
6
+export { default as JitsiPortal } from './JitsiPortal';
7
 export { default as DialogPortal } from './DialogPortal';
7
 export { default as DialogPortal } from './DialogPortal';

Loading…
Peruuta
Tallenna