Просмотр исходного кода

[RN] Implement Notifications on mobile

master
Bettenbuk Zoltan 6 лет назад
Родитель
Сommit
ffd0827354
34 измененных файлов: 2513 добавлений и 1919 удалений
  1. 3
    0
      css/_font.scss
  2. Двоичные данные
      fonts/jitsi.eot
  3. 1
    0
      fonts/jitsi.svg
  4. Двоичные данные
      fonts/jitsi.ttf
  5. Двоичные данные
      fonts/jitsi.woff
  6. 803
    776
      fonts/selection.json
  7. 803
    776
      react/features/base/font-icons/jitsi.json
  8. 12
    1
      react/features/base/styles/components/styles/ColorPalette.js
  9. 3
    0
      react/features/conference/components/Conference.native.js
  10. 4
    2
      react/features/filmstrip/components/native/Filmstrip.js
  11. 5
    1
      react/features/filmstrip/components/styles.js
  12. 6
    0
      react/features/filmstrip/constants.js
  13. 20
    0
      react/features/filmstrip/functions.native.js
  14. 16
    0
      react/features/filmstrip/functions.web.js
  15. 1
    0
      react/features/filmstrip/index.js
  16. 10
    0
      react/features/notifications/actionTypes.js
  17. 21
    5
      react/features/notifications/actions.js
  18. 147
    0
      react/features/notifications/components/AbstractNotification.js
  19. 146
    0
      react/features/notifications/components/AbstractNotificationsContainer.js
  20. 109
    0
      react/features/notifications/components/Notification.native.js
  21. 11
    126
      react/features/notifications/components/Notification.web.js
  22. 145
    0
      react/features/notifications/components/NotificationsContainer.native.js
  23. 11
    142
      react/features/notifications/components/NotificationsContainer.web.js
  24. 115
    0
      react/features/notifications/components/styles.js
  25. 1
    0
      react/features/notifications/index.js
  26. 19
    0
      react/features/notifications/middleware.js
  27. 6
    0
      react/features/notifications/reducer.js
  28. 2
    88
      react/features/overlay/components/OverlayContainer.js
  29. 14
    0
      react/features/overlay/components/index.js
  30. 66
    0
      react/features/overlay/functions.js
  31. 1
    0
      react/features/overlay/index.js
  32. 4
    2
      react/features/toolbox/components/native/styles.js
  33. 7
    0
      react/features/toolbox/constants.js
  34. 1
    0
      react/features/toolbox/index.js

+ 3
- 0
css/_font.scss Просмотреть файл

27
 .icon-arrow_back:before {
27
 .icon-arrow_back:before {
28
   content: "\e5c4";
28
   content: "\e5c4";
29
 }
29
 }
30
+.icon-close:before {
31
+  content: "\e5cd";
32
+}
30
 .icon-event_note:before {
33
 .icon-event_note:before {
31
     content: "\e616";
34
     content: "\e616";
32
 }
35
 }

Двоичные данные
fonts/jitsi.eot Просмотреть файл


+ 1
- 0
fonts/jitsi.svg Просмотреть файл

15
 <glyph unicode="&#xe409;" glyph-name="navigate_next" d="M426 768l256-256-256-256-60 60 196 196-196 196z" />
15
 <glyph unicode="&#xe409;" glyph-name="navigate_next" d="M426 768l256-256-256-256-60 60 196 196-196 196z" />
16
 <glyph unicode="&#xe425;" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
16
 <glyph unicode="&#xe425;" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
17
 <glyph unicode="&#xe5c4;" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
17
 <glyph unicode="&#xe5c4;" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
18
+<glyph unicode="&#xe5cd;" glyph-name="close" d="M810 750l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
18
 <glyph unicode="&#xe5d2;" glyph-name="menu" d="M128 768h768v-86h-768v86zM128 470v84h768v-84h-768zM128 256v86h768v-86h-768z" />
19
 <glyph unicode="&#xe5d2;" glyph-name="menu" d="M128 768h768v-86h-768v86zM128 470v84h768v-84h-768zM128 256v86h768v-86h-768z" />
19
 <glyph unicode="&#xe5d4;" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
20
 <glyph unicode="&#xe5d4;" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
20
 <glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
21
 <glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />

Двоичные данные
fonts/jitsi.ttf Просмотреть файл


Двоичные данные
fonts/jitsi.woff Просмотреть файл


+ 803
- 776
fonts/selection.json
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 803
- 776
react/features/base/font-icons/jitsi.json
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 12
- 1
react/features/base/styles/components/styles/ColorPalette.js Просмотреть файл

24
     darkGrey: '#555555',
24
     darkGrey: '#555555',
25
     green: '#40b183',
25
     green: '#40b183',
26
     red: '#D00000',
26
     red: '#D00000',
27
-    white: 'white'
27
+    white: 'white',
28
+
29
+    /**
30
+     * These are colors from the atlaskit to be used on mobile, when needed.
31
+     *
32
+     * FIXME: Maybe a better solution would be good, or a native packaging of
33
+     * the respective atlaskit components.
34
+     */
35
+    G400: '#00875A', // Slime
36
+    N500: '#42526E', // McFanning
37
+    R400: '#DE350B', // Red dirt
38
+    Y200: '#FFC400' // Pub mix
28
 };
39
 };

+ 3
- 0
react/features/conference/components/Conference.native.js Просмотреть файл

17
 import { ConferenceNotification } from '../../calendar-sync';
17
 import { ConferenceNotification } from '../../calendar-sync';
18
 import { Filmstrip } from '../../filmstrip';
18
 import { Filmstrip } from '../../filmstrip';
19
 import { LargeVideo } from '../../large-video';
19
 import { LargeVideo } from '../../large-video';
20
+import { NotificationsContainer } from '../../notifications';
20
 import { setToolboxVisible, Toolbox } from '../../toolbox';
21
 import { setToolboxVisible, Toolbox } from '../../toolbox';
21
 
22
 
22
 import ConferenceIndicators from './ConferenceIndicators';
23
 import ConferenceIndicators from './ConferenceIndicators';
267
                     this._renderConferenceNotification()
268
                     this._renderConferenceNotification()
268
                 }
269
                 }
269
 
270
 
271
+                <NotificationsContainer />
272
+
270
                 {/*
273
                 {/*
271
                   * The dialogs are in the topmost stacking layers.
274
                   * The dialogs are in the topmost stacking layers.
272
                   */
275
                   */

+ 4
- 2
react/features/filmstrip/components/native/Filmstrip.js Просмотреть файл

10
     makeAspectRatioAware
10
     makeAspectRatioAware
11
 } from '../../../base/responsive-ui';
11
 } from '../../../base/responsive-ui';
12
 
12
 
13
+import { isFilmstripVisible } from '../../functions';
14
+
13
 import LocalThumbnail from './LocalThumbnail';
15
 import LocalThumbnail from './LocalThumbnail';
14
 import styles from './styles';
16
 import styles from './styles';
15
 import Thumbnail from './Thumbnail';
17
 import Thumbnail from './Thumbnail';
188
  */
190
  */
189
 function _mapStateToProps(state) {
191
 function _mapStateToProps(state) {
190
     const participants = state['features/base/participants'];
192
     const participants = state['features/base/participants'];
191
-    const { enabled, visible } = state['features/filmstrip'];
193
+    const { enabled } = state['features/filmstrip'];
192
 
194
 
193
     return {
195
     return {
194
         /**
196
         /**
215
          * @private
217
          * @private
216
          * @type {boolean}
218
          * @type {boolean}
217
          */
219
          */
218
-        _visible: visible && participants.length > 1
220
+        _visible: isFilmstripVisible(state)
219
     };
221
     };
220
 }
222
 }
