Parcourir la source

added recent list

j8
Ritwik Heda il y a 6 ans
Parent
révision
046b06e436
32 fichiers modifiés avec 1302 ajouts et 608 suppressions
  1. 19
    0
      conference.js
  2. 43
    0
      css/_navigate_section_list.scss
  3. 2
    1
      css/main.scss
  4. 8
    1
      interface_config.js
  5. 6
    3
      lang/main.json
  6. 1
    0
      package.json
  7. 15
    1
      react/features/base/i18n/dateUtil.js
  8. 77
    0
      react/features/base/react/Types.js
  9. 229
    0
      react/features/base/react/components/NavigateSectionList.js
  10. 1
    0
      react/features/base/react/components/index.js
  11. 0
    346
      react/features/base/react/components/native/NavigateSectionList.js
  12. 51
    0
      react/features/base/react/components/native/NavigateSectionListEmptyComponent.js
  13. 145
    0
      react/features/base/react/components/native/NavigateSectionListItem.js
  14. 40
    0
      react/features/base/react/components/native/NavigateSectionListSectionHeader.js
  15. 91
    0
      react/features/base/react/components/native/SectionList.js
  16. 7
    1
      react/features/base/react/components/native/index.js
  17. 0
    0
      react/features/base/react/components/web/NavigateSectionListEmptyComponent.js
  18. 73
    0
      react/features/base/react/components/web/NavigateSectionListItem.js
  19. 35
    0
      react/features/base/react/components/web/NavigateSectionListSectionHeader.js
  20. 92
    0
      react/features/base/react/components/web/SectionList.js
  21. 7
    0
      react/features/base/react/components/web/index.js
  22. 5
    0
      react/features/base/storage/middleware.js
  23. 109
    0
      react/features/recent-list/components/RecentList.js
  24. 0
    234
      react/features/recent-list/components/RecentList.native.js
  25. 7
    0
      react/features/recent-list/featureFlag.native.js
  26. 10
    0
      react/features/recent-list/featureFlag.web.js
  27. 81
    0
      react/features/recent-list/functions.all.js
  28. 62
    0
      react/features/recent-list/functions.native.js
  29. 34
    0
      react/features/recent-list/functions.web.js
  30. 35
    8
      react/features/recent-list/middleware.js
  31. 14
    12
      react/features/recent-list/reducer.js
  32. 3
    1
      react/features/welcome/components/WelcomePage.web.js

+ 19
- 0
conference.js Voir le fichier