221
 
223
 

+ 5
- 1
react/features/filmstrip/components/styles.js Просмотреть файл

1
 import { ColorPalette } from '../../base/styles';
1
 import { ColorPalette } from '../../base/styles';
2
+import { FILMSTRIP_SIZE } from '../constants';
2
 
3
 
3
 /**
4
 /**
4
  * Size for the Avatar.
5
  * Size for the Avatar.
44
         ...filmstrip,
45
         ...filmstrip,
45
         flexDirection: 'row',
46
         flexDirection: 'row',
46
         justifyContent: 'flex-end',
47
         justifyContent: 'flex-end',
47
-        height: 90
48
+        height: FILMSTRIP_SIZE
48
     },
49
     },
49
 
50
 
50
     /**
51
     /**
51
      * The style of the wide {@link Filmstrip} version which displays thumbnails
52
      * The style of the wide {@link Filmstrip} version which displays thumbnails
52
      * in a column on the short size of the screen.
53
      * in a column on the short size of the screen.
54
+     *
55
+     * NOTE: width is calculated based on the children, but it should also align
56
+     * to {@code FILMSTRIP_SIZE}.
53
      */
57
      */
54
     filmstripWide: {
58
     filmstripWide: {
55
         ...filmstrip,
59
         ...filmstrip,

+ 6
- 0
react/features/filmstrip/constants.js Просмотреть файл

1
+// @flow
2
+
3
+/**
4
+ * The height of the filmstrip in narrow aspect ratio, or width in wide.
5
+ */
6
+export const FILMSTRIP_SIZE = 90;

+ 20
- 0
react/features/filmstrip/functions.native.js Просмотреть файл

1
+// @flow
2
+
3
+import { toState } from '../base/redux';
4
+
5
+/**
6
+ * Returns true if the filmstrip on mobile is visible, false otherwise.
7
+ *
8
+ * NOTE: Filmstrip on mobile behaves differently to web, and is only visible
9
+ * when there are at least 2 participants.
10
+ *
11
+ * @param {Object | Function} stateful - The Object or Function that can be
12
+ * resolved to a Redux state object with the toState function.
13
+ * @returns {boolean}
14
+ */
15
+export function isFilmstripVisible(stateful: Object | Function) {
16
+    const state = toState(stateful);
17
+    const { length: participantCount } = state['features/base/participants'];
18
+
19
+    return state['features/filmstrip'].visible && participantCount > 1;
20
+}

react/features/filmstrip/functions.js → react/features/filmstrip/functions.web.js Просмотреть файл

4
     getParticipantCount,
4
     getParticipantCount,
5
     getPinnedParticipant
5
     getPinnedParticipant
6
 } from '../base/participants';
6
 } from '../base/participants';
7
+import { toState } from '../base/redux';
7
 
8
 
8
 declare var interfaceConfig: Object;
9
 declare var interfaceConfig: Object;
9
 
10
 
11
+/**
12
+ * Returns true if the filmstrip on mobile is visible, false otherwise.
13
+ *
14
+ * NOTE: Filmstrip on web behaves differently to mobile, much simpler, but so
15
+ * function lies here only for the sake of consistency and to avoid flow errors
16
+ * on import.
17
+ *
18
+ * @param {Object | Function} stateful - The Object or Function that can be
19
+ * resolved to a Redux state object with the toState function.
20
+ * @returns {boolean}
21
+ */
22
+export function isFilmstripVisible(stateful: Object | Function) {
23
+    return toState(stateful)['features/filmstrip'].visible;
24
+}
25
+
10
 /**
26
 /**
11
  * Determines whether the remote video thumbnails should be displayed/visible in
27
  * Determines whether the remote video thumbnails should be displayed/visible in
12
  * the filmstrip.
28
  * the filmstrip.

+ 1
- 0
react/features/filmstrip/index.js Просмотреть файл

1
 export * from './actions';
1
 export * from './actions';
2
 export * from './actionTypes';
2
 export * from './actionTypes';
3
 export * from './components';
3
 export * from './components';
4
+export * from './constants';
4
 export * from './functions';
5
 export * from './functions';
5
 
6
 
6
 import './middleware';
7
 import './middleware';

+ 10
- 0
react/features/notifications/actionTypes.js Просмотреть файл

1
+/**
2
+ * The type of (redux) action which signals that all the stored notifications
3
+ * need to be cleared.
4
+ *
5
+ * {
6
+ *     type: CLEAR_NOTIFICATIONS
7
+ * }
8
+ */
9
+export const CLEAR_NOTIFICATIONS = Symbol('CLEAR_NOTIFICATIONS');
10
+
1
 /**
11
 /**
2
  * The type of (redux) action which signals that a specific notification should
12
  * The type of (redux) action which signals that a specific notification should
3
  * not be displayed anymore.
13
  * not be displayed anymore.

+ 21
- 5
react/features/notifications/actions.js Просмотреть файл

1
+// @flow
2
+
1
 import {
3
 import {
4
+    CLEAR_NOTIFICATIONS,
2
     HIDE_NOTIFICATION,
5
     HIDE_NOTIFICATION,
3
     SET_NOTIFICATIONS_ENABLED,
6
     SET_NOTIFICATIONS_ENABLED,
4
     SHOW_NOTIFICATION
7
     SHOW_NOTIFICATION
6
 
9
 
7
 import { NOTIFICATION_TYPE } from './constants';
10
 import { NOTIFICATION_TYPE } from './constants';
8
 
11
 
12
+/**
13
+ * Clears (removes) all the notifications.
14
+ *
15
+ * @returns {{
16
+ *     type: CLEAR_NOTIFICATIONS
17
+ * }}
18
+ */
19
+export function clearNotifications() {
20
+    return {
21
+        type: CLEAR_NOTIFICATIONS
22
+    };
23
+}
24
+
9
 /**
25
 /**
10
  * Removes the notification with the passed in id.
26
  * Removes the notification with the passed in id.
11
  *
27
  *
16
  *     uid: number
32
  *     uid: number
17
  * }}
33
  * }}
18
  */
34
  */
19
-export function hideNotification(uid) {
35
+export function hideNotification(uid: number) {
20
     return {
36
     return {
21
         type: HIDE_NOTIFICATION,
37
         type: HIDE_NOTIFICATION,
22
         uid
38
         uid
32
  *     enabled: boolean
48
  *     enabled: boolean
33
  * }}
49
  * }}
34
  */
50
  */