@@ -35,8 +35,10 @@ import {
35 35
     conferenceJoined,
36 36
     conferenceLeft,
37 37
     conferenceWillJoin,
38
+    conferenceWillLeave,
38 39
     dataChannelOpened,
39 40
     EMAIL_COMMAND,
41
+    getCurrentConference,
40 42
     lockStateChanged,
41 43
     onStartMutedPolicyChanged,
42 44
     p2pStatusChanged,
@@ -305,6 +307,10 @@ class ConferenceConnector {
305 307
     _onConferenceFailed(err, ...params) {
306 308
         APP.store.dispatch(conferenceFailed(room, err, ...params));
307 309
         logger.error('CONFERENCE FAILED:', err, ...params);
310
+        const state = APP.store.getState();
311
+
312
+        // The conference we have already joined or are joining.
313
+        const conference = getCurrentConference(state);
308 314
 
309 315
         switch (err) {
310 316
         case JitsiConferenceErrors.CONNECTION_ERROR: {
@@ -375,6 +381,7 @@ class ConferenceConnector {
375 381
             // FIXME the conference should be stopped by the library and not by
376 382
             // the app. Both the errors above are unrecoverable from the library
377 383
             // perspective.
384
+            APP.store.dispatch(conferenceWillLeave(conference));
378 385
             room.leave().then(() => connection.disconnect());
379 386
             break;
380 387
 
@@ -468,6 +475,12 @@ function _connectionFailedHandler(error) {
468 475
             JitsiConnectionEvents.CONNECTION_FAILED,
469 476
             _connectionFailedHandler);
470 477
         if (room) {
478
+            const state = APP.store.getState();
479
+
480
+            // The conference we have already joined or are joining.
481
+            const conference = getCurrentConference(state);
482
+
483
+            APP.store.dispatch(conferenceWillLeave(conference));
471 484
             room.leave();
472 485
         }
473 486
     }
@@ -2465,6 +2478,12 @@ export default {
2465 2478
      * requested
2466 2479
      */
2467 2480
     hangup(requestFeedback = false) {
2481
+        const state = APP.store.getState();
2482
+
2483
+        // The conference we have already joined or are joining.
2484
+        const conference = getCurrentConference(state);
2485
+
2486
+        APP.store.dispatch(conferenceWillLeave(conference));
2468 2487
         eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
2469 2488
         APP.UI.removeLocalMedia();
2470 2489
 

+ 43
- 0
css/_navigate_section_list.scss Voir le fichier

@@ -0,0 +1,43 @@
1
+%navigate-section-list-text {
2
+    width: 100%;
3
+    font-size: 14px;
4
+    line-height: 20px;
5
+    color: $welcomePageTitleColor;
6
+    text-align: left;
7
+    font-family: 'open_sanslight', Helvetica, sans-serif;
8
+}
9
+%navigate-section-list-tile-text {
10
+    @extend %navigate-section-list-text;
11
+    overflow: hidden;
12
+    text-overflow: ellipsis;
13
+    float: left;
14
+}
15
+.navigate-section-list-tile {
16
+    height: 90px;
17
+    width: 260px;
18
+    border-radius: 4px;
19
+    background-color: #1754A9;
20
+    margin-right: 8px;
21
+    padding: 16px;
22
+    display: inline-block;
23
+    box-sizing: border-box;
24
+    cursor: pointer;
25
+}
26
+.navigate-section-tile-body {
27
+    @extend %navigate-section-list-tile-text;
28
+    font-weight: normal;
29
+}
30
+.navigate-section-tile-title {
31
+    @extend %navigate-section-list-tile-text;
32
+    font-weight: bold;
33
+}
34
+.navigate-section-section-header {
35
+    @extend %navigate-section-list-text;
36
+    font-weight: bold;
37
+    margin-bottom: 16px;
38
+}
39
+.navigate-section-list {
40
+    position: relative;
41
+    margin-top: 36px;
42
+    margin-bottom: 36px;
43
+}

+ 2
- 1
css/main.scss Voir le fichier

@@ -78,4 +78,5 @@
78 78
 @import 'modals/invite/add-people';
79 79
 @import 'deep-linking/main';
80 80
 @import 'transcription-subtitles';
81
-/* Modules END */
81
+@import 'navigate_section_list';
82
+@import 'transcription-subtitles';

+ 8
- 1
interface_config.js Voir le fichier

@@ -163,7 +163,14 @@ var interfaceConfig = {
163 163
      *
164 164
      * @type {boolean}
165 165
      */
166
-    VIDEO_QUALITY_LABEL_DISABLED: false
166
+    VIDEO_QUALITY_LABEL_DISABLED: false,
167
+
168
+    /**
169
+     * If true, will display recent list
170
+     *
171
+     * @type {boolean}
172
+     */
173
+    RECENT_LIST_ENABLED: true
167 174
 
168 175
     /**
169 176
      * Specify custom URL for downloading android mobile app.

+ 6
- 3
lang/main.json Voir le fichier

@@ -626,9 +626,7 @@
626 626
         "permissionMessage": "The Calendar permission is required to see your meetings in the app."
627 627
     },
628 628
     "recentList": {
629
-        "today": "Today",
630
-        "yesterday": "Yesterday",
631
-        "earlier": "Earlier"
629
+        "joinPastMeeting": "Join A Past Meeting"
632 630
     },
633 631
     "sectionList": {
634 632
         "pullToRefresh": "Pull to refresh"
@@ -656,6 +654,11 @@
656 654
         "ignored": "Ignored",
657 655
         "expired": "Expired"
658 656
     },
657
+    "dateUtils": {
658
+        "today": "Today",
659
+        "yesterday": "Yesterday",
660
+        "earlier": "Earlier"
661
+    },
659 662
     "incomingCall": {
660 663
         "answer": "Answer",
661 664
         "audioCallTitle": "Incoming call",

+ 1
- 0
package.json Voir le fichier

@@ -51,6 +51,7 @@
51 51
     "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2be752fc88ff71e454c6b9178b21a33b59c53f41",
52 52
     "lodash": "4.17.4",
53 53
     "moment": "2.19.4",
54
+    "moment-duration-format": "2.2.2",
54 55
     "postis": "2.2.0",
55 56
     "prop-types": "15.6.0",
56 57
     "react": "16.3.1",

+ 15
- 1
react/features/base/i18n/dateUtil.js Voir le fichier

@@ -4,6 +4,9 @@ import moment from 'moment';
4 4
 
5 5
 import i18next from './i18next';
6 6
 
7
+// allows for moment durations to be formatted
8
+import 'moment-duration-format';
9
+
7 10
 // MomentJS uses static language bundle loading, so in order to support dynamic
8 11
 // language selection in the app we need to load all bundles that we support in
9 12
 // the app.
@@ -55,8 +58,19 @@ export function getLocalizedDurationFormatter(duration: number) {
55 58
     // states v2.19 so maybe locale on moment's duration was introduced in
56 59
     // between?
57 60
     //
61
+
62
+    // If the conference is under an hour long we want to display it without
63
+    // showing the hour and we want to include the hour if the conference is
64
+    // more than an hour long
65
+
66
+    // $FlowFixMe
67
+    if (moment.duration(duration).format('h') !== '0') {
68
+        // $FlowFixMe
69
+        return moment.duration(duration).format('h:mm:ss');
70
+    }
71
+
58 72
     // $FlowFixMe
59
-    return moment.duration(duration).locale(_getSupportedLocale());
73
+    return moment.duration(duration).format('mm:ss', { trim: false });
60 74
 }
61 75
 
62 76
 /**

+ 77
- 0
react/features/base/react/Types.js Voir le fichier

@@ -0,0 +1,77 @@
1
+// @flow
2
+/**
3
+ * item data for NavigateSectionList
4
+ */
5
+import type {
6
+    ComponentType,
7
+    Element
8
+} from 'react';
9
+
10
+export type Item = {
11
+
12
+    /**
13
+     * the color base of the avatar
14
+     */
15
+    colorBase: string,
16
+
17
+    /**
18
+     * Item title
19
+     */
20
+    title: string,
21
+
22
+    /**
23
+     * Item url
24
+     */
25
+    url: string,
26
+
27
+    /**
28
+     * lines[0] - date
29
+     * lines[1] - duration
30
+     * lines[2] - server name
31
+     */
32
+    lines: Array<string>
33
+}
34
+
35
+/**
36
+ * web implementation of section data for NavigateSectionList
37
+ */
38
+export type Section = {
39
+
40
+    /**
41
+     * section title
42
+     */
43
+    title: string,
44
+
45
+    /**
46
+     * unique key for the section
47
+     */
48
+    key?: string,
49
+
50
+    /**
51
+     * Array of items in the section
52
+     */
53
+    data: $ReadOnlyArray<Item>,
54
+
55
+    /**
56
+     * Optional properties added only to fix some flow errors thrown by React
57
+     * SectionList
58
+     */
59
+    ItemSeparatorComponent?: ?ComponentType<any>,
60
+
61
+    keyExtractor?: (item: Object) => string,
62
+
63
+    renderItem?: ?(info: Object) => ?Element<any>
64
+
65
+}
66
+
67
+/**
68
+ * native implementation of section data for NavigateSectionList
69
+ *
70
+ * When react-native's SectionList component parses through an array of sections
71
+ * it passes the section nested within the section property of another object
72
+ * to the renderSection method (on web for our own implementation of SectionList
73
+ * this nesting is not implemented as there is no need for nesting)
74
+ */
75
+export type SetionListSection = {
76
+    section: Section
77
+}

+ 229
- 0
react/features/base/react/components/NavigateSectionList.js Voir le fichier

@@ -0,0 +1,229 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { translate } from '../../i18n';
6
+
7
+import {
8
+    NavigateSectionListEmptyComponent,
9
+    NavigateSectionListItem,
10
+    NavigateSectionListSectionHeader,
11
+    SectionList
12
+} from './_';
13
+import type { Section } from '../Types';
14
+
15
+type Props = {
16
+
17
+    /**
18
+     * Indicates if the list is disabled or not.
19
+     */
20
+    disabled: boolean,
21
+
22
+    /**
23
+     * The translate function.
24
+     */
25
+    t: Function,
26
+
27
+    /**
28
+     * Function to be invoked when an item is pressed. The item's URL is passed.
29
+     */
30
+    onPress: Function,
31
+
32
+    /**
33
+     * Function to be invoked when pull-to-refresh is performed.
34
+     */
35
+    onRefresh: Function,
36
+
37
+    /**
38
+     * Function to override the rendered default empty list component.
39
+     */
40
+    renderListEmptyComponent: Function,
41
+
42
+    /**
43
+     * An array of sections
44
+     */
45
+    sections: Array<Section>
46
+};
47
+
48
+/**
49
+ * Implements a general section list to display items that have a URL property
50
+ * and navigates to (probably) meetings, such as the recent list or the meeting
51
+ * list components.
52
+ */
53
+class NavigateSectionList extends Component<Props> {
54
+    /**
55
+     * Creates an empty section object.
56
+     *
57
+     * @param {string} title - The title of the section.
58
+     * @param {string} key - The key of the section. It must be unique.
59
+     * @private
60
+     * @returns {Object}
61
+     */
62
+    static createSection(title, key) {
63
+        return {
64
+            data: [],
65
+            key,
66
+            title
67
+        };
68
+    }
69
+
70
+    /**
71
+     * Constructor of the NavigateSectionList component.
72
+     *
73
+     * @inheritdoc
74
+     */
75
+    constructor(props: Props) {
76
+        super(props);
77
+        this._getItemKey = this._getItemKey.bind(this);
78
+        this._onPress = this._onPress.bind(this);
79
+        this._onRefresh = this._onRefresh.bind(this);
80
+        this._renderItem = this._renderItem.bind(this);
81
+        this._renderListEmptyComponent
82
+            = this._renderListEmptyComponent.bind(this);
83
+        this._renderSectionHeader = this._renderSectionHeader.bind(this);
84
+    }
85
+
86
+    /**
87
+     * Implements React's Component.render.
88
+     * Note: we don't use the refreshing value yet, because refreshing of these
89
+     * lists is super quick, no need to complicate the code - yet.
90
+     *
91
+     * @inheritdoc
92
+     */
93
+    render() {
94
+        const {
95
+            renderListEmptyComponent = this._renderListEmptyComponent,
96
+            sections
97
+        } = this.props;
98
+
99
+        return (
100
+            <SectionList
101
+                ListEmptyComponent = { renderListEmptyComponent }
102
+                keyExtractor = { this._getItemKey }
103
+                onItemClick = { this.props.onPress }
104
+                onRefresh = { this._onRefresh }
105
+                refreshing = { false }
106
+                renderItem = { this._renderItem }
107
+                renderSectionHeader = { this._renderSectionHeader }
108
+                sections = { sections } />
109
+        );
110
+    }
111
+
112
+    _getItemKey: (Object, number) => string;
113
+
114
+    /**
115
+     * Generates a unique id to every item.
116
+     *
117
+     * @param {Object} item - The item.
118
+     * @param {number} index - The item index.
119
+     * @private
120
+     * @returns {string}
121
+     */
122
+    _getItemKey(item, index) {
123
+        return `${index}-${item.key}`;
124
+    }
125
+
126
+    _onPress: string => Function;
127
+
128
+    /**
129
+     * Returns a function that is used in the onPress callback of the items.
130
+     *
131
+     * @param {string} url - The URL of the item to navigate to.
132
+     * @private
133
+     * @returns {Function}
134
+     */
135
+    _onPress(url) {
136
+        return () => {
137
+            const { disabled, onPress } = this.props;
138
+
139
+            !disabled && url && typeof onPress === 'function' && onPress(url);
140
+        };
141
+    }
142
+
143
+    _onRefresh: () => void;
144
+
145
+    /**
146
+     * Invokes the onRefresh callback if present.
147
+     *
148
+     * @private
149
+     * @returns {void}
150
+     */
151
+    _onRefresh() {
152
+        const { onRefresh } = this.props;
153
+
154
+        if (typeof onRefresh === 'function') {
155
+            onRefresh();
156
+        }
157
+    }
158
+
159
+    _renderItem: Object => Object;
160
+
161
+    /**
162
+     * Renders a single item in the list.
163
+     *
164
+     * @param {Object} listItem - The item to render.
165
+     * @param {string} key - The item needed for rendering using map on web.
166
+     * @private
167
+     * @returns {Component}
168
+     */
169
+    _renderItem(listItem, key = '') {
170
+        const { item } = listItem;
171
+        const { url } = item;
172
+
173
+        // XXX The value of title cannot be undefined; otherwise, react-native
174
+        // will throw a TypeError: Cannot read property of undefined. While it's
175
+        // difficult to get an undefined title and very likely requires the
176
+        // execution of incorrect source code, it is undesirable to break the
177
+        // whole app because of an undefined string.
178
+        if (typeof item.title === 'undefined') {
179
+            return null;
180
+        }
181
+
182
+        return (
183
+            <NavigateSectionListItem
184
+                item = { item }
185
+                key = { key }
186
+                onPress = { this._onPress(url) } />
187
+        );
188
+    }
189
+
190
+    _renderListEmptyComponent: () => Object;
191
+
192
+    /**
193
+     * Renders a component to display when the list is empty.
194
+     *
195
+     * @param {Object} section - The section being rendered.
196
+     * @private
197
+     * @returns {React$Node}
198
+     */
199
+    _renderListEmptyComponent() {
200
+        const { t, onRefresh } = this.props;
201
+
202
+        if (typeof onRefresh === 'function') {
203
+            return (
204
+                <NavigateSectionListEmptyComponent
205
+                    t = { t } />
206
+            );
207
+        }
208
+
209
+        return null;
210
+    }
211
+
212
+    _renderSectionHeader: Object => Object;
213
+
214
+    /**
215
+     * Renders a section header.
216
+     *
217
+     * @param {Object} section - The section being rendered.
218
+     * @private
219
+     * @returns {React$Node}
220
+     */
221
+    _renderSectionHeader(section) {
222
+        return (
223
+            <NavigateSectionListSectionHeader
224
+                section = { section } />
225
+        );
226
+    }
227
+}
228
+
229
+export default translate(NavigateSectionList);

+ 1
- 0
react/features/base/react/components/index.js Voir le fichier

@@ -1 +1,2 @@
1 1
 export * from './_';
2
+export { default as NavigateSectionList } from './NavigateSectionList';

+ 0
- 346
react/features/base/react/components/native/NavigateSectionList.js Voir le fichier

@@ -1,346 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-import {
5
-    SafeAreaView,
6
-    SectionList,
7
-    Text,
8
-    TouchableHighlight,
9
-    View
10
-} from 'react-native';
11
-
12
-import { Icon } from '../../../font-icons';
13
-import { translate } from '../../../i18n';
14
-
15
-import styles, { UNDERLAY_COLOR } from './styles';
16
-
17
-type Props = {
18
-
19
-    /**
20
-     * Indicates if the list is disabled or not.
21
-     */
22
-    disabled: boolean,
23
-
24
-    /**
25
-     * The translate function.
26
-     */
27
-    t: Function,
28
-
29
-    /**
30
-     * Function to be invoked when an item is pressed. The item's URL is passed.
31
-     */
32
-    onPress: Function,
33
-
34
-    /**
35
-     * Function to be invoked when pull-to-refresh is performed.
36
-     */
37
-    onRefresh: Function,
38
-
39
-    /**
40
-     * Function to override the rendered default empty list component.
41
-     */
42
-    renderListEmptyComponent: Function,
43
-
44
-    /**
45
-     * Sections to be rendered in the following format:
46
-     *
47
-     * [
48
-     *   {
49
-     *     title: string,               <- section title
50
-     *     key: string,                 <- unique key for the section
51
-     *     data: [                      <- Array of items in the section
52
-     *       {
53
-     *         colorBase: string,       <- the color base of the avatar
54
-     *         title: string,           <- item title
55
-     *         url: string,             <- item url
56
-     *         lines: Array<string>     <- additional lines to be rendered
57
-     *       }
58
-     *     ]
59
-     *   }
60
-     * ]
61
-     */
62
-    sections: Array<Object>
63
-};
64
-
65
-/**
66
- * Implements a general section list to display items that have a URL property
67
- * and navigates to (probably) meetings, such as the recent list or the meeting
68
- * list components.
69
- */
70
-class NavigateSectionList extends Component<Props> {
71
-    /**
72
-     * Creates an empty section object.
73
-     *
74
-     * @param {string} title - The title of the section.
75
-     * @param {string} key - The key of the section. It must be unique.
76
-     * @private
77
-     * @returns {Object}
78
-     */
79
-    static createSection(title, key) {
80
-        return {
81
-            data: [],
82
-            key,
83
-            title
84
-        };
85
-    }
86
-
87
-    /**
88
-     * Constructor of the NavigateSectionList component.
89
-     *
90
-     * @inheritdoc
91
-     */
92
-    constructor(props: Props) {
93
-        super(props);
94
-
95
-        this._getAvatarColor = this._getAvatarColor.bind(this);
96
-        this._getItemKey = this._getItemKey.bind(this);
97
-        this._onPress = this._onPress.bind(this);
98
-        this._onRefresh = this._onRefresh.bind(this);
99
-        this._renderItem = this._renderItem.bind(this);
100
-        this._renderItemLine = this._renderItemLine.bind(this);
101
-        this._renderItemLines = this._renderItemLines.bind(this);
102
-        this._renderListEmptyComponent
103
-            = this._renderListEmptyComponent.bind(this);
104
-        this._renderSection = this._renderSection.bind(this);
105
-    }
106
-
107
-    /**
108
-     * Implements React's Component.render.
109
-     * Note: we don't use the refreshing value yet, because refreshing of these
110
-     * lists is super quick, no need to complicate the code - yet.
111
-     *
112
-     * @inheritdoc
113
-     */
114
-    render() {
115
-        const {
116
-            renderListEmptyComponent = this._renderListEmptyComponent,
117
-            sections
118
-        } = this.props;
119
-
120
-        return (
121
-            <SafeAreaView
122
-                style = { styles.container } >
123
-                <SectionList
124
-                    ListEmptyComponent = { renderListEmptyComponent }
125
-                    keyExtractor = { this._getItemKey }
126
-                    onRefresh = { this._onRefresh }
127
-                    refreshing = { false }
128
-                    renderItem = { this._renderItem }
129
-                    renderSectionHeader = { this._renderSection }
130
-                    sections = { sections }
131
-                    style = { styles.list } />
132
-            </SafeAreaView>
133
-        );
134
-    }
135
-
136
-    _getAvatarColor: string => Object;
137
-
138
-    /**
139
-     * Returns a style (color) based on the string that determines the color of
140
-     * the avatar.
141
-     *
142
-     * @param {string} colorBase - The string that is the base of the color.
143
-     * @private
144
-     * @returns {Object}
145
-     */
146
-    _getAvatarColor(colorBase) {
147
-        if (!colorBase) {
148
-            return null;
149
-        }
150
-
151
-        let nameHash = 0;
152
-
153
-        for (let i = 0; i < colorBase.length; i++) {
154
-            nameHash += colorBase.codePointAt(i);
155
-        }
156
-
157
-        return styles[`avatarColor${(nameHash % 5) + 1}`];
158
-    }
159
-
160
-    _getItemKey: (Object, number) => string;
161
-
162
-    /**
163
-     * Generates a unique id to every item.
164
-     *
165
-     * @param {Object} item - The item.
166
-     * @param {number} index - The item index.
167
-     * @private
168
-     * @returns {string}
169
-     */
170
-    _getItemKey(item, index) {
171
-        return `${index}-${item.key}`;
172
-    }
173
-
174
-    _onPress: string => Function;
175
-
176
-    /**
177
-     * Returns a function that is used in the onPress callback of the items.
178
-     *
179
-     * @param {string} url - The URL of the item to navigate to.
180
-     * @private
181
-     * @returns {Function}
182
-     */
183
-    _onPress(url) {
184
-        return () => {
185
-            const { disabled, onPress } = this.props;
186
-
187
-            !disabled && url && typeof onPress === 'function' && onPress(url);
188
-        };
189
-    }
190
-
191
-    _onRefresh: () => void;
192
-
193
-    /**
194
-     * Invokes the onRefresh callback if present.
195
-     *
196
-     * @private
197
-     * @returns {void}
198
-     */
199
-    _onRefresh() {
200
-        const { onRefresh } = this.props;
201
-
202
-        if (typeof onRefresh === 'function') {
203
-            onRefresh();
204
-        }
205
-    }
206
-
207
-    _renderItem: Object => Object;
208
-
209
-    /**
210
-     * Renders a single item in the list.
211
-     *
212
-     * @param {Object} listItem - The item to render.
213
-     * @private
214
-     * @returns {Component}
215
-     */
216
-    _renderItem(listItem) {
217
-        const { item: { colorBase, lines, title, url } } = listItem;
218
-
219
-        // XXX The value of title cannot be undefined; otherwise, react-native
220
-        // will throw a TypeError: Cannot read property of undefined. While it's
221
-        // difficult to get an undefined title and very likely requires the
222
-        // execution of incorrect source code, it is undesirable to break the
223
-        // whole app because of an undefined string.
224
-        if (typeof title === 'undefined') {
225
-            return null;
226
-        }
227
-
228
-        return (
229
-            <TouchableHighlight
230
-                onPress = { this._onPress(url) }
231
-                underlayColor = { UNDERLAY_COLOR }>
232
-                <View style = { styles.listItem }>
233
-                    <View style = { styles.avatarContainer } >
234
-                        <View
235
-                            style = { [
236
-                                styles.avatar,
237
-                                this._getAvatarColor(colorBase)
238
-                            ] } >
239
-                            <Text style = { styles.avatarContent }>
240
-                                { title.substr(0, 1).toUpperCase() }
241
-                            </Text>
242
-                        </View>
243
-                    </View>
244
-                    <View style = { styles.listItemDetails }>
245
-                        <Text
246
-                            numberOfLines = { 1 }
247
-                            style = { [
248
-                                styles.listItemText,
249
-                                styles.listItemTitle
250
-                            ] }>
251
-                            { title }
252
-                        </Text>
253
-                        { this._renderItemLines(lines) }
254
-                    </View>
255
-                </View>
256
-            </TouchableHighlight>
257
-        );
258
-    }
259
-
260
-    _renderItemLine: (string, number) => React$Node;
261
-
262
-    /**
263
-     * Renders a single line from the additional lines.
264
-     *
265
-     * @param {string} line - The line text.
266
-     * @param {number} index - The index of the line.
267
-     * @private
268
-     * @returns {React$Node}
269
-     */
270
-    _renderItemLine(line, index) {
271
-        if (!line) {
272
-            return null;
273
-        }
274
-
275
-        return (
276
-            <Text
277
-                key = { index }
278
-                numberOfLines = { 1 }
279
-                style = { styles.listItemText }>
280
-                { line }
281
-            </Text>
282
-        );
283
-    }
284
-
285
-    _renderItemLines: Array<string> => Array<React$Node>;
286
-
287
-    /**
288
-     * Renders the additional item lines, if any.
289
-     *
290
-     * @param {Array<string>} lines - The lines to render.
291
-     * @private
292
-     * @returns {Array<React$Node>}
293
-     */
294
-    _renderItemLines(lines) {
295
-        return lines && lines.length ? lines.map(this._renderItemLine) : null;
296
-    }
297
-
298
-    _renderListEmptyComponent: () => Object;
299
-
300
-    /**
301
-     * Renders a component to display when the list is empty.
302
-     *
303
-     * @param {Object} section - The section being rendered.
304
-     * @private
305
-     * @returns {React$Node}
306
-     */
307
-    _renderListEmptyComponent() {
308
-        const { t, onRefresh } = this.props;
309
-
310
-        if (typeof onRefresh === 'function') {
311
-            return (
312
-                <View style = { styles.pullToRefresh }>
313
-                    <Text style = { styles.pullToRefreshText }>
314
-                        { t('sectionList.pullToRefresh') }
315
-                    </Text>
316
-                    <Icon
317
-                        name = 'menu-down'
318
-                        style = { styles.pullToRefreshIcon } />
319
-                </View>
320
-            );
321
-        }
322
-
323
-        return null;
324
-    }
325
-
326
-    _renderSection: Object => Object;
327
-
328
-    /**
329
-     * Renders a section title.
330
-     *
331
-     * @param {Object} section - The section being rendered.
332
-     * @private
333
-     * @returns {React$Node}
334
-     */
335
-    _renderSection(section) {
336
-        return (
337
-            <View style = { styles.listSection }>
338
-                <Text style = { styles.listSectionText }>
339
-                    { section.section.title }
340
-                </Text>
341
-            </View>
342
-        );
343
-    }
344
-}
345
-
346
-export default translate(NavigateSectionList);

+ 51
- 0
react/features/base/react/components/native/NavigateSectionListEmptyComponent.js Voir le fichier

@@ -0,0 +1,51 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import {
5
+    Text,
6
+    View
7
+} from 'react-native';
8
+
9
+import { Icon } from '../../../font-icons/index';
10
+import { translate } from '../../../i18n/index';
11
+
12
+import styles from './styles';
13
+
14
+type Props = {
15
+
16
+    /**
17
+     * The translate function.
18
+     */
19
+    t: Function,
20
+};
21
+
22
+/**
23
+ * Implements a React Native {@link Component} that is to be displayed when the
24
+ * list is empty
25
+ *
26
+ * @extends Component
27
+ */
28
+class NavigateSectionListEmptyComponent extends Component<Props> {
29
+    /**
30
+     * Implements React's {@link Component#render()}.
31
+     *
32
+     * @inheritdoc
33
+     * @returns {ReactElement}
34
+     */
35
+    render() {
36
+        const { t } = this.props;
37
+
38
+        return (
39
+            <View style = { styles.pullToRefresh }>
40
+                <Text style = { styles.pullToRefreshText }>
41
+                    { t('sectionList.pullToRefresh') }
42
+                </Text>
43
+                <Icon
44
+                    name = 'menu-down'
45
+                    style = { styles.pullToRefreshIcon } />
46
+            </View>
47
+        );
48
+    }
49
+}
50
+
51
+export default translate(NavigateSectionListEmptyComponent);

+ 145
- 0
react/features/base/react/components/native/NavigateSectionListItem.js Voir le fichier

@@ -0,0 +1,145 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { TouchableHighlight } from 'react-native';
5
+
6
+import {
7
+    Text,
8
+    Container
9
+} from './index';
10
+import styles, { UNDERLAY_COLOR } from './styles';
11
+import type { Item } from '../../Types';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * item containing data to be rendered
17
+     */
18
+    item: Item,
19
+
20
+    /**
21
+     * Function to be invoked when an Item is pressed. The Item's URL is passed.
22
+     */
23
+    onPress: Function
24
+}
25
+
26
+/**
27
+ * Implements a React/Native {@link Component} that renders the Navigate Section
28
+ * List Item
29
+ *
30
+ * @extends Component
31
+ */
32
+export default class NavigateSectionListItem extends Component<Props> {
33
+    /**
34
+     * Constructor of the NavigateSectionList component.
35
+     *
36
+     * @inheritdoc
37
+     */
38
+    constructor(props: Props) {
39
+        super(props);
40
+        this._getAvatarColor = this._getAvatarColor.bind(this);
41
+        this._renderItemLine = this._renderItemLine.bind(this);
42
+        this._renderItemLines = this._renderItemLines.bind(this);
43
+    }
44
+
45
+    _getAvatarColor: string => Object;
46
+
47
+    /**
48
+     * Returns a style (color) based on the string that determines the color of
49
+     * the avatar.
50
+     *
51
+     * @param {string} colorBase - The string that is the base of the color.
52
+     * @private
53
+     * @returns {Object}
54
+     */
55
+    _getAvatarColor(colorBase) {
56
+        if (!colorBase) {
57
+            return null;
58
+        }
59
+        let nameHash = 0;
60
+
61
+        for (let i = 0; i < colorBase.length; i++) {
62
+            nameHash += colorBase.codePointAt(i);
63
+        }
64
+
65
+        return styles[`avatarColor${(nameHash % 5) + 1}`];
66
+    }
67
+
68
+    _renderItemLine: (string, number) => React$Node;
69
+
70
+    /**
71
+     * Renders a single line from the additional lines.
72
+     *
73
+     * @param {string} line - The line text.
74
+     * @param {number} index - The index of the line.
75
+     * @private
76
+     * @returns {React$Node}
77
+     */
78
+    _renderItemLine(line, index) {
79
+        if (!line) {
80
+            return null;
81
+        }
82
+
83
+        return (
84
+            <Text
85
+                key = { index }
86
+                numberOfLines = { 1 }
87
+                style = { styles.listItemText }>
88
+                {line}
89
+            </Text>
90
+        );
91
+    }
92
+
93
+    _renderItemLines: Array<string> => Array<React$Node>;
94
+
95
+    /**
96
+     * Renders the additional item lines, if any.
97
+     *
98
+     * @param {Array<string>} lines - The lines to render.
99
+     * @private
100
+     * @returns {Array<React$Node>}
101
+     */
102
+    _renderItemLines(lines) {
103
+        return lines && lines.length ? lines.map(this._renderItemLine) : null;
104
+    }
105
+
106
+    /**
107
+     * Renders the content of this component.
108
+     *
109
+     * @returns {ReactElement}
110
+     */
111
+    render() {
112
+        const { colorBase, lines, title } = this.props.item;
113
+
114
+        return (
115
+            <TouchableHighlight
116
+                onPress = { this.props.onPress }
117
+                underlayColor = { UNDERLAY_COLOR }>
118
+                <Container style = { styles.listItem }>
119
+                    <Container style = { styles.avatarContainer }>
120
+                        <Container
121
+                            style = { [
122
+                                styles.avatar,
123
+                                this._getAvatarColor(colorBase)
124
+                            ] }>
125
+                            <Text style = { styles.avatarContent }>
126
+                                {title.substr(0, 1).toUpperCase()}
127
+                            </Text>
128
+                        </Container>
129
+                    </Container>
130
+                    <Container style = { styles.listItemDetails }>
131
+                        <Text
132
+                            numberOfLines = { 1 }
133
+                            style = { [
134
+                                styles.listItemText,
135
+                                styles.listItemTitle
136
+                            ] }>
137
+                            {title}
138
+                        </Text>
139
+                        {this._renderItemLines(lines)}
140
+                    </Container>
141
+                </Container>
142
+            </TouchableHighlight>
143
+        );
144
+    }
145
+}

+ 40
- 0
react/features/base/react/components/native/NavigateSectionListSectionHeader.js Voir le fichier

@@ -0,0 +1,40 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { Text, Container } from './index';
6
+import styles from './styles';
7
+import type { SetionListSection } from '../../Types';
8
+
9
+type Props = {
10
+
11
+    /**
12
+     * A section containing the data to be rendered
13
+     */
14
+    section: SetionListSection
15
+}
16
+
17
+/**
18
+ * Implements a React/Native {@link Component} that renders the section header
19
+ * of the list
20
+ *
21
+ * @extends Component
22
+ */
23
+export default class NavigateSectionListSectionHeader extends Component<Props> {
24
+    /**
25
+     * Renders the content of this component.
26
+     *
27
+     * @returns {ReactElement}
28
+     */
29
+    render() {
30
+        const { section } = this.props.section;
31
+
32
+        return (
33
+            <Container style = { styles.listSection }>
34
+                <Text style = { styles.listSectionText }>
35
+                    { section.title }
36
+                </Text>
37
+            </Container>
38
+        );
39
+    }
40
+}

+ 91
- 0
react/features/base/react/components/native/SectionList.js Voir le fichier

@@ -0,0 +1,91 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import {
5
+    SafeAreaView,
6
+    SectionList as ReactNativeSectionList
7
+} from 'react-native';
8
+
9
+import styles from './styles';
10
+import type { Section } from '../../Types';
11
+
12
+/**
13
+ * The type of the React {@code Component} props of {@link SectionList}
14
+ */
15
+type Props = {
16
+
17
+    /**
18
+     * Rendered when the list is empty. Can be a React Component Class, a render
19
+     * function, or a rendered element.
20
+     */
21
+    ListEmptyComponent: Object,
22
+
23
+    /**
24
+    *
25
+    * Used to extract a unique key for a given item at the specified index.
26
+     * Key is used for caching and as the react key to track item re-ordering.
27
+    */
28
+    keyExtractor: Function,
29
+
30
+    /**
31
+    *
32
+    * Functions that defines what happens when the list is pulled for refresh
33
+    */
34
+    onRefresh: Function,
35
+
36
+    /**
37
+    *
38
+    * A boolean that is set true while waiting for new data from a refresh.
39
+    */
40
+    refreshing: ?boolean,
41
+
42
+    /**
43
+    *
44
+    * Default renderer for every item in every section.
45
+    */
46
+    renderItem: Function,
47
+
48
+    /**
49
+    *
50
+    * A component rendered at the top of each section. These stick to the top
51
+     * of the ScrollView by default on iOS.
52
+    */
53
+    renderSectionHeader: Object,
54
+
55
+    /**
56
+     * An array of sections
57
+     */
58
+    sections: Array<Section>
59
+};
60
+
61
+/**
62
+ * Implements a React Native {@link Component} that wraps the React Native
63
+ * SectionList component in a SafeAreaView so that it renders the sectionlist
64
+ * within the safe area of the device
65
+ *
66
+ * @extends Component
67
+ */
68
+export default class SectionList extends Component<Props> {
69
+    /**
70
+     * Implements React's {@link Component#render()}.
71
+     *
72
+     * @inheritdoc
73
+     * @returns {ReactElement}
74
+     */
75
+    render() {
76
+        return (
77
+            <SafeAreaView
78
+                style = { styles.container } >
79
+                <ReactNativeSectionList
80
+                    ListEmptyComponent = { this.props.ListEmptyComponent }
81
+                    keyExtractor = { this.props.keyExtractor }
82
+                    onRefresh = { this.props.onRefresh }
83
+                    refreshing = { this.props.refreshing }
84
+                    renderItem = { this.props.renderItem }
85
+                    renderSectionHeader = { this.props.renderSectionHeader }
86
+                    sections = { this.props.sections }
87
+                    style = { styles.list } />
88
+            </SafeAreaView>
89
+        );
90
+    }
91
+}

+ 7
- 1
react/features/base/react/components/native/index.js Voir le fichier

@@ -1,10 +1,16 @@
1 1
 export { default as Container } from './Container';
2 2
 export { default as Header } from './Header';
3
-export { default as NavigateSectionList } from './NavigateSectionList';
4 3
 export { default as Link } from './Link';
5 4
 export { default as LoadingIndicator } from './LoadingIndicator';
5
+export { default as NavigateSectionListEmptyComponent } from
6
+    './NavigateSectionListEmptyComponent';
7
+export { default as NavigateSectionListItem }
8
+    from './NavigateSectionListItem';
9
+export { default as NavigateSectionListSectionHeader }
10
+    from './NavigateSectionListSectionHeader';
6 11
 export { default as PagedList } from './PagedList';
7 12
 export { default as Pressable } from './Pressable';
8 13
 export { default as SideBar } from './SideBar';
9 14
 export { default as Text } from './Text';
15
+export { default as SectionList } from './SectionList';
10 16
 export { default as TintedView } from './TintedView';

+ 0
- 0
react/features/base/react/components/web/NavigateSectionListEmptyComponent.js Voir le fichier


+ 73
- 0
react/features/base/react/components/web/NavigateSectionListItem.js Voir le fichier

@@ -0,0 +1,73 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { Text, Container } from './index';
6
+import type { Item } from '../../Types';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * Function to be invoked when an item is pressed. The item's URL is passed.
12
+     */
13
+    onPress: Function,
14
+
15
+    /**
16
+     * A item containing data to be rendered
17
+     */
18
+    item: Item
19
+}
20
+
21
+/**
22
+ * Implements a React/Web {@link Component} for displaying an item in a
23
+ * NavigateSectionList
24
+ *
25
+ * @extends Component
26
+ */
27
+export default class NavigateSectionListItem extends Component<Props> {
28
+    /**
29
+     * Renders the content of this component.
30
+     *
31
+     * @returns {ReactElement}
32
+     */
33
+    render() {
34
+        const { lines, title } = this.props.item;
35
+        const { onPress } = this.props;
36
+
37
+        /**
38
+         * Initiliazes the date and duration of the conference to the an empty
39
+         * string in case for some reason there is an error where the item data
40
+         * lines doesn't contain one or both of those values (even though this
41
+         * unlikely the app shouldn't break because of it)
42
+         * @type {string}
43
+         */
44
+        let date = '';
45
+        let duration = '';
46
+
47
+        if (lines[0]) {
48
+            date = lines[0];
49
+        }
50
+        if (lines[1]) {
51
+            duration = lines[1];
52
+        }
53
+
54
+        return (
55
+            <Container
56
+                className = 'navigate-section-list-tile'
57
+                onClick = { onPress }>
58
+                <Text
59
+                    className = 'navigate-section-tile-title'>
60
+                    { title }
61
+                </Text>
62
+                <Text
63
+                    className = 'navigate-section-tile-body'>
64
+                    { date }
65
+                </Text>
66
+                <Text
67
+                    className = 'navigate-section-tile-body'>
68
+                    { duration }
69
+                </Text>
70
+            </Container>
71
+        );
72
+    }
73
+}

+ 35
- 0
react/features/base/react/components/web/NavigateSectionListSectionHeader.js Voir le fichier

@@ -0,0 +1,35 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { Text } from './index';
6
+import type { Section } from '../../Types';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * A section containing the data to be rendered
12
+     */
13
+    section: Section
14
+}
15
+
16
+/**
17
+ * Implements a React/Web {@link Component} that renders the section header of
18
+ * the list
19
+ *
20
+ * @extends Component
21
+ */
22
+export default class NavigateSectionListSectionHeader extends Component<Props> {
23
+    /**
24
+     * Renders the content of this component.
25
+     *
26
+     * @returns {ReactElement}
27
+     */
28
+    render() {
29
+        return (
30
+            <Text className = 'navigate-section-section-header'>
31
+                { this.props.section.title }
32
+            </Text>
33
+        );
34
+    }
35
+}

+ 92
- 0
react/features/base/react/components/web/SectionList.js Voir le fichier

@@ -0,0 +1,92 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { Container } from './index';
6
+import type { Section } from '../../Types';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * Used to extract a unique key for a given item at the specified index.
12
+     * Key is used for caching and as the react key to track item re-ordering.
13
+     */
14
+    keyExtractor: Function,
15
+
16
+    /**
17
+     * Returns a React component that renders each Item in the list
18
+     */
19
+    renderItem: Function,
20
+
21
+    /**
22
+     * Returns a React component that renders the header for every section
23
+     */
24
+    renderSectionHeader: Function,
25
+
26
+    /**
27
+     * An array of sections
28
+     */
29
+    sections: Array<Section>,
30
+
31
+    /**
32
+     * defines what happens when  an item in the section list is clicked
33
+     */
34
+    onItemClick: Function
35
+};
36
+
37
+/**
38
+ * Implements a React/Web {@link Component} for displaying a list with
39
+ * sections similar to React Native's {@code SectionList} in order to
40
+ * faciliate cross-platform source code.
41
+ *
42
+ * @extends Component
43
+ */
44
+export default class SectionList extends Component<Props> {
45
+    /**
46
+     * Renders the content of this component.
47
+     *
48
+     * @returns {React.ReactNode}
49
+     */
50
+    render() {
51
+        const {
52
+            renderSectionHeader,
53
+            renderItem,
54
+            sections,
55
+            keyExtractor
56
+        } = this.props;
57
+
58
+        /**
59
+         * If there are no recent items we dont want to display anything
60
+         */
61
+        if (sections) {
62
+            return (
63
+            /* eslint-disable no-extra-parens */
64
+                <Container
65
+                    className = 'navigate-section-list'>
66
+                    {
67
+                        sections.map((section, sectionIndex) => (
68
+                            <Container
69
+                                key = { sectionIndex }>
70
+                                { renderSectionHeader(section) }
71
+                                { section.data
72
+                                    .map((item, listIndex) => {
73
+                                        const listItem = {
74
+                                            item
75
+                                        };
76
+
77
+                                        return renderItem(listItem,
78
+                                            keyExtractor(section,
79
+                                                listIndex));
80
+                                    }) }
81
+                            </Container>
82
+                        )
83
+                        )
84
+                    }
85
+                </Container>
86
+            /* eslint-enable no-extra-parens */
87
+            );
88
+        }
89
+
90
+        return null;
91
+    }
92
+}

+ 7
- 0
react/features/base/react/components/web/index.js Voir le fichier

@@ -1,4 +1,11 @@
1 1
 export { default as Container } from './Container';
2 2
 export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete';
3
+export { default as NavigateSectionListEmptyComponent } from
4
+    './NavigateSectionListEmptyComponent';
5
+export { default as NavigateSectionListItem } from
6
+    './NavigateSectionListItem';
7
+export { default as NavigateSectionListSectionHeader }
8
+    from './NavigateSectionListSectionHeader';
9
+export { default as SectionList } from './SectionList';
3 10
 export { default as Text } from './Text';
4 11
 export { default as Watermarks } from './Watermarks';

+ 5
- 0
react/features/base/storage/middleware.js Voir le fichier

@@ -37,3 +37,8 @@ MiddlewareRegistry.register(store => next => action => {
37 37
 
38 38
     return result;
39 39
 });
40
+
41
+window.addEventListener('beforeunload', () => {
42
+    // Stop the LogCollector
43
+    throttledPersistState.flush();
44
+});

+ 109
- 0
react/features/recent-list/components/RecentList.js Voir le fichier

@@ -0,0 +1,109 @@
1
+// @flow
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { appNavigate, getDefaultURL } from '../../app';
6
+import { translate } from '../../base/i18n';
7
+import type { Section } from '../../base/react/Types';
8
+import { NavigateSectionList } from '../../base/react';
9
+
10
+import { toDisplayableList } from '../functions';
11
+
12
+/**
13
+ * The type of the React {@code Component} props of {@link RecentList}
14
+ */
15
+type Props = {
16
+
17
+    /**
18
+     * Renders the list disabled.
19
+     */
20
+    disabled: boolean,
21
+
22
+    /**
23
+     * The redux store's {@code dispatch} function.
24
+     */
25
+    dispatch: Dispatch<*>,
26
+
27
+    /**
28
+     * The translate function.
29
+     */
30
+    t: Function,
31
+
32
+    /**
33
+     * The default server URL.
34
+     */
35
+    _defaultServerURL: string,
36
+
37
+    /**
38
+     * The recent list from the Redux store.
39
+     */
40
+    _recentList: Array<Section>
41
+};
42
+
43
+/**
44
+ * The cross platform container rendering the list of the recently joined rooms.
45
+ *
46
+ */
47
+class RecentList extends Component<Props> {
48
+    /**
49
+     * Initializes a new {@code RecentList} instance.
50
+     *
51
+     * @inheritdoc
52
+     */
53
+    constructor(props: Props) {
54
+        super(props);
55
+
56
+        this._onPress = this._onPress.bind(this);
57
+    }
58
+
59
+    /**
60
+     * Implements the React Components's render method.
61
+     *
62
+     * @inheritdoc
63
+     */
64
+    render() {
65
+        const { disabled, t, _defaultServerURL, _recentList } = this.props;
66
+        const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
67
+
68
+        return (
69
+            <NavigateSectionList
70
+                disabled = { disabled }
71
+                onPress = { this._onPress }
72
+                sections = { recentList } />
73
+        );
74
+    }
75
+
76
+    _onPress: string => Function;
77
+
78
+    /**
79
+     * Handles the list's navigate action.
80
+     *
81
+     * @private
82
+     * @param {string} url - The url string to navigate to.
83
+     * @returns {void}
84
+     */
85
+    _onPress(url) {
86
+        const { dispatch } = this.props;
87
+
88
+        dispatch(appNavigate(url));
89
+    }
90
+
91
+}
92
+
93
+/**
94
+ * Maps redux state to component props.
95
+ *
96
+ * @param {Object} state - The redux state.
97
+ * @returns {{
98
+ *     _defaultServerURL: string,
99
+ *     _recentList: Array
100
+ * }}
101
+ */
102
+export function _mapStateToProps(state: Object) {
103
+    return {
104
+        _defaultServerURL: getDefaultURL(state),
105
+        _recentList: state['features/recent-list']
106
+    };
107
+}
108
+
109
+export default translate(connect(_mapStateToProps)(RecentList));

+ 0
- 234
react/features/recent-list/components/RecentList.native.js Voir le fichier

@@ -1,234 +0,0 @@
1
-// @flow
2
-import React, { Component } from 'react';
3
-import { connect } from 'react-redux';
4
-
5
-import { appNavigate, getDefaultURL } from '../../app';
6
-import {
7
-    getLocalizedDateFormatter,
8
-    getLocalizedDurationFormatter,
9
-    translate
10
-} from '../../base/i18n';
11
-import { NavigateSectionList } from '../../base/react';
12
-import { parseURIString } from '../../base/util';
13
-
14
-/**
15
- * The type of the React {@code Component} props of {@link RecentList}
16
- */
17
-type Props = {
18
-
19
-    /**
20
-     * Renders the list disabled.
21
-     */
22
-    disabled: boolean,
23
-
24
-    /**
25
-     * The redux store's {@code dispatch} function.
26
-     */
27
-    dispatch: Dispatch<*>,
28
-
29
-    /**
30
-     * The translate function.
31
-     */
32
-    t: Function,
33
-
34
-    /**
35
-     * The default server URL.
36
-     */
37
-    _defaultServerURL: string,
38
-
39
-    /**
40
-     * The recent list from the Redux store.
41
-     */
42
-    _recentList: Array<Object>
43
-};
44
-
45
-/**
46
- * The native container rendering the list of the recently joined rooms.
47
- *
48
- */
49
-class RecentList extends Component<Props> {
50
-    /**
51
-     * Initializes a new {@code RecentList} instance.
52
-     *
53
-     * @inheritdoc
54
-     */
55
-    constructor(props: Props) {
56
-        super(props);
57
-
58
-        this._onPress = this._onPress.bind(this);
59
-        this._toDateString = this._toDateString.bind(this);
60
-        this._toDurationString = this._toDurationString.bind(this);
61
-        this._toDisplayableItem = this._toDisplayableItem.bind(this);
62
-        this._toDisplayableList = this._toDisplayableList.bind(this);
63
-    }
64
-
65
-    /**
66
-     * Implements the React Components's render method.
67
-     *
68
-     * @inheritdoc
69
-     */
70
-    render() {
71
-        const { disabled } = this.props;
72
-
73
-        return (
74
-            <NavigateSectionList
75
-                disabled = { disabled }
76
-                onPress = { this._onPress }
77
-                sections = { this._toDisplayableList() } />
78
-        );
79
-    }
80
-
81
-    _onPress: string => Function;
82
-
83
-    /**
84
-     * Handles the list's navigate action.
85
-     *
86
-     * @private
87
-     * @param {string} url - The url string to navigate to.
88
-     * @returns {void}
89
-     */
90
-    _onPress(url) {
91
-        const { dispatch } = this.props;
92
-
93
-        dispatch(appNavigate(url));
94
-    }
95
-
96
-    _toDisplayableItem: Object => Object;
97
-
98
-    /**
99
-     * Creates a displayable list item of a recent list entry.
100
-     *
101
-     * @private
102
-     * @param {Object} item - The recent list entry.
103
-     * @returns {Object}
104
-     */
105
-    _toDisplayableItem(item) {
106
-        const { _defaultServerURL } = this.props;
107
-        const location = parseURIString(item.conference);
108
-        const baseURL = `${location.protocol}//${location.host}`;
109
-        const serverName = baseURL === _defaultServerURL ? null : location.host;
110
-
111
-        return {
112
-            colorBase: serverName,
113
-            key: `key-${item.conference}-${item.date}`,
114
-            lines: [
115
-                this._toDateString(item.date),
116
-                this._toDurationString(item.duration),
117
-                serverName
118
-            ],
119
-            title: location.room,
120
-            url: item.conference
121
-        };
122
-    }
123
-
124
-    _toDisplayableList: () => Array<Object>;
125
-
126
-    /**
127
-     * Transforms the history list to a displayable list
128
-     * with sections.
129
-     *
130
-     * @private
131
-     * @returns {Array<Object>}
132
-     */
133
-    _toDisplayableList() {
134
-        const { _recentList, t } = this.props;
135
-        const { createSection } = NavigateSectionList;
136
-        const todaySection = createSection(t('recentList.today'), 'today');
137
-        const yesterdaySection
138
-            = createSection(t('recentList.yesterday'), 'yesterday');
139
-        const earlierSection
140
-            = createSection(t('recentList.earlier'), 'earlier');
141
-        const today = new Date().toDateString();
142
-        const yesterdayDate = new Date();
143
-
144
-        yesterdayDate.setDate(yesterdayDate.getDate() - 1);
145
-
146
-        const yesterday = yesterdayDate.toDateString();
147
-
148
-        for (const item of _recentList) {
149
-            const itemDay = new Date(item.date).toDateString();
150
-            const displayableItem = this._toDisplayableItem(item);
151
-
152
-            if (itemDay === today) {
153
-                todaySection.data.push(displayableItem);
154
-            } else if (itemDay === yesterday) {
155
-                yesterdaySection.data.push(displayableItem);
156
-            } else {
157
-                earlierSection.data.push(displayableItem);
158
-            }
159
-        }
160
-
161
-        const displayableList = [];
162
-
163
-        if (todaySection.data.length) {
164
-            todaySection.data.reverse();
165
-            displayableList.push(todaySection);
166
-        }
167
-        if (yesterdaySection.data.length) {
168
-            yesterdaySection.data.reverse();
169
-            displayableList.push(yesterdaySection);
170
-        }
171
-        if (earlierSection.data.length) {
172
-            earlierSection.data.reverse();
173
-            displayableList.push(earlierSection);
174
-        }
175
-
176
-        return displayableList;
177
-    }
178
-
179
-    _toDateString: number => string;
180
-
181
-    /**
182
-     * Generates a date string for the item.
183
-     *
184
-     * @private
185
-     * @param {number} itemDate - The item's timestamp.
186
-     * @returns {string}
187
-     */
188
-    _toDateString(itemDate) {
189
-        const date = new Date(itemDate);
190
-        const m = getLocalizedDateFormatter(itemDate);
191
-
192
-        if (date.toDateString() === new Date().toDateString()) {
193
-            // The date is today, we use fromNow format.
194
-            return m.fromNow();
195
-        }
196
-
197
-        return m.format('lll');
198
-    }
199
-
200
-    _toDurationString: number => string;
201
-
202
-    /**
203
-     * Generates a duration string for the item.
204
-     *
205
-     * @private
206
-     * @param {number} duration - The item's duration.
207
-     * @returns {string}
208
-     */
209
-    _toDurationString(duration) {
210
-        if (duration) {
211
-            return getLocalizedDurationFormatter(duration).humanize();
212
-        }
213
-
214
-        return null;
215
-    }
216
-}
217
-
218
-/**
219
- * Maps redux state to component props.
220
- *
221
- * @param {Object} state - The redux state.
222
- * @returns {{
223
- *     _defaultServerURL: string,
224
- *     _recentList: Array
225
- * }}
226
- */
227
-export function _mapStateToProps(state: Object) {
228
-    return {
229
-        _defaultServerURL: getDefaultURL(state),
230
-        _recentList: state['features/recent-list']
231
-    };
232
-}
233
-
234
-export default translate(connect(_mapStateToProps)(RecentList));

+ 7
- 0
react/features/recent-list/featureFlag.native.js Voir le fichier

@@ -0,0 +1,7 @@
1
+/**
2
+ * Everything about recent list on web should be behind a feature flag and in
3
+ * order to share code, this alias for the feature flag on mobile is always true
4
+ * because we dont need a feature flag for recent list on mobile
5
+ * @type {boolean}
6
+ */
7
+export const RECENT_LIST_ENABLED = true;

+ 10
- 0
react/features/recent-list/featureFlag.web.js Voir le fichier

@@ -0,0 +1,10 @@
1
+// @flow
2
+declare var interfaceConfig: Object;
3
+
4
+/**
5
+ * Everything about recent list on web should be behind a feature flag and in
6
+ * order to share code, this alias for the feature flag on mobile is set to the
7
+ * value defined in interface_config
8
+ * @type {boolean}
9
+ */
10
+export const { RECENT_LIST_ENABLED } = interfaceConfig;

+ 81
- 0
react/features/recent-list/functions.all.js Voir le fichier

@@ -0,0 +1,81 @@
1
+import { getLocalizedDateFormatter, getLocalizedDurationFormatter }
2
+    from '../base/i18n/index';
3
+import { parseURIString } from '../base/util/index';
4
+
5
+/**
6
+ * Creates a displayable list item of a recent list entry.
7
+ *
8
+ * @private
9
+ * @param {Object} item - The recent list entry.
10
+ * @param {string} defaultServerURL - The default server URL.
11
+ * @param {Function} t - The translate function.
12
+ * @returns {Object}
13
+ */
14
+export function toDisplayableItem(item, defaultServerURL, t) {
15
+    // const { _defaultServerURL } = this.props;
16
+    const location = parseURIString(item.conference);
17
+    const baseURL = `${location.protocol}//${location.host}`;
18
+    const serverName = baseURL === defaultServerURL ? null : location.host;
19
+
20
+    return {
21
+        colorBase: serverName,
22
+        key: `key-${item.conference}-${item.date}`,
23
+        lines: [
24
+            _toDateString(item.date, t),
25
+            _toDurationString(item.duration),
26
+            serverName
27
+        ],
28
+        title: location.room,
29
+        url: item.conference
30
+    };
31
+}
32
+
33
+/**
34
+ * Generates a duration string for the item.
35
+ *
36
+ * @private
37
+ * @param {number} duration - The item's duration.
38
+ * @returns {string}
39
+ */
40
+export function _toDurationString(duration) {
41
+    if (duration) {
42
+        return getLocalizedDurationFormatter(duration);
43
+    }
44
+
45
+    return null;
46
+}
47
+
48
+/**
49
+ * Generates a date string for the item.
50
+ *
51
+ * @private
52
+ * @param {number} itemDate - The item's timestamp.
53
+ * @param {Function} t - The translate function.
54
+ * @returns {string}
55
+ */
56
+export function _toDateString(itemDate, t) {
57
+    const date = new Date(itemDate);
58
+    const dateString = date.toDateString();
59
+    const m = getLocalizedDateFormatter(itemDate);
60
+    const yesterday = new Date();
61
+
62
+    yesterday.setDate(yesterday.getDate() - 1);
63
+    const yesterdayString = yesterday.toDateString();
64
+    const today = new Date();
65
+    const todayString = today.toDateString();
66
+    const currentYear = today.getFullYear();
67
+    const year = date.getFullYear();
68
+
69
+    if (dateString === todayString) {
70
+        // The date is today, we use fromNow format.
71
+        return m.fromNow();
72
+    } else if (dateString === yesterdayString) {
73
+        return t('dateUtils.yesterday');
74
+    } else if (year !== currentYear) {
75
+        // we only want to include the year in the date if its not the current
76
+        // year
77
+        return m.format('ddd, MMMM DD h:mm A, gggg');
78
+    }
79
+
80
+    return m.format('ddd, MMMM DD h:mm A');
81
+}

+ 62
- 0
react/features/recent-list/functions.native.js Voir le fichier

@@ -0,0 +1,62 @@
1
+import { NavigateSectionList } from '../base/react/index';
2
+
3
+import { toDisplayableItem } from './functions.all';
4
+
5
+/**
6
+ * Transforms the history list to a displayable list
7
+ * with sections.
8
+ *
9
+ * @private
10
+ * @param {Array<Object>} recentList - The recent list form the redux store.
11
+ * @param {Function} t - The translate function.
12
+ * @param {string} defaultServerURL - The default server URL.
13
+ * @returns {Array<Object>}
14
+ */
15
+export function toDisplayableList(recentList, t, defaultServerURL) {
16
+    const { createSection } = NavigateSectionList;
17
+    const todaySection = createSection(t('dateUtils.today'), 'today');
18
+    const yesterdaySection
19
+        = createSection(t('dateUtils.yesterday'), 'yesterday');
20
+    const earlierSection
21
+        = createSection(t('dateUtils.earlier'), 'earlier');
22
+    const today = new Date();
23
+    const todayString = today.toDateString();
24
+    const yesterday = new Date();
25
+
26
+    yesterday.setDate(yesterday.getDate() - 1);
27
+    const yesterdayString = yesterday.toDateString();
28
+
29
+    for (const item of recentList) {
30
+        const itemDateString = new Date(item.date).toDateString();
31
+        const displayableItem = toDisplayableItem(item, defaultServerURL, t);
32
+
33
+        if (itemDateString === todayString) {
34
+            todaySection.data.push(displayableItem);
35
+        } else if (itemDateString === yesterdayString) {
36
+            yesterdaySection.data.push(displayableItem);
37
+        } else {
38
+            earlierSection.data.push(displayableItem);
39
+        }
40
+    }
41
+    const displayableList = [];
42
+
43
+    // the recent list in the redux store has the latest date in the last index
44
+    // therefore all the sectionLists' data that was created by parsing through
45
+    // the recent list is in reverse order and must be reversed for the most
46
+    // item to show first
47
+    if (todaySection.data.length) {
48
+        todaySection.data.reverse();
49
+        displayableList.push(todaySection);
50
+    }
51
+    if (yesterdaySection.data.length) {
52
+        yesterdaySection.data.reverse();
53
+        displayableList.push(yesterdaySection);
54
+    }
55
+    if (earlierSection.data.length) {
56
+        earlierSection.data.reverse();
57
+        displayableList.push(earlierSection);
58
+    }
59
+
60
+    return displayableList;
61
+}
62
+

+ 34
- 0
react/features/recent-list/functions.web.js Voir le fichier

@@ -0,0 +1,34 @@
1
+import { NavigateSectionList } from '../base/react/index';
2
+
3
+import { toDisplayableItem } from './functions.all';
4
+
5
+/**
6
+ * Transforms the history list to a displayable list
7
+ * with sections.
8
+ *
9
+ * @private
10
+ * @param {Array<Object>} recentList - The recent list form the redux store.
11
+ * @param {Function} t - The translate function.
12
+ * @param {string} defaultServerURL - The default server URL.
13
+ * @returns {Array<Object>}
14
+ */
15
+export function toDisplayableList(recentList, t, defaultServerURL) {
16
+    const { createSection } = NavigateSectionList;
17
+    const section = createSection(t('recentList.joinPastMeeting'), 'all');
18
+
19
+    // we only want the last three conferences we were in for web
20
+    for (const item of recentList.slice(1).slice(-3)) {
21
+        const displayableItem = toDisplayableItem(item, defaultServerURL, t);
22
+
23
+        section.data.push(displayableItem);
24
+    }
25
+    const displayableList = [];
26
+
27
+    if (section.data.length) {
28
+        section.data.reverse();
29
+        displayableList.push(section);
30
+    }
31
+
32
+    return displayableList;
33
+}
34
+

+ 35
- 8
react/features/recent-list/middleware.js Voir le fichier

@@ -2,12 +2,20 @@
2 2
 
3 3
 import { APP_WILL_MOUNT } from '../base/app';
4 4
 import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference';
5
+import { JITSI_CONFERENCE_URL_KEY } from '../base/conference/constants';
5 6
 import { addKnownDomains } from '../base/known-domains';
6 7
 import { MiddlewareRegistry } from '../base/redux';
7 8
 import { parseURIString } from '../base/util';
9
+import { RECENT_LIST_ENABLED } from './featureFlag';
8 10
 
9 11
 import { _storeCurrentConference, _updateConferenceDuration } from './actions';
10 12
 
13
+/**
14
+ * used in order to get the device because there is a different way to get the
15
+ * location URL on web and on native
16
+ */
17
+declare var APP: Object;
18
+
11 19
 /**
12 20
  * Middleware that captures joined rooms so they can be saved into
13 21
  * {@code window.localStorage}.
@@ -16,15 +24,17 @@ import { _storeCurrentConference, _updateConferenceDuration } from './actions';
16 24
  * @returns {Function}
17 25
  */
18 26
 MiddlewareRegistry.register(store => next => action => {
19
-    switch (action.type) {
20
-    case APP_WILL_MOUNT:
21
-        return _appWillMount(store, next, action);
27
+    if (RECENT_LIST_ENABLED) {
28
+        switch (action.type) {
29
+        case APP_WILL_MOUNT:
30
+            return _appWillMount(store, next, action);
22 31
 
23
-    case CONFERENCE_WILL_LEAVE:
24
-        return _conferenceWillLeave(store, next, action);
32
+        case CONFERENCE_WILL_LEAVE:
33
+            return _conferenceWillLeave(store, next, action);
25 34
 
26
-    case SET_ROOM:
27
-        return _setRoom(store, next, action);
35
+        case SET_ROOM:
36
+            return _setRoom(store, next, action);
37
+        }
28 38
     }
29 39
 
30 40
     return next(action);
@@ -77,9 +87,26 @@ function _appWillMount({ dispatch, getState }, next, action) {
77 87
  * @returns {*} The result returned by {@code next(action)}.
78 88
  */
79 89
 function _conferenceWillLeave({ dispatch, getState }, next, action) {
90
+    let locationURL;
91
+
92
+    /** FIXME
93
+     * It is better to use action.conference[JITSI_CONFERENCE_URL_KEY]
94
+     * in order to make sure we get the url the conference is leaving
95
+     * from (i.e. the room we are leaving from) because if the order of events
96
+     * is different, we cannot be guranteed that the location URL in base
97
+     * connection is the url we are leaving from... not the one we are going to
98
+     * (the latter happens on mobile -- if we use the web implementation);
99
+     * however, the conference object on web does not have
100
+     * JITSI_CONFERENCE_URL_KEY so we cannot call it and must use the other way
101
+     */
102
+    if (typeof APP === 'undefined') {
103
+        locationURL = action.conference[JITSI_CONFERENCE_URL_KEY];
104
+    } else {
105
+        locationURL = getState()['features/base/connection'].locationURL;
106
+    }
80 107
     dispatch(
81 108
         _updateConferenceDuration(
82
-            getState()['features/base/connection'].locationURL));
109
+            locationURL));
83 110
 
84 111
     return next(action);
85 112
 }

+ 14
- 12
react/features/recent-list/reducer.js Voir le fichier

@@ -1,5 +1,4 @@
1 1
 // @flow
2
-
3 2
 import { APP_WILL_MOUNT } from '../base/app';
4 3
 import { getURLWithoutParamsNormalized } from '../base/connection';
5 4
 import { ReducerRegistry } from '../base/redux';
@@ -9,6 +8,7 @@ import {
9 8
     _STORE_CURRENT_CONFERENCE,
10 9
     _UPDATE_CONFERENCE_DURATION
11 10
 } from './actionTypes';
11
+import { RECENT_LIST_ENABLED } from './featureFlag';
12 12
 
13 13
 const logger = require('jitsi-meet-logger').getLogger(__filename);
14 14
 
@@ -50,17 +50,19 @@ PersistenceRegistry.register(STORE_NAME);
50 50
 ReducerRegistry.register(
51 51
     STORE_NAME,
52 52
     (state = _getLegacyRecentRoomList(), action) => {
53
-        switch (action.type) {
54
-        case APP_WILL_MOUNT:
55
-            return _appWillMount(state);
56
-
57
-        case _STORE_CURRENT_CONFERENCE:
58
-            return _storeCurrentConference(state, action);
59
-
60
-        case _UPDATE_CONFERENCE_DURATION:
61
-            return _updateConferenceDuration(state, action);
62
-
63
-        default:
53
+        if (RECENT_LIST_ENABLED) {
54
+            switch (action.type) {
55
+            case APP_WILL_MOUNT:
56
+                return _appWillMount(state);
57
+            case _STORE_CURRENT_CONFERENCE:
58
+                return _storeCurrentConference(state, action);
59
+
60
+            case _UPDATE_CONFERENCE_DURATION:
61
+                return _updateConferenceDuration(state, action);
62
+            default:
63
+                return state;
64
+            }
65
+        } else {
64 66
             return state;
65 67
         }
66 68
     });

+ 3
- 1
react/features/welcome/components/WelcomePage.web.js Voir le fichier

@@ -8,6 +8,7 @@ import { connect } from 'react-redux';
8 8
 
9 9
 import { translate } from '../../base/i18n';
10 10
 import { Watermarks } from '../../base/react';
11
+import { RecentList } from '../../recent-list';
11 12
 
12 13
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
13 14
 
@@ -99,7 +100,7 @@ class WelcomePage extends AbstractWelcomePage {
99 100
      */
100 101
     render() {
101 102
         const { t } = this.props;
102
-        const { APP_NAME } = interfaceConfig;
103
+        const { APP_NAME, RECENT_LIST_ENABLED } = interfaceConfig;
103 104
         const showAdditionalContent = this._shouldShowAdditionalContent();
104 105
 
105 106
         return (
@@ -146,6 +147,7 @@ class WelcomePage extends AbstractWelcomePage {
146 147
                                 { t('welcomepage.go') }
147 148
                             </Button>
148 149
                         </div>
150
+                        { RECENT_LIST_ENABLED ? <RecentList /> : null }
149 151
                     </div>
150 152
                     { showAdditionalContent
151 153
                         ? <div

Chargement…
Annuler
Enregistrer