35
-export function setNotificationsEnabled(enabled) {
51
+export function setNotificationsEnabled(enabled: boolean) {
36
     return {
52
     return {
37
         type: SET_NOTIFICATIONS_ENABLED,
53
         type: SET_NOTIFICATIONS_ENABLED,
38
         enabled
54
         enabled
45
  * @param {Object} props - The props needed to show the notification component.
61
  * @param {Object} props - The props needed to show the notification component.
46
  * @returns {Object}
62
  * @returns {Object}
47
  */
63
  */
48
-export function showErrorNotification(props) {
64
+export function showErrorNotification(props: Object) {
49
     return showNotification({
65
     return showNotification({
50
         ...props,
66
         ...props,
51
         appearance: NOTIFICATION_TYPE.ERROR
67
         appearance: NOTIFICATION_TYPE.ERROR
65
  *     uid: number
81
  *     uid: number
66
  * }}
82
  * }}
67
  */
83
  */
68
-export function showNotification(props = {}, timeout) {
84
+export function showNotification(props: Object = {}, timeout: ?number) {
69
     return {
85
     return {
70
         type: SHOW_NOTIFICATION,
86
         type: SHOW_NOTIFICATION,
71
         props,
87
         props,
80
  * @param {Object} props - The props needed to show the notification component.
96
  * @param {Object} props - The props needed to show the notification component.
81
  * @returns {Object}
97
  * @returns {Object}
82
  */
98
  */
83
-export function showWarningNotification(props) {
99
+export function showWarningNotification(props: Object) {
84
     return showNotification({
100
     return showNotification({
85
         ...props,
101
         ...props,
86
         appearance: NOTIFICATION_TYPE.WARNING
102
         appearance: NOTIFICATION_TYPE.WARNING

+ 147
- 0
react/features/notifications/components/AbstractNotification.js Просмотреть файл

1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+import { NOTIFICATION_TYPE } from '../constants';
6
+
7
+export type Props = {
8
+
9
+    /**
10
+     * Display appearance for the component, passed directly to the
11
+     * notification.
12
+     */
13
+    appearance: string,
14
+
15
+    /**
16
+     * The text to display in the body of the notification. If not passed
17
+     * in, the passed in descriptionKey will be used.
18
+     */
19
+    defaultTitleKey: string,
20
+
21
+    /**
22
+     * A description string that can be used in addition to the prop
23
+     * descriptionKey.
24
+     */
25
+    description: string,
26
+
27
+    /**
28
+     * The translation arguments that may be necessary for the description.
29
+     */
30
+    descriptionArguments: Object,
31
+
32
+    /**
33
+     * The translation key to use as the body of the notification.
34
+     */
35
+    descriptionKey: string,
36
+
37
+    /**
38
+     * Whether the support link should be hidden in the case of an error
39
+     * message.
40
+     */
41
+    hideErrorSupportLink: boolean,
42
+
43
+    /**
44
+     * Whether or not the dismiss button should be displayed.
45
+     */
46
+    isDismissAllowed: boolean,
47
+
48
+    /**
49
+     * Callback invoked when the user clicks to dismiss the notification.
50
+     */
51
+    onDismissed: Function,
52
+
53
+    /**
54
+     * Invoked to obtain translated strings.
55
+     */
56
+    t: Function,
57
+
58
+    /**
59
+     * The text to display at the top of the notification. If not passed in,
60
+     * the passed in titleKey will be used.
61
+     */
62
+    title: string,
63
+
64
+    /**
65
+     * The translation arguments that may be necessary for the title.
66
+     */
67
+    titleArguments: Object,
68
+
69
+    /**
70
+     * The translation key to display as the title of the notification if
71
+     * no title is provided.
72
+     */
73
+    titleKey: string,
74
+
75
+    /**
76
+     * The unique identifier for the notification.
77
+     */
78
+    uid: number
79
+};
80
+
81
+/**
82
+ * Abstract class for {@code Notification} component.
83
+ *
84
+ * @extends Component
85
+ */
86
+export default class AbstractNotification<P: Props> extends Component<P> {
87
+    /**
88
+     * Default values for {@code Notification} component's properties.
89
+     *
90
+     * @static
91
+     */
92
+    static defaultProps = {
93
+        appearance: NOTIFICATION_TYPE.NORMAL,
94
+        isDismissAllowed: true
95
+    };
96
+
97
+    /**
98
+     * Initializes a new {@code Notification} instance.
99
+     *
100
+     * @param {Object} props - The read-only properties with which the new
101
+     * instance is to be initialized.
102
+     */
103
+    constructor(props: P) {
104
+        super(props);
105
+
106
+        // Bind event handler so it is only bound once for every instance.
107
+        this._onDismissed = this._onDismissed.bind(this);
108
+    }
109
+
110
+    _getDescription: () => Array<string>
111
+
112
+    /**
113
+     * Returns the description array to be displayed.
114
+     *
115
+     * @protected
116
+     * @returns {Array<string>}
117
+     */
118
+    _getDescription() {
119
+        const {
120
+            description,
121
+            descriptionArguments,
122
+            descriptionKey,
123
+            t
124
+        } = this.props;
125
+
126
+        const descriptionArray = [];
127
+
128
+        descriptionKey
129
+            && descriptionArray.push(t(descriptionKey, descriptionArguments));
130
+
131
+        description && descriptionArray.push(description);
132
+
133
+        return descriptionArray;
134
+    }
135
+
136
+    _onDismissed: () => void;
137
+
138
+    /**
139
+     * Callback to dismiss the notification.
140
+     *
141
+     * @private
142
+     * @returns {void}
143
+     */
144
+    _onDismissed() {
145
+        this.props.onDismissed(this.props.uid);
146
+    }
147
+}

+ 146
- 0
react/features/notifications/components/AbstractNotificationsContainer.js Просмотреть файл

1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+import { getOverlayToRender } from '../../overlay';
6
+
7
+import { hideNotification } from '../actions';
8
+
9
+export type Props = {
10
+
11
+    /**
12
+     * The notifications to be displayed, with the first index being the
13
+     * notification at the top and the rest shown below it in order.
14
+     */
15
+    _notifications: Array<Object>,
16
+
17
+    /**
18
+     * Invoked to update the redux store in order to remove notifications.
19
+     */
20
+    dispatch: Function
21
+};
22
+
23
+/**
24
+ * Abstract class for {@code NotificationsContainer} component.
25
+ */
26
+export default class AbstractNotificationsContainer<P: Props>
27
+    extends Component<P> {
28
+    /**
29
+     * A timeout id returned by setTimeout.
30
+     */
31
+    _notificationDismissTimeout: ?TimeoutID;
32
+
33
+    /**
34
+     * Initializes a new {@code AbstractNotificationsContainer} instance.
35
+     *
36
+     * @inheritdoc
37
+     */
38
+    constructor(props: P) {
39
+        super(props);
40
+
41
+        /**
42
+         * The timeout set for automatically dismissing a displayed
43
+         * notification. This value is set on the instance and not state to
44
+         * avoid additional re-renders.
45
+         *
46
+         * @type {number|null}
47
+         */
48
+        this._notificationDismissTimeout = null;
49
+
50
+        // Bind event handlers so they are only bound once for every instance.
51
+        this._onDismissed = this._onDismissed.bind(this);
52
+    }
53
+
54
+    /**
55
+     * Sets a timeout if the currently displayed notification has changed.
56
+     *
57
+     * @inheritdoc
58
+     */
59
+    componentDidUpdate(prevProps: P) {
60
+        const { _notifications } = this.props;
61
+
62
+        if (_notifications.length) {
63
+            const notification = _notifications[0];
64
+            const previousNotification
65
+                = prevProps._notifications.length
66
+                    ? prevProps._notifications[0]
67
+                    : undefined;
68
+
69
+            if (notification !== previousNotification) {
70
+                this._clearNotificationDismissTimeout();
71
+
72
+                if (notification) {
73
+                    const { timeout, uid } = notification;
74
+
75
+                    this._notificationDismissTimeout = setTimeout(() => {
76
+                        // Perform a no-op if a timeout is not specified.
77
+                        if (Number.isInteger(timeout)) {
78
+                            this._onDismissed(uid);
79
+                        }
80
+                    }, timeout);
81
+                }
82
+            }
83
+        } else if (this._notificationDismissTimeout) {
84
+            // Clear timeout when all notifications are cleared (e.g external
85
+            // call to clear them)
86
+            this._clearNotificationDismissTimeout();
87
+        }
88
+    }
89
+
90
+    /**
91
+     * Clear any dismissal timeout that is still active.
92
+     *
93
+     * @inheritdoc
94
+     * returns {void}
95
+     */
96
+    componentWillUnmount() {
97
+        this._clearNotificationDismissTimeout();
98
+    }
99
+
100
+    _onDismissed: number => void;
101
+
102
+    /**
103
+     * Clears the running notification dismiss timeout, if any.
104
+     *
105
+     * @returns {void}
106
+     */
107
+    _clearNotificationDismissTimeout() {
108
+        this._notificationDismissTimeout
109
+            && clearTimeout(this._notificationDismissTimeout);
110
+
111
+        this._notificationDismissTimeout = null;
112
+    }
113
+
114
+    /**
115
+     * Emits an action to remove the notification from the redux store so it
116
+     * stops displaying.
117
+     *
118
+     * @param {number} uid - The id of the notification to be removed.
119
+     * @private
120
+     * @returns {void}
121
+     */
122
+    _onDismissed(uid) {
123
+        this._clearNotificationDismissTimeout();
124
+
125
+        this.props.dispatch(hideNotification(uid));
126
+    }
127
+}
128
+
129
+/**
130
+ * Maps (parts of) the Redux state to the associated NotificationsContainer's
131
+ * props.
132
+ *
133
+ * @param {Object} state - The Redux state.
134
+ * @private
135
+ * @returns {{
136
+ *     _notifications: Array
137
+ * }}
138
+ */
139
+export function _abstractMapStateToProps(state: Object) {
140
+    const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
141
+    const { enabled, notifications } = state['features/notifications'];
142
+
143
+    return {
144
+        _notifications: enabled && !isAnyOverlayVisible ? notifications : []
145
+    };
146
+}

+ 109
- 0
react/features/notifications/components/Notification.native.js Просмотреть файл

1
+// @flow
2
+
3
+import React from 'react';
4
+import { Text, TouchableOpacity, View } from 'react-native';
5
+
6
+import { Icon } from '../../base/font-icons';
7
+import { translate } from '../../base/i18n';
8
+
9
+import { NOTIFICATION_TYPE } from '../constants';
10
+
11
+import AbstractNotification, {
12
+    type Props
13
+} from './AbstractNotification';
14
+import styles from './styles';
15
+
16
+/**
17
+ * Implements a React {@link Component} to display a notification.
18
+ *
19
+ * @extends Component
20
+ */
21
+class Notification extends AbstractNotification<Props> {
22
+    /**
23
+     * Implements React's {@link Component#render()}.
24
+     *
25
+     * @inheritdoc
26
+     * @returns {ReactElement}
27
+     */
28
+    render() {
29
+        const {
30
+            appearance,
31
+            isDismissAllowed,
32
+            t,
33
+            title,
34
+            titleArguments,
35
+            titleKey
36
+        } = this.props;
37
+
38
+        let notificationStyle;
39
+
40
+        switch (appearance) {
41
+        case NOTIFICATION_TYPE.ERROR:
42
+            notificationStyle = styles.notificationTypeError;
43
+            break;
44
+        case NOTIFICATION_TYPE.NORMAL:
45
+            notificationStyle = styles.notificationTypeNormal;
46
+            break;
47
+        case NOTIFICATION_TYPE.SUCCESS:
48
+            notificationStyle = styles.notificationTypeSuccess;
49
+            break;
50
+        case NOTIFICATION_TYPE.WARNING:
51
+            notificationStyle = styles.notificationTypeWarning;
52
+            break;
53
+        case NOTIFICATION_TYPE.INFO:
54
+        default:
55
+            notificationStyle = styles.notificationTypeInfo;
56
+        }
57
+
58
+        return (
59
+            <View
60
+                pointerEvents = 'box-none'
61
+                style = { [
62
+                    styles.notification,
63
+                    notificationStyle
64
+                ] }>
65
+                <View style = { styles.contentColumn }>
66
+                    <View
67
+                        pointerEvents = 'box-none'
68
+                        style = { styles.notificationTitle }>
69
+                        <Text style = { styles.titleText }>
70
+                            {
71
+                                title || t(titleKey, titleArguments)
72
+                            }
73
+                        </Text>
74
+                    </View>
75
+                    <View
76
+                        pointerEvents = 'box-none'
77
+                        style = { styles.notificationContent }>
78
+                        {
79
+                            // eslint-disable-next-line no-extra-parens
80
+                            this._getDescription().map((line, index) => (
81
+                                <Text
82
+                                    key = { index }
83
+                                    style = { styles.contentText }>
84
+                                    { line }
85
+                                </Text>
86
+                            ))
87
+                        }
88
+                    </View>
89
+                </View>
90
+                {
91
+                    isDismissAllowed
92
+                    && <View style = { styles.actionColumn }>
93
+                        <TouchableOpacity onPress = { this._onDismissed }>
94
+                            <Icon
95
+                                name = { 'close' }
96
+                                style = { styles.dismissIcon } />
97
+                        </TouchableOpacity>
98
+                    </View>
99
+                }
100
+            </View>
101
+        );
102
+    }
103
+
104
+    _getDescription: () => Array<string>;
105
+
106
+    _onDismissed: () => void;
107
+}
108
+
109
+export default translate(Notification);

+ 11
- 126
react/features/notifications/components/Notification.web.js Просмотреть файл

5
 import ErrorIcon from '@atlaskit/icon/glyph/error';
5
 import ErrorIcon from '@atlaskit/icon/glyph/error';
6
 import WarningIcon from '@atlaskit/icon/glyph/warning';
6
 import WarningIcon from '@atlaskit/icon/glyph/warning';
7
 import { colors } from '@atlaskit/theme';
7
 import { colors } from '@atlaskit/theme';
8
-import PropTypes from 'prop-types';
9
-import React, { Component } from 'react';
8
+import React from 'react';
10
 
9
 
11
 import { translate } from '../../base/i18n';
10
 import { translate } from '../../base/i18n';
12
 
11
 
13
 import { NOTIFICATION_TYPE } from '../constants';
12
 import { NOTIFICATION_TYPE } from '../constants';
14
 
13
 
14
+import AbstractNotification, {
15
+    type Props
16
+} from './AbstractNotification';
17
+
15
 declare var interfaceConfig: Object;
18
 declare var interfaceConfig: Object;
16
 
19
 
17
 /**
20
 /**
32
  *
35
  *
33
  * @extends Component
36
  * @extends Component
34
  */
37
  */
35
-class Notification extends Component<*> {
36
-    /**
37
-     * Default values for {@code Notification} component's properties.
38
-     *
39
-     * @static
40
-     */
41
-    static defaultProps = {
42
-        appearance: NOTIFICATION_TYPE.NORMAL
43
-    };
44
-
45
-    /**
46
-     * {@code Notification} component's property types.
47
-     *
48
-     * @static
49
-     */
50
-    static propTypes = {
51
-        /**
52
-         * Display appearance for the component, passed directly to
53
-         * {@code Flag}.
54
-         */
55
-        appearance: PropTypes.string,
56
-
57
-        /**
58
-         * The text to display in the body of the notification. If not passed
59
-         * in, the passed in descriptionKey will be used.
60
-         */
61
-        defaultTitleKey: PropTypes.string,
62
-
63
-        /**
64
-         * A description string that can be used in addition to the prop
65
-         * descriptionKey.
66
-         */
67
-        description: PropTypes.string,
68
-
69
-        /**
70
-         * The translation arguments that may be necessary for the description.
71
-         */
72
-        descriptionArguments: PropTypes.object,
73
-
74
-        /**
75
-         * The translation key to use as the body of the notification.
76
-         */
77
-        descriptionKey: PropTypes.string,
78
-
79
-        /**
80
-         * Whether the support link should be hidden in the case of an error
81
-         * message.
82
-         */
83
-        hideErrorSupportLink: PropTypes.bool,
84
-
85
-        /**
86
-         * Whether or not the dismiss button should be displayed. This is passed
87
-         * in by {@code FlagGroup}.
88
-         */
89
-        isDismissAllowed: PropTypes.bool,
90
-
91
-        /**
92
-         * Callback invoked when the user clicks to dismiss the notification.
93
-         * this is passed in by {@code FlagGroup}.
94
-         */
95
-        onDismissed: PropTypes.func,
96
-
97
-        /**
98
-         * Invoked to obtain translated strings.
99
-         */
100
-        t: PropTypes.func,
101
-
102
-        /**
103
-         * The text to display at the top of the notification. If not passed in,
104
-         * the passed in titleKey will be used.
105
-         */
106
-        title: PropTypes.string,
107
-
108
-        /**
109
-         * The translation arguments that may be necessary for the title.
110
-         */
111
-        titleArguments: PropTypes.object,
112
-
113
-        /**
114
-         * The translation key to display as the title of the notification if
115
-         * no title is provided.
116
-         */
117
-        titleKey: PropTypes.string,
118
-
119
-        /**
120
-         * The unique identifier for the notification. Passed back by the
121
-         * {@code Flag} component in the onDismissed callback.
122
-         */
123
-        uid: PropTypes.number
124
-    };
125
-
126
-    /**
127
-     * Initializes a new {@code Notification} instance.
128
-     *
129
-     * @param {Object} props - The read-only properties with which the new
130
-     * instance is to be initialized.
131
-     */
132
-    constructor(props) {
133
-        super(props);
134
-
135
-        // Bind event handler so it is only bound once for every instance.
136
-        this._onDismissed = this._onDismissed.bind(this);
137
-    }
138
-
38
+class Notification extends AbstractNotification<Props> {
139
     /**
39
     /**
140
      * Implements React's {@link Component#render()}.
40
      * Implements React's {@link Component#render()}.
141
      *
41
      *
168
         );
68
         );
169
     }
69
     }
170
 
70
 
71
+    _getDescription: () => Array<string>
72
+
171
     _onDismissed: () => void;
73
     _onDismissed: () => void;
172
 
74
 
173
     /**
75
     /**
178
      * @returns {ReactElement}
80
      * @returns {ReactElement}
179
      */
81
      */
180
     _renderDescription() {
82
     _renderDescription() {
181
-        const {
182
-            description,
183
-            descriptionArguments,
184
-            descriptionKey,
185
-            t
186
-        } = this.props;
187
-
188
         return (
83
         return (
189
             <div>
84
             <div>
190
-                { descriptionKey
191
-                    ? t(descriptionKey, descriptionArguments) : null }
192
-                { description || null }
85
+                {
86
+                    this._getDescription()
87
+                }
193
             </div>
88
             </div>
194
         );
89
         );
195
     }
90
     }
196
 
91
 
197
-    /**
198
-     * Calls back into {@code FlagGroup} to dismiss the notification.
199
-     *
200
-     * @private
201
-     * @returns {void}
202
-     */
203
-    _onDismissed() {
204
-        this.props.onDismissed(this.props.uid);
205
-    }
206
-
207
     /**
92
     /**
208
      * Opens the support page.
93
      * Opens the support page.
209
      *
94
      *

+ 145
- 0
react/features/notifications/components/NotificationsContainer.native.js Просмотреть файл

1
+// @flow
2
+
3
+import React from 'react';
4
+import { View } from 'react-native';
5
+import { connect } from 'react-redux';
6
+
7
+import {
8
+    isNarrowAspectRatio,
9
+    makeAspectRatioAware
10
+} from '../../base/responsive-ui';
11
+import { FILMSTRIP_SIZE, isFilmstripVisible } from '../../filmstrip';
12
+import { HANGUP_BUTTON_SIZE } from '../../toolbox';
13
+
14
+import AbstractNotificationsContainer, {
15
+    _abstractMapStateToProps,
16
+    type Props as AbstractProps
17
+} from './AbstractNotificationsContainer';
18
+import Notification from './Notification';
19
+import styles from './styles';
20
+
21
+type Props = AbstractProps & {
22
+
23
+    /**
24
+     * True if the {@code Filmstrip} is visible, false otherwise.
25
+     */
26
+    _filmstripVisible: boolean,
27
+
28
+    /**
29
+     * True if the {@ćode Toolbox} is visible, false otherwise.
30
+     */
31
+    _toolboxVisible: boolean
32
+};
33
+
34
+/**
35
+ * The margin of the container to be kept from other components.
36
+ */
37
+const CONTAINER_MARGIN = 10;
38
+
39
+/**
40
+ * Implements a React {@link Component} which displays notifications and handles
41
+ * automatic dismissmal after a notification is shown for a defined timeout
42
+ * period.
43
+ *
44
+ * @extends {Component}
45
+ */
46
+class NotificationsContainer extends AbstractNotificationsContainer<Props> {
47
+
48
+    /**
49
+     * Implements React's {@link Component#render()}.
50
+     *
51
+     * @inheritdoc
52
+     * @returns {ReactElement}
53
+     */
54
+    render() {
55
+        const { _notifications } = this.props;
56
+
57
+        if (!_notifications || !_notifications.length) {
58
+            return null;
59
+        }
60
+
61
+        return (
62
+            <View
63
+                pointerEvents = 'box-none'
64
+                style = { styles.notificationOverlay }>
65
+                <View
66
+                    pointerEvents = 'box-none'
67
+                    style = { [
68
+                        styles.notificationContainer,
69
+                        this._getContainerStyle()
70
+                    ] }>
71
+                    {
72
+                        _notifications.map(notification => {
73
+                            const { props, uid } = notification;
74
+
75
+                            return (
76
+                                <Notification
77
+                                    { ...props }
78
+                                    key = { uid }
79
+                                    onDismissed = { this._onDismissed }
80
+                                    uid = { uid } />
81
+
82
+                            );
83
+                        })
84
+                    }
85
+                </View>
86
+            </View>
87
+        );
88
+    }
89
+
90
+    /**
91
+     * Generates a style object that is to be used for the notification
92
+     * container.
93
+     *
94
+     * @private
95
+     * @returns {?Object}
96
+     */
97
+    _getContainerStyle() {
98
+        const { _filmstripVisible, _toolboxVisible } = this.props;
99
+
100
+        // The filmstrip only affects the position if we're on a narrow view.
101
+        const _narrow = isNarrowAspectRatio(this);
102
+
103
+        let bottom = 0;
104
+        let right = 0;
105
+
106
+        // The container needs additional distance from bottom when the
107
+        // filmstrip or the toolbox is visible.
108
+        _filmstripVisible && !_narrow && (right += FILMSTRIP_SIZE);
109
+        _filmstripVisible && _narrow && (bottom += FILMSTRIP_SIZE);
110
+        _toolboxVisible && (bottom += HANGUP_BUTTON_SIZE);
111
+
112
+        bottom += CONTAINER_MARGIN;
113
+
114
+        return {
115
+            bottom,
116
+            right
117
+        };
118
+    }
119
+
120
+    _onDismissed: number => void;
121
+}
122
+
123
+/**
124
+ * Maps (parts of) the Redux state to the associated NotificationsContainer's
125
+ * props.
126
+ *
127
+ * @param {Object} state - The Redux state.
128
+ * @private
129
+ * @returns {{
130
+ *     _filmstripVisible: boolean,
131
+ *     _notifications: Array,
132
+ *     _showNotifications: boolean,
133
+ *     _toolboxVisible: boolean
134
+ * }}
135
+ */
136
+export function _mapStateToProps(state: Object) {
137
+    return {
138
+        ..._abstractMapStateToProps(state),
139
+        _filmstripVisible: isFilmstripVisible(state),
140
+        _toolboxVisible: state['features/toolbox'].visible
141
+    };
142
+}
143
+
144
+export default connect(_mapStateToProps)(
145
+    makeAspectRatioAware(NotificationsContainer));

+ 11
- 142
react/features/notifications/components/NotificationsContainer.web.js Просмотреть файл

1
+// @flow
2
+
1
 import { FlagGroup } from '@atlaskit/flag';
3
 import { FlagGroup } from '@atlaskit/flag';
2
-import PropTypes from 'prop-types';
3
-import React, { Component } from 'react';
4
+import React from 'react';
4
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
5
 
6
 
6
-import { hideNotification } from '../actions';
7
-
8
-import { Notification } from './';
7
+import AbstractNotificationsContainer, {
8
+    _abstractMapStateToProps as _mapStateToProps,
9
+    type Props
10
+} from './AbstractNotificationsContainer';
11
+import Notification from './Notification';
9
 
12
 
10
 /**
13
 /**
11
  * Implements a React {@link Component} which displays notifications and handles
14
  * Implements a React {@link Component} which displays notifications and handles
14
  *
17
  *
15
  * @extends {Component}
18
  * @extends {Component}
16
  */
19
  */
17
-class NotificationsContainer extends Component {
18
-    /**
19
-     * {@code NotificationsContainer} component's property types.
20
-     *
21
-     * @static
22
-     */
23
-    static propTypes = {
24
-        /**
25
-         * The notifications to be displayed, with the first index being the
26
-         * notification at the top and the rest shown below it in order.
27
-         */
28
-        _notifications: PropTypes.array,
29
-
30
-        /**
31
-         * Whether or not notifications should be displayed at all. If not,
32
-         * notifications will be dismissed immediately.
33
-         */
34
-        _showNotifications: PropTypes.bool,
35
-
36
-        /**
37
-         * Invoked to update the redux store in order to remove notifications.
38
-         */
39
-        dispatch: PropTypes.func
40
-    };
41
-
42
-    /**
43
-     * Initializes a new {@code NotificationsContainer} instance.
44
-     *
45
-     * @param {Object} props - The read-only React Component props with which
46
-     * the new instance is to be initialized.
47
-     */
48
-    constructor(props) {
49
-        super(props);
50
-
51
-        /**
52
-         * The timeout set for automatically dismissing a displayed
53
-         * notification. This value is set on the instance and not state to
54
-         * avoid additional re-renders.
55
-         *
56
-         * @type {number|null}
57
-         */
58
-        this._notificationDismissTimeout = null;
59
-
60
-        // Bind event handlers so they are only bound once for every instance.
61
-        this._onDismissed = this._onDismissed.bind(this);
62
-    }
63
-
64
-    /**
65
-     * Sets a timeout if the currently displayed notification has changed.
66
-     *
67
-     * @inheritdoc
68
-     * returns {void}
69
-     */
70
-    componentDidUpdate() {
71
-        const { _notifications, _showNotifications } = this.props;
72
-
73
-        if (_notifications.length) {
74
-            const notification = _notifications[0];
75
-
76
-            if (!_showNotifications || this._notificationDismissTimeout) {
77
-
78
-                // No-op because there should already be a notification that
79
-                // is waiting for dismissal.
80
-            } else {
81
-                const { timeout, uid } = notification;
82
-
83
-                this._notificationDismissTimeout = setTimeout(() => {
84
-                    // Perform a no-op if a timeout is not specified.
85
-                    if (Number.isInteger(timeout)) {
86
-                        this._onDismissed(uid);
87
-                    }
88
-                }, timeout);
89
-            }
90
-        }
91
-    }
92
-
93
-    /**
94
-     * Clear any dismissal timeout that is still active.
95
-     *
96
-     * @inheritdoc
97
-     * returns {void}
98
-     */
99
-    componentWillUnmount() {
100
-        clearTimeout(this._notificationDismissTimeout);
101
-    }
20
+class NotificationsContainer extends AbstractNotificationsContainer<Props> {
102
 
21
 
103
     /**
22
     /**
104
      * Implements React's {@link Component#render()}.
23
      * Implements React's {@link Component#render()}.
114
         );
33
         );
115
     }
34
     }
116
 
35
 
117
-    /**
118
-     * Emits an action to remove the notification from the redux store so it
119
-     * stops displaying.
120
-     *
121
-     * @param {number} flagUid - The id of the notification to be removed.
122
-     * @private
123
-     * @returns {void}
124
-     */
125
-    _onDismissed(flagUid) {
126
-        clearTimeout(this._notificationDismissTimeout);
127
-        this._notificationDismissTimeout = null;
128
-
129
-        this.props.dispatch(hideNotification(flagUid));
130
-    }
36
+    _onDismissed: number => void;
131
 
37
 
132
     /**
38
     /**
133
      * Renders notifications to display as ReactElements. An empty array will
39
      * Renders notifications to display as ReactElements. An empty array will
137
      * @returns {ReactElement[]}
43
      * @returns {ReactElement[]}
138
      */
44
      */
139
     _renderFlags() {
45
     _renderFlags() {
140
-        const { _notifications, _showNotifications } = this.props;
141
-
142
-        if (!_showNotifications) {
143
-            return [];
144
-        }
46
+        const { _notifications } = this.props;
145
 
47
 
146
         return _notifications.map(notification => {
48
         return _notifications.map(notification => {
147
             const { props, uid } = notification;
49
             const { props, uid } = notification;
161
     }
63
     }
162
 }
64
 }
163
 
65
 
164
-/**
165
- * Maps (parts of) the Redux state to the associated NotificationsContainer's
166
- * props.
167
- *
168
- * @param {Object} state - The Redux state.
169
- * @private
170
- * @returns {{
171
- *     _notifications: Array
172
- * }}
173
- */
174
-function _mapStateToProps(state) {
175
-    // TODO: Per existing behavior, notifications should not display when an
176
-    // overlay is visible. This logic for checking overlay display can likely be
177
-    // simplified.
178
-    const {
179
-        connectionEstablished,
180
-        haveToReload,
181
-        isMediaPermissionPromptVisible,
182
-        suspendDetected
183
-    } = state['features/overlay'];
184
-    const isAnyOverlayVisible = (connectionEstablished && haveToReload)
185
-        || isMediaPermissionPromptVisible
186
-        || suspendDetected
187
-        || state['features/base/jwt'].calleeInfoVisible;
188
-
189
-    const { enabled, notifications } = state['features/notifications'];
190
-
191
-    return {
192
-        _notifications: notifications,
193
-        _showNotifications: enabled && !isAnyOverlayVisible
194
-    };
195
-}
196
-
197
 export default connect(_mapStateToProps)(NotificationsContainer);
66
 export default connect(_mapStateToProps)(NotificationsContainer);

+ 115
- 0
react/features/notifications/components/styles.js Просмотреть файл

1
+// @flow
2
+
3
+import { StyleSheet } from 'react-native';
4
+
5
+import { BoxModel, createStyleSheet, ColorPalette } from '../../base/styles';
6
+
7
+/**
8
+ * The styles of the React {@code Components} of the feature notifications.
9
+ */
10
+export default createStyleSheet({
11
+
12
+    /**
13
+     * The content (left) column of the notification.
14
+     */
15
+    contentColumn: {
16
+        flex: 1,
17
+        flexDirection: 'column',
18
+        padding: BoxModel.padding
19
+    },
20
+
21
+    /**
22
+     * Test style of the notification.
23
+     */
24
+    contentText: {
25
+        color: ColorPalette.white
26
+    },
27
+
28
+    /**
29
+     * Dismiss icon style.
30
+     */
31
+    dismissIcon: {
32
+        alignSelf: 'center',
33
+        color: ColorPalette.white,
34
+        fontSize: 16,
35
+        padding: 1.5 * BoxModel.padding
36
+    },
37
+
38
+    /**
39
+     * Outermost view of a single notification.
40
+     */
41
+    notification: {
42
+        borderRadius: 5,
43
+        flexDirection: 'row',
44
+        marginTop: 0.5 * BoxModel.margin
45
+    },
46
+
47
+    /**
48
+     * Outermost container of a list of notifications.
49
+     */
50
+    notificationContainer: {
51
+        alignItems: 'flex-start',
52
+        bottom: 0,
53
+        left: 0,
54
+        padding: 2 * BoxModel.padding,
55
+        position: 'absolute',
56
+        right: 0
57
+    },
58
+
59
+    /**
60
+     * Wrapper for the message (without title).
61
+     */
62
+    notificationContent: {
63
+        flexDirection: 'column',
64
+        paddingVertical: 0.5 * BoxModel.padding
65
+    },
66
+
67
+    /**
68
+     * A full screen overlay to help to position the container.
69
+     */
70
+    notificationOverlay: {
71
+        ...StyleSheet.absoluteFillObject
72
+    },
73
+
74
+    /**
75
+     * The View containing the title.
76
+     */
77
+    notificationTitle: {
78
+        paddingVertical: 0.5 * BoxModel.padding
79
+    },
80
+
81
+    /**
82
+     * Background settings for different notification types.
83
+     */
84
+
85
+    notificationTypeError: {
86
+        backgroundColor: ColorPalette.R400
87
+    },
88
+
89
+    notificationTypeInfo: {
90
+        backgroundColor: ColorPalette.N500
91
+    },
92
+
93
+    notificationTypeNormal: {
94
+        // NOTE: Mobile has black background when the large video doesn't render
95
+        // a stream, so we avoid using black as the background of the normal
96
+        // type notifications.
97
+        backgroundColor: ColorPalette.N500
98
+    },
99
+
100
+    notificationTypeSuccess: {
101
+        backgroundColor: ColorPalette.G400
102
+    },
103
+
104
+    notificationTypeWarning: {
105
+        backgroundColor: ColorPalette.Y200
106
+    },
107
+
108
+    /**
109
+     * Title text style.
110
+     */
111
+    titleText: {
112
+        color: ColorPalette.white,
113
+        fontWeight: 'bold'
114
+    }
115
+});

+ 1
- 0
react/features/notifications/index.js Просмотреть файл

2
 export * from './actionTypes';
2
 export * from './actionTypes';
3
 export * from './components';
3
 export * from './components';
4
 
4
 
5
+import './middleware';
5
 import './reducer';
6
 import './reducer';

+ 19
- 0
react/features/notifications/middleware.js Просмотреть файл

1
+/* @flow */
2
+
3
+import { getCurrentConference } from '../base/conference';
4
+import { StateListenerRegistry } from '../base/redux';
5
+
6
+import { clearNotifications } from './actions';
7
+
8
+/**
9
+ * StateListenerRegistry provides a reliable way to detect the leaving of a
10
+ * conference, where we need to clean up the notifications.
11
+ */
12
+StateListenerRegistry.register(
13
+    /* selector */ state => getCurrentConference(state),
14
+    /* listener */ (conference, { dispatch }) => {
15
+        if (!conference) {
16
+            dispatch(clearNotifications());
17
+        }
18
+    }
19
+);

+ 6
- 0
react/features/notifications/reducer.js Просмотреть файл

1
 import { ReducerRegistry } from '../base/redux';
1
 import { ReducerRegistry } from '../base/redux';
2
 
2
 
3
 import {
3
 import {
4
+    CLEAR_NOTIFICATIONS,
4
     HIDE_NOTIFICATION,
5
     HIDE_NOTIFICATION,
5
     SET_NOTIFICATIONS_ENABLED,
6
     SET_NOTIFICATIONS_ENABLED,
6
     SHOW_NOTIFICATION
7
     SHOW_NOTIFICATION
28
 ReducerRegistry.register('features/notifications',
29
 ReducerRegistry.register('features/notifications',
29
     (state = DEFAULT_STATE, action) => {
30
     (state = DEFAULT_STATE, action) => {
30
         switch (action.type) {
31
         switch (action.type) {
32
+        case CLEAR_NOTIFICATIONS:
33
+            return {
34
+                ...state,
35
+                notifications: []
36
+            };
31
         case HIDE_NOTIFICATION:
37
         case HIDE_NOTIFICATION:
32
             return {
38
             return {
33
                 ...state,
39
                 ...state,

+ 2
- 88
react/features/overlay/components/OverlayContainer.js Просмотреть файл

3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 import { connect } from 'react-redux';
4
 import { connect } from 'react-redux';
5
 
5
 
6
-import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
7
-import PageReloadOverlay from './PageReloadOverlay';
8
-import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
9
-import SuspendedOverlay from './SuspendedOverlay';
10
-import UserMediaPermissionsFilmstripOnlyOverlay
11
-    from './UserMediaPermissionsFilmstripOnlyOverlay';
12
-import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
6
+import { getOverlayToRender } from '../functions';
13
 
7
 
14
 declare var interfaceConfig: Object;
8
 declare var interfaceConfig: Object;
15
 
9
 
16
-/**
17
- * The lazily-initialized list of overlay React {@link Component} types used The
18
- * user interface is filmstrip-only.
19
- *
20
- * XXX The value is meant to be compile-time defined so it does not contradict
21
- * our coding style to not have global values that are runtime defined and
22
- * merely works around side effects of circular imports.
23
- *
24
- * @type Array
25
- */
26
-let _filmstripOnlyOverlays;
27
-
28
-/**
29
- * The lazily-initialized list of overlay React {@link Component} types used The
30
- * user interface is not filmstrip-only.
31
- *
32
- * XXX The value is meant to be compile-time defined so it does not contradict
33
- * our coding style to not have global values that are runtime defined and
34
- * merely works around side effects of circular imports.
35
- *
36
- * @type Array
37
- */
38
-let _nonFilmstripOnlyOverlays;
39
-
40
 /**
10
 /**
41
  * The type of the React {@link Component} props of {@code OverlayContainer}.
11
  * The type of the React {@link Component} props of {@code OverlayContainer}.
42
  */
12
  */
68
     }
38
     }
69
 }
39
 }
70
 
40
 
71
-/**
72
- * Returns the list of overlay React {@link Component} types to be rendered by
73
- * {@code OverlayContainer}. The list is lazily initialized the first time it is
74
- * required in order to works around side effects of circular imports.
75
- *
76
- * @param {boolean} filmstripOnly - The indicator which determines whether the
77
- * user interface is filmstrip-only.
78
- * @returns {Array} The list of overlay React {@code Component} types to be
79
- * rendered by {@code OverlayContainer}.
80
- */
81
-function _getOverlays(filmstripOnly) {
82
-    let overlays;
83
-
84
-    if (filmstripOnly) {
85
-        if (!(overlays = _filmstripOnlyOverlays)) {
86
-            overlays = _filmstripOnlyOverlays = [
87
-                PageReloadFilmstripOnlyOverlay,
88
-                SuspendedFilmstripOnlyOverlay,
89
-                UserMediaPermissionsFilmstripOnlyOverlay
90
-            ];
91
-        }
92
-    } else if (!(overlays = _nonFilmstripOnlyOverlays)) {
93
-        overlays = _nonFilmstripOnlyOverlays = [
94
-            PageReloadOverlay
95
-        ];
96
-
97
-        // Mobile only has a PageReloadOverlay.
98
-        if (navigator.product !== 'ReactNative') {
99
-            overlays.push(...[
100
-                SuspendedOverlay,
101
-                UserMediaPermissionsOverlay
102
-            ]);
103
-        }
104
-    }
105
-
106
-    return overlays;
107
-}
108
-
109
 /**
41
 /**
110
  * Maps (parts of) the redux state to the associated {@code OverlayContainer}'s
42
  * Maps (parts of) the redux state to the associated {@code OverlayContainer}'s
111
  * props.
43
  * props.
117
  * }}
49
  * }}
118
  */
50
  */
119
 function _mapStateToProps(state) {
51
 function _mapStateToProps(state) {
120
-    // XXX In the future interfaceConfig is expected to not be a global variable
121
-    // but a redux state like config. Hence, the variable filmStripOnly
122
-    // naturally belongs here in preparation for the future.
123
-    const filmstripOnly
124
-        = typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly;
125
-    let overlay;
126
-
127
-    for (const o of _getOverlays(filmstripOnly)) {
128
-        // react-i18n / react-redux wrap components and thus we cannot access
129
-        // the wrapped component's static methods directly.
130
-        const component = o.WrappedComponent || o;
131
-
132
-        if (component.needsRender(state)) {
133
-            overlay = o;
134
-            break;
135
-        }
136
-    }
137
-
138
     return {
52
     return {
139
         /**
53
         /**
140
          * The React {@link Component} type of overlay to be rendered by the
54
          * The React {@link Component} type of overlay to be rendered by the
141
          * associated {@code OverlayContainer}.
55
          * associated {@code OverlayContainer}.
142
          */
56
          */
143
-        overlay
57
+        overlay: getOverlayToRender(state)
144
     };
58
     };
145
 }
59
 }
146
 
60
 

+ 14
- 0
react/features/overlay/components/index.js Просмотреть файл

1
 export { default as OverlayContainer } from './OverlayContainer';
1
 export { default as OverlayContainer } from './OverlayContainer';
2
+export {
3
+    default as PageReloadFilmstripOnlyOverlay
4
+} from './PageReloadFilmstripOnlyOverlay';
5
+export { default as PageReloadOverlay } from './PageReloadOverlay';
6
+export {
7
+    default as SuspendedFilmstripOnlyOverlay
8
+} from './SuspendedFilmstripOnlyOverlay';
9
+export { default as SuspendedOverlay } from './SuspendedOverlay';
10
+export {
11
+    default as UserMediaPermissionsFilmstripOnlyOverlay
12
+} from './UserMediaPermissionsFilmstripOnlyOverlay';
13
+export {
14
+    default as UserMediaPermissionsOverlay
15
+} from './UserMediaPermissionsOverlay';

+ 66
- 0
react/features/overlay/functions.js Просмотреть файл

1
+// @flow
2
+
3
+import {
4
+    PageReloadFilmstripOnlyOverlay,
5
+    PageReloadOverlay,
6
+    SuspendedFilmstripOnlyOverlay,
7
+    SuspendedOverlay,
8
+    UserMediaPermissionsFilmstripOnlyOverlay,
9
+    UserMediaPermissionsOverlay
10
+} from './components';
11
+
12
+declare var interfaceConfig: Object;
13
+
14
+/**
15
+ * Returns the list of available overlays that might be rendered.
16
+ *
17
+ * @private
18
+ * @returns {Array<?React$ComponentType<*>>}
19
+ */
20
+function _getOverlays() {
21
+    const filmstripOnly
22
+        = typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly;
23
+    let overlays;
24
+
25
+    if (filmstripOnly) {
26
+        overlays = [
27
+            PageReloadFilmstripOnlyOverlay,
28
+            SuspendedFilmstripOnlyOverlay,
29
+            UserMediaPermissionsFilmstripOnlyOverlay
30
+        ];
31
+    } else {
32
+        overlays = [
33
+            PageReloadOverlay
34
+        ];
35
+    }
36
+
37
+    // Mobile only has a PageReloadOverlay.
38
+    if (navigator.product !== 'ReactNative') {
39
+        overlays.push(...[
40
+            SuspendedOverlay,
41
+            UserMediaPermissionsOverlay
42
+        ]);
43
+    }
44
+
45
+    return overlays;
46
+}
47
+
48
+/**
49
+ * Returns the overlay to be currently rendered.
50
+ *
51
+ * @param {Object} state - The Redux state.
52
+ * @returns {?React$ComponentType<*>}
53
+ */
54
+export function getOverlayToRender(state: Object) {
55
+    for (const overlay of _getOverlays()) {
56
+        // react-i18n / react-redux wrap components and thus we cannot access
57
+        // the wrapped component's static methods directly.
58
+        const component = overlay.WrappedComponent || overlay;
59
+
60
+        if (component.needsRender(state)) {
61
+            return overlay;
62
+        }
63
+    }
64
+
65
+    return undefined;
66
+}

+ 1
- 0
react/features/overlay/index.js Просмотреть файл

1
 export * from './actions';
1
 export * from './actions';
2
 export * from './components';
2
 export * from './components';
3
+export * from './functions';
3
 
4
 
4
 import './middleware';
5
 import './middleware';
5
 import './reducer';
6
 import './reducer';

+ 4
- 2
react/features/toolbox/components/native/styles.js Просмотреть файл

3
 
3
 
4
 import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles';
4
 import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles';
5
 
5
 
6
+import { HANGUP_BUTTON_SIZE } from '../../constants';
7
+
6
 // Toolbox, toolbar:
8
 // Toolbox, toolbar:
7
 
9
 
8
 /**
10
 /**
44
         ...toolbarButton,
46
         ...toolbarButton,
45
         backgroundColor: ColorPalette.red,
47
         backgroundColor: ColorPalette.red,
46
         borderRadius: 30,
48
         borderRadius: 30,
47
-        height: 60,
48
-        width: 60
49
+        height: HANGUP_BUTTON_SIZE,
50
+        width: HANGUP_BUTTON_SIZE
49
     },
51
     },
50
 
52
 
51
     /**
53
     /**

+ 7
- 0
react/features/toolbox/constants.js Просмотреть файл

1
+// @flow
2
+
3
+/**
4
+ * The size of the hangup button. As that is the largest button, it defines
5
+ * the size of the {@code ToolBox}, so other components may relate to that.
6
+ */
7
+export const HANGUP_BUTTON_SIZE = 60;

+ 1
- 0
react/features/toolbox/index.js Просмотреть файл

1
 export * from './actions';
1
 export * from './actions';
2
 export * from './actionTypes';
2
 export * from './actionTypes';
3
 export * from './components';
3
 export * from './components';
4
+export * from './constants';
4
 export * from './functions';
5
 export * from './functions';
5
 
6
 
6
 import './middleware';
7
 import './middleware';

Загрузка…
Отмена
Сохранить