Ver código fonte

[WiP] Calendar integration ui (#3395)

Calendar integration ui
master
yanas 6 anos atrás
pai
commit
f2cb15ba44
Nenhuma conta vinculada ao e-mail do autor do commit

+ 21
- 6
css/_navigate_section_list.scss Ver arquivo

@@ -13,20 +13,31 @@
13 13
     float: left;
14 14
 }
15 15
 .navigate-section-list-tile {
16
-    height: 90px;
17
-    width: 260px;
18
-    border-radius: 4px;
19 16
     background-color: #1754A9;
17
+    border-radius: 4px;
18
+    box-sizing: border-box;
19
+    display: inline-flex;
20
+    height: 100px;
21
+    margin-bottom: 8px;
20 22
     margin-right: 8px;
21 23
     padding: 16px;
22
-    display: inline-block;
23
-    box-sizing: border-box;
24
-    cursor: pointer;
24
+    width: 100%;
25
+
26
+    &.with-click-handler {
27
+        cursor: pointer;
28
+    }
29
+
30
+    &.with-click-handler:hover {
31
+        background-color: #1a5dbb;
32
+    }
25 33
 }
26 34
 .navigate-section-tile-body {
27 35
     @extend %navigate-section-list-tile-text;
28 36
     font-weight: normal;
29 37
 }
38
+.navigate-section-list-tile-info {
39
+    flex: 1;
40
+}
30 41
 .navigate-section-tile-title {
31 42
     @extend %navigate-section-list-tile-text;
32 43
     font-weight: bold;
@@ -40,4 +51,8 @@
40 51
     position: relative;
41 52
     margin-top: 36px;
42 53
     margin-bottom: 36px;
54
+    width: 100%;
55
+}
56
+.navigate-section-list-empty {
57
+    text-align: center;
43 58
 }

+ 19
- 0
css/_welcome_page.scss Ver arquivo

@@ -45,6 +45,7 @@ body.welcome-page {
45 45
             font-size: 1rem;
46 46
             font-weight: 400;
47 47
             line-height: 24px;
48
+            margin-bottom: 20px;
48 49
         }
49 50
 
50 51
         #enter_room {
@@ -62,12 +63,30 @@ body.welcome-page {
62 63
                 width: 100%;
63 64
             }
64 65
         }
66
+
67
+        .tab-container {
68
+            font-size: 16px;
69
+            position: relative;
70
+            text-align: left;
71
+            width: 650px;
72
+        }
65 73
     }
66 74
 
67 75
     .welcome-page-button {
68 76
         font-size: 16px;
69 77
     }
70 78
 
79
+    .welcome-page-settings {
80
+        color: $welcomePageDescriptionColor;
81
+        position: absolute;
82
+        right: 10px;
83
+        z-index: $zindex2;
84
+
85
+        * {
86
+            cursor: pointer;
87
+        }
88
+    }
89
+
71 90
     .welcome-watermark {
72 91
         position: absolute;
73 92
         width: 100%;

+ 1
- 1
interface_config.js Ver arquivo

@@ -52,7 +52,7 @@ var interfaceConfig = {
52 52
         'tileview'
53 53
     ],
54 54
 
55
-    SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile' ],
55
+    SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
56 56
 
57 57
     // Determines how the video would fit the screen. 'both' would fit the whole
58 58
     // screen, 'height' would fit the original video height to the height of the

+ 7
- 4
lang/main.json Ver arquivo

@@ -57,6 +57,8 @@
57 57
             "video": "Video"
58 58
         },
59 59
         "calendar": "Calendar",
60
+        "connectCalendarText": "Connect your calendar to view all your meetings in __app__. Plus, add __app__ meetings to your calendar and start them with one click.",
61
+        "connectCalendarButton": "Connect your calendar",
60 62
         "go": "GO",
61 63
         "join": "JOIN",
62 64
         "privacy": "Privacy",
@@ -639,13 +641,14 @@
639 641
         "startWithVideoMuted": "Start with video muted"
640 642
     },
641 643
     "calendarSync": {
642
-        "later": "Later",
643
-        "next": "Upcoming",
644
+        "addMeetingURL": "Add a meeting link",
645
+        "today": "Today",
644 646
         "nextMeeting": "next meeting",
645
-        "now": "Now",
647
+        "noEvents": "There are no upcoming events scheduled.",
646 648
         "ongoingMeeting": "ongoing meeting",
647 649
         "permissionButton": "Open settings",
648
-        "permissionMessage": "The Calendar permission is required to see your meetings in the app."
650
+        "permissionMessage": "The Calendar permission is required to see your meetings in the app.",
651
+        "refresh": "Refresh calendar"
649 652
     },
650 653
     "recentList": {
651 654
         "joinPastMeeting": "Join A Past Meeting"

+ 5
- 0
react/features/base/react/Types.js Ver arquivo

@@ -12,6 +12,11 @@ export type Item = {
12 12
      */
13 13
     colorBase: string,
14 14
 
15
+    /**
16
+     * An optional react element to append to the end of the Item.
17
+     */
18
+    elementAfter?: ?ComponentType<any>,
19
+
15 20
     /**
16 21
      * Item title
17 22
      */

+ 7
- 5
react/features/base/react/components/NavigateSectionList.js Ver arquivo

@@ -87,7 +87,7 @@ class NavigateSectionList extends Component<Props> {
87 87
      */
88 88
     render() {
89 89
         const {
90
-            renderListEmptyComponent = this._renderListEmptyComponent,
90
+            renderListEmptyComponent = this._renderListEmptyComponent(),
91 91
             sections
92 92
         } = this.props;
93 93
 
@@ -128,11 +128,13 @@ class NavigateSectionList extends Component<Props> {
128 128
      * @returns {Function}
129 129
      */
130 130
     _onPress(url) {
131
-        return () => {
132
-            const { disabled, onPress } = this.props;
131
+        const { disabled, onPress } = this.props;
133 132
 
134
-            !disabled && url && typeof onPress === 'function' && onPress(url);
135
-        };
133
+        if (!disabled && url && typeof onPress === 'function') {
134
+            return () => onPress(url);
135
+        }
136
+
137
+        return null;
136 138
     }
137 139
 
138 140
     _onRefresh: () => void;

+ 44
- 17
react/features/base/react/components/web/NavigateSectionListItem.js Ver arquivo

@@ -6,18 +6,37 @@ import Container from './Container';
6 6
 import Text from './Text';
7 7
 import type { Item } from '../../Types';
8 8
 
9
+/**
10
+ * The type of the React {@code Component} props of
11
+ * {@link NavigateSectionListItem}.
12
+ */
9 13
 type Props = {
10 14
 
15
+    /**
16
+     * The icon to use for the action button.
17
+     */
18
+    actionIconName: string,
19
+
20
+    /**
21
+     * The function to call when the action button is clicked.
22
+     */
23
+    actionOnClick: ?Function,
24
+
25
+    /**
26
+     * The tooltip to attach to the action button of this list item.
27
+     */
28
+    actionTooltip: string,
29
+
11 30
     /**
12 31
      * Function to be invoked when an item is pressed. The item's URL is passed.
13 32
      */
14
-    onPress: Function,
33
+    onPress: ?Function,
15 34
 
16 35
     /**
17 36
      * A item containing data to be rendered
18 37
      */
19 38
     item: Item
20
-}
39
+};
21 40
 
22 41
 /**
23 42
  * Implements a React/Web {@link Component} for displaying an item in a
@@ -25,14 +44,16 @@ type Props = {
25 44
  *
26 45
  * @extends Component
27 46
  */
28
-export default class NavigateSectionListItem extends Component<Props> {
47
+export default class NavigateSectionListItem<P: Props>
48
+    extends Component<P> {
49
+
29 50
     /**
30 51
      * Renders the content of this component.
31 52
      *
32 53
      * @returns {ReactElement}
33 54
      */
34 55
     render() {
35
-        const { lines, title } = this.props.item;
56
+        const { elementAfter, lines, title } = this.props.item;
36 57
         const { onPress } = this.props;
37 58
 
38 59
         /**
@@ -52,22 +73,28 @@ export default class NavigateSectionListItem extends Component<Props> {
52 73
             duration = lines[1];
53 74
         }
54 75
 
76
+        const rootClassName = `navigate-section-list-tile ${
77
+            onPress ? 'with-click-handler' : 'without-click-handler'}`;
78
+
55 79
         return (
56 80
             <Container
57
-                className = 'navigate-section-list-tile'
81
+                className = { rootClassName }
58 82
                 onClick = { onPress }>
59
-                <Text
60
-                    className = 'navigate-section-tile-title'>
61
-                    { title }
62
-                </Text>
63
-                <Text
64
-                    className = 'navigate-section-tile-body'>
65
-                    { date }
66
-                </Text>
67
-                <Text
68
-                    className = 'navigate-section-tile-body'>
69
-                    { duration }
70
-                </Text>
83
+                <Container className = 'navigate-section-list-tile-info'>
84
+                    <Text
85
+                        className = 'navigate-section-tile-title'>
86
+                        { title }
87
+                    </Text>
88
+                    <Text
89
+                        className = 'navigate-section-tile-body'>
90
+                        { date }
91
+                    </Text>
92
+                    <Text
93
+                        className = 'navigate-section-tile-body'>
94
+                        { duration }
95
+                    </Text>
96
+                </Container>
97
+                { elementAfter || null }
71 98
             </Container>
72 99
         );
73 100
     }

+ 25
- 17
react/features/base/react/components/web/SectionList.js Ver arquivo

@@ -7,6 +7,11 @@ import type { Section } from '../../Types';
7 7
 
8 8
 type Props = {
9 9
 
10
+    /**
11
+     * Rendered when the list is empty. Should be a rendered element.
12
+     */
13
+    ListEmptyComponent: Object,
14
+
10 15
     /**
11 16
      * Used to extract a unique key for a given item at the specified index.
12 17
      * Key is used for caching and as the react key to track item re-ordering.
@@ -49,6 +54,7 @@ export default class SectionList extends Component<Props> {
49 54
      */
50 55
     render() {
51 56
         const {
57
+            ListEmptyComponent,
52 58
             renderSectionHeader,
53 59
             renderItem,
54 60
             sections,
@@ -56,30 +62,32 @@ export default class SectionList extends Component<Props> {
56 62
         } = this.props;
57 63
 
58 64
         /**
59
-         * If there are no recent items we dont want to display anything
65
+         * If there are no recent items we don't want to display anything
60 66
          */
61 67
         if (sections) {
62 68
             return (
63 69
                 <Container
64 70
                     className = 'navigate-section-list'>
65 71
                     {
66
-                        sections.map((section, sectionIndex) => (
67
-                            <Container
68
-                                key = { sectionIndex }>
69
-                                { renderSectionHeader(section) }
70
-                                { section.data
71
-                                    .map((item, listIndex) => {
72
-                                        const listItem = {
73
-                                            item
74
-                                        };
72
+                        sections.length === 0
73
+                            ? ListEmptyComponent
74
+                            : sections.map((section, sectionIndex) => (
75
+                                <Container
76
+                                    key = { sectionIndex }>
77
+                                    { renderSectionHeader(section) }
78
+                                    { section.data
79
+                                        .map((item, listIndex) => {
80
+                                            const listItem = {
81
+                                                item
82
+                                            };
75 83
 
76
-                                        return renderItem(listItem,
77
-                                            keyExtractor(section,
78
-                                                listIndex));
79
-                                    }) }
80
-                            </Container>
81
-                        )
82
-                        )
84
+                                            return renderItem(listItem,
85
+                                                keyExtractor(section,
86
+                                                    listIndex));
87
+                                        }) }
88
+                                </Container>
89
+                            )
90
+                            )
83 91
                     }
84 92
                 </Container>
85 93
             );

+ 13
- 0
react/features/calendar-sync/actionTypes.js Ver arquivo

@@ -74,3 +74,16 @@ export const SET_CALENDAR_AUTH_STATE = Symbol('SET_CALENDAR_AUTH_STATE');
74 74
  * @public
75 75
  */
76 76
 export const SET_CALENDAR_PROFILE_EMAIL = Symbol('SET_CALENDAR_PROFILE_EMAIL');
77
+
78
+/**
79
+ * The type of Redux action which denotes whether a request is in flight to get
80
+ * updated calendar events.
81
+ *
82
+ * {
83
+ *     type: SET_LOADING_CALENDAR_EVENTS,
84
+ *     isLoadingEvents: string
85
+ * }
86
+ * @public
87
+ */
88
+export const SET_LOADING_CALENDAR_EVENTS
89
+    = Symbol('SET_LOADING_CALENDAR_EVENTS');

+ 20
- 1
react/features/calendar-sync/actions.js Ver arquivo

@@ -9,7 +9,8 @@ import {
9 9
     SET_CALENDAR_AUTHORIZATION,
10 10
     SET_CALENDAR_EVENTS,
11 11
     SET_CALENDAR_INTEGRATION,
12
-    SET_CALENDAR_PROFILE_EMAIL
12
+    SET_CALENDAR_PROFILE_EMAIL,
13
+    SET_LOADING_CALENDAR_EVENTS
13 14
 } from './actionTypes';
14 15
 import { _getCalendarIntegration, isCalendarEnabled } from './functions';
15 16
 import { generateRoomWithoutSeparator } from '../welcome';
@@ -173,6 +174,23 @@ export function setCalendarProfileEmail(newEmail: ?string) {
173 174
     };
174 175
 }
175 176
 
177
+/**
178
+ * Sends an to denote a request in is flight to get calendar events.
179
+ *
180
+ * @param {boolean} isLoadingEvents - Whether or not calendar events are being
181
+ * fetched.
182
+ * @returns {{
183
+ *     type: SET_LOADING_CALENDAR_EVENTS,
184
+ *     isLoadingEvents: boolean
185
+ * }}
186
+ */
187
+export function setLoadingCalendarEvents(isLoadingEvents: boolean) {
188
+    return {
189
+        type: SET_LOADING_CALENDAR_EVENTS,
190
+        isLoadingEvents
191
+    };
192
+}
193
+
176 194
 /**
177 195
  * Sets the calendar integration type to be used by web and signals that the
178 196
  * integration is ready to be used.
@@ -211,6 +229,7 @@ export function signIn(calendarType: string): Function {
211 229
             .then(() => dispatch(integration.signIn()))
212 230
             .then(() => dispatch(setIntegrationReady(calendarType)))
213 231
             .then(() => dispatch(updateProfile(calendarType)))
232
+            .then(() => dispatch(refreshCalendar()))
214 233
             .catch(error => {
215 234
                 logger.error(
216 235
                     'Error occurred while signing into calendar integration',

react/features/calendar-sync/components/MeetingList.native.js → react/features/calendar-sync/components/AbstractCalendarList.js Ver arquivo

@@ -1,28 +1,23 @@
1 1
 // @flow
2 2
 
3 3
 import React, { Component } from 'react';
4
-import { Text, TouchableOpacity, View } from 'react-native';
5 4
 import { connect } from 'react-redux';
6 5
 
7 6
 import { appNavigate } from '../../app';
8 7
 import { getLocalizedDateFormatter, translate } from '../../base/i18n';
9 8
 import { NavigateSectionList } from '../../base/react';
10
-import { openSettings } from '../../mobile/permissions';
11 9
 
12 10
 import { refreshCalendar } from '../actions';
13 11
 import { isCalendarEnabled } from '../functions';
14
-import styles from './styles';
12
+
13
+import AddMeetingUrlButton from './AddMeetingUrlButton';
15 14
 
16 15
 /**
17
- * The tyoe of the React {@code Component} props of {@link MeetingList}.
16
+ * The type of the React {@code Component} props of
17
+ * {@link AbstractCalendarList}.
18 18
  */
19 19
 type Props = {
20 20
 
21
-    /**
22
-     * The current state of the calendar access permission.
23
-     */
24
-    _authorization: ?string,
25
-
26 21
     /**
27 22
      * The calendar event list.
28 23
      */
@@ -38,6 +33,11 @@ type Props = {
38 33
      */
39 34
     dispatch: Function,
40 35
 
36
+    /**
37
+     *
38
+     */
39
+    renderListEmptyComponent: Function,
40
+
41 41
     /**
42 42
      * The translate function.
43 43
      */
@@ -45,9 +45,9 @@ type Props = {
45 45
 };
46 46
 
47 47
 /**
48
- * Component to display a list of events from the (mobile) user's calendar.
48
+ * Component to display a list of events from a connected calendar.
49 49
  */
50
-class MeetingList extends Component<Props> {
50
+class AbstractCalendarList extends Component<Props> {
51 51
     /**
52 52
      * Default values for the component's props.
53 53
      */
@@ -75,7 +75,7 @@ class MeetingList extends Component<Props> {
75 75
     }
76 76
 
77 77
     /**
78
-     * Initializes a new {@code MeetingList} instance.
78
+     * Initializes a new {@code CalendarList} instance.
79 79
      *
80 80
      * @inheritdoc
81 81
      */
@@ -83,13 +83,12 @@ class MeetingList extends Component<Props> {
83 83
         super(props);
84 84
 
85 85
         // Bind event handlers so they are only bound once per instance.
86
-        this._getRenderListEmptyComponent
87
-            = this._getRenderListEmptyComponent.bind(this);
88 86
         this._onPress = this._onPress.bind(this);
89 87
         this._onRefresh = this._onRefresh.bind(this);
90 88
         this._toDateString = this._toDateString.bind(this);
91 89
         this._toDisplayableItem = this._toDisplayableItem.bind(this);
92 90
         this._toDisplayableList = this._toDisplayableList.bind(this);
91
+        this._toTimeString = this._toTimeString.bind(this);
93 92
     }
94 93
 
95 94
     /**
@@ -98,7 +97,7 @@ class MeetingList extends Component<Props> {
98 97
      * @inheritdoc
99 98
      */
100 99
     render() {
101
-        const { disabled } = this.props;
100
+        const { disabled, renderListEmptyComponent } = this.props;
102 101
 
103 102
         return (
104 103
             <NavigateSectionList
@@ -106,46 +105,11 @@ class MeetingList extends Component<Props> {
106 105
                 onPress = { this._onPress }
107 106
                 onRefresh = { this._onRefresh }
108 107
                 renderListEmptyComponent
109
-                    = { this._getRenderListEmptyComponent() }
108
+                    = { renderListEmptyComponent }
110 109
                 sections = { this._toDisplayableList() } />
111 110
         );
112 111
     }
113 112
 
114
-    _getRenderListEmptyComponent: () => Object;
115
-
116
-    /**
117
-     * Returns a list empty component if a custom one has to be rendered instead
118
-     * of the default one in the {@link NavigateSectionList}.
119
-     *
120
-     * @private
121
-     * @returns {?React$Component}
122
-     */
123
-    _getRenderListEmptyComponent() {
124
-        const { _authorization, t } = this.props;
125
-
126
-        // If we don't provide a list specific renderListEmptyComponent, then
127
-        // the default empty component of the NavigateSectionList will be
128
-        // rendered, which (atm) is a simple "Pull to refresh" message.
129
-        if (_authorization !== 'denied') {
130
-            return undefined;
131
-        }
132
-
133
-        return (
134
-            <View style = { styles.noPermissionMessageView }>
135
-                <Text style = { styles.noPermissionMessageText }>
136
-                    { t('calendarSync.permissionMessage') }
137
-                </Text>
138
-                <TouchableOpacity
139
-                    onPress = { openSettings }
140
-                    style = { styles.noPermissionMessageButton } >
141
-                    <Text style = { styles.noPermissionMessageButtonText }>
142
-                        { t('calendarSync.permissionButton') }
143
-                    </Text>
144
-                </TouchableOpacity>
145
-            </View>
146
-        );
147
-    }
148
-
149 113
     _onPress: string => Function;
150 114
 
151 115
     /**
@@ -174,7 +138,7 @@ class MeetingList extends Component<Props> {
174 138
     _toDateString: Object => string;
175 139
 
176 140
     /**
177
-     * Generates a date (interval) string for a given event.
141
+     * Generates a date string for a given event.
178 142
      *
179 143
      * @param {Object} event - The event.
180 144
      * @private
@@ -182,11 +146,9 @@ class MeetingList extends Component<Props> {
182 146
      */
183 147
     _toDateString(event) {
184 148
         const startDateTime
185
-            = getLocalizedDateFormatter(event.startDate).format('lll');
186
-        const endTime
187
-            = getLocalizedDateFormatter(event.endDate).format('LT');
149
+            = getLocalizedDateFormatter(event.startDate).format('MMM Do, YYYY');
188 150
 
189
-        return `${startDateTime} - ${endTime}`;
151
+        return `${startDateTime}`;
190 152
     }
191 153
 
192 154
     _toDisplayableItem: Object => Object;
@@ -200,10 +162,15 @@ class MeetingList extends Component<Props> {
200 162
      */
201 163
     _toDisplayableItem(event) {
202 164
         return {
165
+            elementAfter: event.url ? undefined : (
166
+                <AddMeetingUrlButton
167
+                    calendarId = { event.calendarId }
168
+                    eventId = { event.id } />
169
+            ),
203 170
             key: `${event.id}-${event.startDate}`,
204 171
             lines: [
205 172
                 event.url,
206
-                this._toDateString(event)
173
+                this._toTimeString(event)
207 174
             ],
208 175
             title: event.title,
209 176
             url: event.url
@@ -221,39 +188,60 @@ class MeetingList extends Component<Props> {
221 188
     _toDisplayableList() {
222 189
         const { _eventList, t } = this.props;
223 190
 
224
-        const now = Date.now();
191
+        const now = new Date();
225 192
 
226 193
         const { createSection } = NavigateSectionList;
227
-        const nowSection = createSection(t('calendarSync.now'), 'now');
228
-        const nextSection = createSection(t('calendarSync.next'), 'next');
229
-        const laterSection = createSection(t('calendarSync.later'), 'later');
194
+        const TODAY_SECTION = 'today';
195
+        const sectionMap = new Map();
230 196
 
231 197
         for (const event of _eventList) {
232 198
             const displayableEvent = this._toDisplayableItem(event);
199
+            const startDate = new Date(event.startDate).getDate();
200
+
201
+            if (startDate === now.getDate()) {
202
+                let todaySection = sectionMap.get(TODAY_SECTION);
203
+
204
+                if (!todaySection) {
205
+                    todaySection
206
+                        = createSection(t('calendarSync.today'), TODAY_SECTION);
207
+                    sectionMap.set(TODAY_SECTION, todaySection);
208
+                }
209
+
210
+                todaySection.data.push(displayableEvent);
211
+            } else if (sectionMap.has(startDate)) {
212
+                const section = sectionMap.get(startDate);
233 213
 
234
-            if (event.startDate < now && event.endDate > now) {
235
-                nowSection.data.push(displayableEvent);
236
-            } else if (event.startDate > now) {
237
-                if (nextSection.data.length
238
-                        && nextSection.data[0].startDate !== event.startDate) {
239
-                    laterSection.data.push(displayableEvent);
240
-                } else {
241
-                    nextSection.data.push(displayableEvent);
214
+                if (section) {
215
+                    section.data.push(displayableEvent);
242 216
                 }
217
+            } else {
218
+                const newSection
219
+                    = createSection(this._toDateString(event), startDate);
220
+
221
+                sectionMap.set(startDate, newSection);
222
+                newSection.data.push(displayableEvent);
243 223
             }
244 224
         }
245 225
 
246
-        const sectionList = [];
226
+        return Array.from(sectionMap.values());
227
+    }
247 228
 
248
-        for (const section of [
249
-            nowSection,
250
-            nextSection,
251
-            laterSection
252
-        ]) {
253
-            section.data.length && sectionList.push(section);
254
-        }
229
+    _toTimeString: Object => string;
255 230
 
256
-        return sectionList;
231
+    /**
232
+     * Generates a time (interval) string for a given event.
233
+     *
234
+     * @param {Object} event - The event.
235
+     * @private
236
+     * @returns {string}
237
+     */
238
+    _toTimeString(event) {
239
+        const startDateTime
240
+            = getLocalizedDateFormatter(event.startDate).format('lll');
241
+        const endTime
242
+            = getLocalizedDateFormatter(event.endDate).format('LT');
243
+
244
+        return `${startDateTime} - ${endTime}`;
257 245
     }
258 246
 }
259 247
 
@@ -262,19 +250,15 @@ class MeetingList extends Component<Props> {
262 250
  *
263 251
  * @param {Object} state - The redux state.
264 252
  * @returns {{
265
- *     _authorization: ?string,
266 253
  *     _eventList: Array<Object>
267 254
  * }}
268 255
  */
269 256
 function _mapStateToProps(state: Object) {
270
-    const { authorization, events } = state['features/calendar-sync'];
271
-
272 257
     return {
273
-        _authorization: authorization,
274
-        _eventList: events
258
+        _eventList: state['features/calendar-sync'].events
275 259
     };
276 260
 }
277 261
 
278 262
 export default isCalendarEnabled()
279
-    ? translate(connect(_mapStateToProps)(MeetingList))
263
+    ? translate(connect(_mapStateToProps)(AbstractCalendarList))
280 264
     : undefined;

+ 23
- 0
react/features/calendar-sync/components/AddMeetingUrlButton.native.js Ver arquivo

@@ -0,0 +1,23 @@
1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+/**
6
+ * A React Component for adding a meeting URL to an existing calendar meeting.
7
+ *
8
+ * @extends Component
9
+ */
10
+class AddMeetingUrlButton extends Component<*> {
11
+    /**
12
+     * Implements React's {@link Component#render}.
13
+     *
14
+     * @inheritdoc
15
+     */
16
+    render() {
17
+        // Not yet implemented.
18
+
19
+        return null;
20
+    }
21
+}
22
+
23
+export default AddMeetingUrlButton;

+ 86
- 0
react/features/calendar-sync/components/AddMeetingUrlButton.web.js Ver arquivo

@@ -0,0 +1,86 @@
1
+// @flow
2
+
3
+import Button from '@atlaskit/button';
4
+import React, { Component } from 'react';
5
+import { connect } from 'react-redux';
6
+
7
+import { translate } from '../../base/i18n';
8
+
9
+import { updateCalendarEvent } from '../actions';
10
+
11
+/**
12
+ * The type of the React {@code Component} props of {@link AddMeetingUrlButton}.
13
+ */
14
+type Props = {
15
+
16
+    /**
17
+     * The calendar ID associated with the calendar event.
18
+     */
19
+    calendarId: string,
20
+
21
+    /**
22
+     * Invoked to add a meeting URL to a calendar event.
23
+     */
24
+    dispatch: Dispatch<*>,
25
+
26
+    /**
27
+     * The ID of the calendar event that will have a meeting URL added on click.
28
+     */
29
+    eventId: string,
30
+
31
+    /**
32
+     * Invoked to obtain translated strings.
33
+     */
34
+    t: Function
35
+};
36
+
37
+/**
38
+ * A React Component for adding a meeting URL to an existing calendar event.
39
+ *
40
+ * @extends Component
41
+ */
42
+class AddMeetingUrlButton extends Component<Props> {
43
+    /**
44
+     * Initializes a new {@code AddMeetingUrlButton} instance.
45
+     *
46
+     * @inheritdoc
47
+     */
48
+    constructor(props: Props) {
49
+        super(props);
50
+
51
+        // Bind event handler so it is only bound once for every instance.
52
+        this._onClick = this._onClick.bind(this);
53
+    }
54
+
55
+    /**
56
+     * Implements React's {@link Component#render}.
57
+     *
58
+     * @inheritdoc
59
+     */
60
+    render() {
61
+        return (
62
+            <Button
63
+                appearance = 'primary'
64
+                onClick = { this._onClick }
65
+                type = 'button'>
66
+                { this.props.t('calendarSync.addMeetingURL') }
67
+            </Button>
68
+        );
69
+    }
70
+
71
+    _onClick: () => void;
72
+
73
+    /**
74
+     * Dispatches an action to adding a meeting URL to a calendar event.
75
+     *
76
+     * @returns {void}
77
+     */
78
+    _onClick() {
79
+        const { calendarId, dispatch, eventId } = this.props;
80
+
81
+        dispatch(updateCalendarEvent(eventId, calendarId));
82
+    }
83
+}
84
+
85
+export default translate(connect()(AddMeetingUrlButton));
86
+

+ 126
- 0
react/features/calendar-sync/components/CalendarList.native.js Ver arquivo

@@ -0,0 +1,126 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { Text, TouchableOpacity, View } from 'react-native';
5
+import { connect } from 'react-redux';
6
+
7
+import { openSettings } from '../../mobile/permissions';
8
+import { translate } from '../../base/i18n';
9
+
10
+import { isCalendarEnabled } from '../functions';
11
+import styles from './styles';
12
+
13
+import AbstractCalendarList from './AbstractCalendarList';
14
+
15
+/**
16
+ * The tyoe of the React {@code Component} props of {@link CalendarList}.
17
+ */
18
+type Props = {
19
+
20
+    /**
21
+     * The current state of the calendar access permission.
22
+     */
23
+    _authorization: ?string,
24
+
25
+    /**
26
+     * Indicates if the list is disabled or not.
27
+     */
28
+    disabled: boolean,
29
+
30
+    /**
31
+     * The translate function.
32
+     */
33
+    t: Function
34
+};
35
+
36
+/**
37
+ * Component to display a list of events from the (mobile) user's calendar.
38
+ */
39
+class CalendarList extends Component<Props> {
40
+    /**
41
+     * Initializes a new {@code CalendarList} instance.
42
+     *
43
+     * @inheritdoc
44
+     */
45
+    constructor(props) {
46
+        super(props);
47
+
48
+        // Bind event handlers so they are only bound once per instance.
49
+        this._getRenderListEmptyComponent
50
+            = this._getRenderListEmptyComponent.bind(this);
51
+    }
52
+
53
+    /**
54
+     * Implements React's {@link Component#render}.
55
+     *
56
+     * @inheritdoc
57
+     */
58
+    render() {
59
+        const { disabled } = this.props;
60
+
61
+        return (
62
+            AbstractCalendarList
63
+                ? <AbstractCalendarList
64
+                    disabled = { disabled }
65
+                    renderListEmptyComponent
66
+                        = { this._getRenderListEmptyComponent() } />
67
+                : null
68
+        );
69
+    }
70
+
71
+    _getRenderListEmptyComponent: () => Object;
72
+
73
+    /**
74
+     * Returns a list empty component if a custom one has to be rendered instead
75
+     * of the default one in the {@link NavigateSectionList}.
76
+     *
77
+     * @private
78
+     * @returns {?React$Component}
79
+     */
80
+    _getRenderListEmptyComponent() {
81
+        const { _authorization, t } = this.props;
82
+
83
+        // If we don't provide a list specific renderListEmptyComponent, then
84
+        // the default empty component of the NavigateSectionList will be
85
+        // rendered, which (atm) is a simple "Pull to refresh" message.
86
+        if (_authorization !== 'denied') {
87
+            return undefined;
88
+        }
89
+
90
+        return (
91
+            <View style = { styles.noPermissionMessageView }>
92
+                <Text style = { styles.noPermissionMessageText }>
93
+                    { t('calendarSync.permissionMessage') }
94
+                </Text>
95
+                <TouchableOpacity
96
+                    onPress = { openSettings }
97
+                    style = { styles.noPermissionMessageButton } >
98
+                    <Text style = { styles.noPermissionMessageButtonText }>
99
+                        { t('calendarSync.permissionButton') }
100
+                    </Text>
101
+                </TouchableOpacity>
102
+            </View>
103
+        );
104
+    }
105
+}
106
+
107
+/**
108
+ * Maps redux state to component props.
109
+ *
110
+ * @param {Object} state - The redux state.
111
+ * @returns {{
112
+ *     _authorization: ?string,
113
+ *     _eventList: Array<Object>
114
+ * }}
115
+ */
116
+function _mapStateToProps(state: Object) {
117
+    const { authorization } = state['features/calendar-sync'];
118
+
119
+    return {
120
+        _authorization: authorization
121
+    };
122
+}
123
+
124
+export default isCalendarEnabled()
125
+    ? translate(connect(_mapStateToProps)(CalendarList))
126
+    : undefined;

+ 194
- 0
react/features/calendar-sync/components/CalendarList.web.js Ver arquivo

@@ -0,0 +1,194 @@
1
+// @flow
2
+
3
+import Button from '@atlaskit/button';
4
+import Spinner from '@atlaskit/spinner';
5
+import React, { Component } from 'react';
6
+import { connect } from 'react-redux';
7
+
8
+import { translate } from '../../base/i18n';
9
+import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
10
+
11
+import { refreshCalendar } from '../actions';
12
+import { isCalendarEnabled } from '../functions';
13
+
14
+import AbstractCalendarList from './AbstractCalendarList';
15
+
16
+declare var interfaceConfig: Object;
17
+
18
+/**
19
+ * The type of the React {@code Component} props of {@link CalendarList}.
20
+ */
21
+type Props = {
22
+
23
+    /**
24
+     * Whether or not a calendar may be connected for fetching calendar events.
25
+     */
26
+    _hasIntegrationSelected: boolean,
27
+
28
+    /**
29
+     * Whether or not events have been fetched from a calendar.
30
+     */
31
+    _hasLoadedEvents: boolean,
32
+
33
+    /**
34
+     * Indicates if the list is disabled or not.
35
+     */
36
+    disabled: boolean,
37
+
38
+    /**
39
+     * The Redux dispatch function.
40
+     */
41
+    dispatch: Function,
42
+
43
+    /**
44
+     * The translate function.
45
+     */
46
+    t: Function
47
+};
48
+
49
+/**
50
+ * Component to display a list of events from the user's calendar.
51
+ */
52
+class CalendarList extends Component<Props> {
53
+    /**
54
+     * Initializes a new {@code CalendarList} instance.
55
+     *
56
+     * @inheritdoc
57
+     */
58
+    constructor(props) {
59
+        super(props);
60
+
61
+        // Bind event handlers so they are only bound once per instance.
62
+        this._getRenderListEmptyComponent
63
+            = this._getRenderListEmptyComponent.bind(this);
64
+        this._onOpenSettings = this._onOpenSettings.bind(this);
65
+        this._onRefreshEvents = this._onRefreshEvents.bind(this);
66
+    }
67
+
68
+    /**
69
+     * Implements React's {@link Component#render}.
70
+     *
71
+     * @inheritdoc
72
+     */
73
+    render() {
74
+        const { disabled } = this.props;
75
+
76
+        return (
77
+            AbstractCalendarList
78
+                ? <AbstractCalendarList
79
+                    disabled = { disabled }
80
+                    renderListEmptyComponent
81
+                        = { this._getRenderListEmptyComponent() } />
82
+                : null
83
+        );
84
+    }
85
+
86
+    _getRenderListEmptyComponent: () => Object;
87
+
88
+    /**
89
+     * Returns a list empty component if a custom one has to be rendered instead
90
+     * of the default one in the {@link NavigateSectionList}.
91
+     *
92
+     * @private
93
+     * @returns {React$Component}
94
+     */
95
+    _getRenderListEmptyComponent() {
96
+        const { _hasIntegrationSelected, _hasLoadedEvents, t } = this.props;
97
+
98
+        if (_hasIntegrationSelected && _hasLoadedEvents) {
99
+            return (
100
+                <div className = 'navigate-section-list-empty'>
101
+                    <div>{ t('calendarSync.noEvents') }</div>
102
+                    <Button
103
+                        appearance = 'primary'
104
+                        className = 'calendar-button'
105
+                        id = 'connect_calendar_button'
106
+                        onClick = { this._onRefreshEvents }
107
+                        type = 'button'>
108
+                        { t('calendarSync.refresh') }
109
+                    </Button>
110
+                </div>
111
+            );
112
+        } else if (_hasIntegrationSelected && !_hasLoadedEvents) {
113
+            return (
114
+                <div className = 'navigate-section-list-empty'>
115
+                    <Spinner
116
+                        invertColor = { true }
117
+                        isCompleting = { false }
118
+                        size = 'medium' />
119
+                </div>
120
+            );
121
+        }
122
+
123
+        return (
124
+            <div className = 'navigate-section-list-empty'>
125
+                <p className = 'header-text-description'>
126
+                    { t('welcomepage.connectCalendarText', {
127
+                        app: interfaceConfig.APP_NAME
128
+                    }) }
129
+                </p>
130
+                <Button
131
+                    appearance = 'primary'
132
+                    className = 'calendar-button'
133
+                    id = 'connect_calendar_button'
134
+                    onClick = { this._onOpenSettings }
135
+                    type = 'button'>
136
+                    { t('welcomepage.connectCalendarButton') }
137
+                </Button>
138
+            </div>
139
+        );
140
+    }
141
+
142
+    _onOpenSettings: () => void;
143
+
144
+    /**
145
+     * Opens {@code SettingsDialog}.
146
+     *
147
+     * @private
148
+     * @returns {void}
149
+     */
150
+    _onOpenSettings() {
151
+        this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR));
152
+    }
153
+
154
+    _onRefreshEvents: () => void;
155
+
156
+
157
+    /**
158
+     * Gets an updated list of calendar events.
159
+     *
160
+     * @private
161
+     * @returns {void}
162
+     */
163
+    _onRefreshEvents() {
164
+        this.props.dispatch(refreshCalendar(true));
165
+    }
166
+}
167
+
168
+/**
169
+ * Maps (parts of) the Redux state to the associated props for the
170
+ * {@code CalendarList} component.
171
+ *
172
+ * @param {Object} state - The Redux state.
173
+ * @private
174
+ * @returns {{
175
+ *     _hasIntegrationSelected: boolean,
176
+ *     _hasLoadedEvents: boolean
177
+ * }}
178
+ */
179
+function _mapStateToProps(state) {
180
+    const {
181
+        events,
182
+        integrationType,
183
+        isLoadingEvents
184
+    } = state['features/calendar-sync'];
185
+
186
+    return {
187
+        _hasIntegrationSelected: Boolean(integrationType),
188
+        _hasLoadedEvents: Boolean(events) || !isLoadingEvents
189
+    };
190
+}
191
+
192
+export default isCalendarEnabled()
193
+    ? translate(connect(_mapStateToProps)(CalendarList))
194
+    : undefined;

+ 0
- 0
react/features/calendar-sync/components/MeetingList.web.js Ver arquivo


+ 1
- 1
react/features/calendar-sync/components/index.js Ver arquivo

@@ -1,3 +1,3 @@
1 1
 export { default as ConferenceNotification } from './ConferenceNotification';
2
-export { default as MeetingList } from './MeetingList';
2
+export { default as CalendarList } from './CalendarList';
3 3
 export { default as MicrosoftSignInButton } from './MicrosoftSignInButton';

+ 1
- 1
react/features/calendar-sync/components/styles.js Ver arquivo

@@ -4,7 +4,7 @@ const NOTIFICATION_SIZE = 55;
4 4
 
5 5
 /**
6 6
  * The styles of the React {@code Component}s of the feature meeting-list i.e.
7
- * {@code MeetingList}.
7
+ * {@code CalendarList}.
8 8
  */
9 9
 export default createStyleSheet({
10 10
 

+ 5
- 1
react/features/calendar-sync/functions.web.js Ver arquivo

@@ -1,5 +1,6 @@
1 1
 // @flow
2 2
 
3
+import { setLoadingCalendarEvents } from './actions';
3 4
 export * from './functions.any';
4 5
 
5 6
 import {
@@ -56,6 +57,8 @@ export function _fetchCalendarEntries(
56 57
         return;
57 58
     }
58 59
 
60
+    dispatch(setLoadingCalendarEvents(true));
61
+
59 62
     dispatch(integration.load())
60 63
         .then(() => dispatch(integration._isSignedIn()))
61 64
         .then(signedIn => {
@@ -72,7 +75,8 @@ export function _fetchCalendarEntries(
72 75
             getState
73 76
         }, events))
74 77
         .catch(error =>
75
-            logger.error('Error fetching calendar.', error));
78
+            logger.error('Error fetching calendar.', error))
79
+        .then(() => dispatch(setLoadingCalendarEvents(false)));
76 80
 }
77 81
 
78 82
 /**

+ 5
- 1
react/features/calendar-sync/reducer.js Ver arquivo

@@ -10,7 +10,8 @@ import {
10 10
     SET_CALENDAR_AUTHORIZATION,
11 11
     SET_CALENDAR_EVENTS,
12 12
     SET_CALENDAR_INTEGRATION,
13
-    SET_CALENDAR_PROFILE_EMAIL
13
+    SET_CALENDAR_PROFILE_EMAIL,
14
+    SET_LOADING_CALENDAR_EVENTS
14 15
 } from './actionTypes';
15 16
 import { isCalendarEnabled } from './functions';
16 17
 
@@ -96,6 +97,9 @@ isCalendarEnabled()
96 97
 
97 98
         case SET_CALENDAR_PROFILE_EMAIL:
98 99
             return set(state, 'profileEmail', action.email);
100
+
101
+        case SET_LOADING_CALENDAR_EVENTS:
102
+            return set(state, 'isLoadingEvents', action.isLoadingEvents);
99 103
         }
100 104
 
101 105
         return state;

+ 0
- 159
react/features/recent-list/components/styles.js Ver arquivo

@@ -1,159 +0,0 @@
1
-import { createStyleSheet, BoxModel } from '../../base/styles';
2
-
3
-const AVATAR_OPACITY = 0.4;
4
-
5
-const AVATAR_SIZE = 65;
6
-
7
-const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
8
-
9
-export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
10
-
11
-/**
12
- * The styles of the React {@code Component}s of the feature recent-list i.e.
13
- * {@code RecentList}.
14
- */
15
-export default createStyleSheet({
16
-
17
-    /**
18
-     * The style of the actual avatar.
19
-     */
20
-    avatar: {
21
-        alignItems: 'center',
22
-        backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
23
-        borderRadius: AVATAR_SIZE,
24
-        height: AVATAR_SIZE,
25
-        justifyContent: 'center',
26
-        width: AVATAR_SIZE
27
-    },
28
-
29
-    /**
30
-     * The style of the avatar container that makes the avatar rounded.
31
-     */
32
-    avatarContainer: {
33
-        alignItems: 'center',
34
-        flexDirection: 'row',
35
-        justifyContent: 'space-around',
36
-        paddingTop: 5
37
-    },
38
-
39
-    /**
40
-     * Simple {@code Text} content of the avatar (the actual initials).
41
-     */
42
-    avatarContent: {
43
-        backgroundColor: 'rgba(0, 0, 0, 0)',
44
-        color: OVERLAY_FONT_COLOR,
45
-        fontSize: 32,
46
-        fontWeight: '100',
47
-        textAlign: 'center'
48
-    },
49
-
50
-    /**
51
-     * List of styles of the avatar of a remote meeting (not the default
52
-     * server). The number of colors are limited because they should match
53
-     * nicely.
54
-     */
55
-    avatarRemoteServer1: {
56
-        backgroundColor: `rgba(232, 105, 156, ${AVATAR_OPACITY})`
57
-    },
58
-
59
-    avatarRemoteServer2: {
60
-        backgroundColor: `rgba(255, 198, 115, ${AVATAR_OPACITY})`
61
-    },
62
-
63
-    avatarRemoteServer3: {
64
-        backgroundColor: `rgba(128, 128, 255, ${AVATAR_OPACITY})`
65
-    },
66
-
67
-    avatarRemoteServer4: {
68
-        backgroundColor: `rgba(105, 232, 194, ${AVATAR_OPACITY})`
69
-    },
70
-
71
-    avatarRemoteServer5: {
72
-        backgroundColor: `rgba(234, 255, 128, ${AVATAR_OPACITY})`
73
-    },
74
-
75
-    /**
76
-     * The style of the conference length (if rendered).
77
-     */
78
-    confLength: {
79
-        color: OVERLAY_FONT_COLOR,
80
-        fontWeight: 'normal'
81
-    },
82
-
83
-    /**
84
-     * The top level container style of the list.
85
-     */
86
-    container: {
87
-        flex: 1
88
-    },
89
-
90
-    /**
91
-     * Shows the container disabled.
92
-     */
93
-    containerDisabled: {
94
-        opacity: 0.2
95
-    },
96
-
97
-    /**
98
-     * Second line of the list (date). May be extended with server name later.
99
-     */
100
-    date: {
101
-        color: OVERLAY_FONT_COLOR
102
-    },
103
-
104
-    /**
105
-     * The style of the details container (right side) of the list.
106
-     */
107
-    detailsContainer: {
108
-        alignItems: 'flex-start',
109
-        flex: 1,
110
-        flexDirection: 'column',
111
-        justifyContent: 'center',
112
-        marginLeft: 2 * BoxModel.margin
113
-    },
114
-
115
-    /**
116
-     * The container for an info line with an inline icon.
117
-     */
118
-    infoWithIcon: {
119
-        alignItems: 'center',
120
-        flexDirection: 'row',
121
-        justifyContent: 'flex-start'
122
-    },
123
-
124
-    /**
125
-     * The style of an inline icon in an info line.
126
-     */
127
-    inlineIcon: {
128
-        color: OVERLAY_FONT_COLOR,
129
-        marginRight: 5
130
-    },
131
-
132
-    /**
133
-     * First line of the list (room name).
134
-     */
135
-    roomName: {
136
-        color: OVERLAY_FONT_COLOR,
137
-        fontSize: 18,
138
-        fontWeight: 'bold'
139
-    },
140
-
141
-    /**
142
-     * The style of one single row in the list.
143
-     */
144
-    row: {
145
-        alignItems: 'center',
146
-        flex: 1,
147
-        flexDirection: 'row',
148
-        padding: 8,
149
-        paddingBottom: 0
150
-    },
151
-
152
-    /**
153
-     * The style of the server name component (if rendered).
154
-     */
155
-    serverName: {
156
-        color: OVERLAY_FONT_COLOR,
157
-        fontWeight: 'normal'
158
-    }
159
-});

+ 46
- 14
react/features/welcome/components/WelcomePage.web.js Ver arquivo

@@ -2,15 +2,17 @@
2 2
 
3 3
 import Button from '@atlaskit/button';
4 4
 import { FieldTextStateless } from '@atlaskit/field-text';
5
+import Tabs from '@atlaskit/tabs';
5 6
 import { AtlasKitThemeProvider } from '@atlaskit/theme';
6 7
 import React from 'react';
7 8
 import { connect } from 'react-redux';
8 9
 
9 10
 import { DialogContainer } from '../../base/dialog';
10 11
 import { translate } from '../../base/i18n';
11
-import { Watermarks } from '../../base/react';
12
+import { Platform, Watermarks } from '../../base/react';
13
+import { CalendarList } from '../../calendar-sync';
12 14
 import { RecentList } from '../../recent-list';
13
-import { openSettingsDialog } from '../../settings';
15
+import { SettingsButton } from '../../settings';
14 16
 
15 17
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
16 18
 
@@ -66,7 +68,6 @@ class WelcomePage extends AbstractWelcomePage {
66 68
 
67 69
         // Bind event handlers so they are only bound once per instance.
68 70
         this._onFormSubmit = this._onFormSubmit.bind(this);
69
-        this._onOpenSettings = this._onOpenSettings.bind(this);
70 71
         this._onRoomChange = this._onRoomChange.bind(this);
71 72
         this._setAdditionalContentRef
72 73
             = this._setAdditionalContentRef.bind(this);
@@ -159,7 +160,7 @@ class WelcomePage extends AbstractWelcomePage {
159 160
                                 { t('welcomepage.go') }
160 161
                             </Button>
161 162
                         </div>
162
-                        <RecentList />
163
+                        { this._renderTabs() }
163 164
                     </div>
164 165
                     { showAdditionalContent
165 166
                         ? <div
@@ -187,16 +188,6 @@ class WelcomePage extends AbstractWelcomePage {
187 188
         this._onJoin();
188 189
     }
189 190
 
190
-    /**
191
-     * Opens {@code SettingsDialog}.
192
-     *
193
-     * @private
194
-     * @returns {void}
195
-     */
196
-    _onOpenSettings() {
197
-        this.props.dispatch(openSettingsDialog());
198
-    }
199
-
200 191
     /**
201 192
      * Overrides the super to account for the differences in the argument types
202 193
      * provided by HTML and React Native text inputs.
@@ -211,6 +202,47 @@ class WelcomePage extends AbstractWelcomePage {
211 202
         super._onRoomChange(event.target.value);
212 203
     }
213 204
 
205
+    /**
206
+     * Renders tabs to show previous meetings and upcoming calendar events. The
207
+     * tabs are purposefully hidden on mobile browsers.
208
+     *
209
+     * @returns {ReactElement|null}
210
+     */
211
+    _renderTabs() {
212
+        const isMobileBrowser
213
+            = Platform.OS === 'android' || Platform.OS === 'ios';
214
+
215
+        if (isMobileBrowser) {
216
+            return null;
217
+        }
218
+
219
+        const { t } = this.props;
220
+
221
+        const tabs = [];
222
+
223
+        if (CalendarList) {
224
+            tabs.push({
225
+                label: t('welcomepage.calendar'),
226
+                content: <CalendarList />,
227
+                defaultSelected: true
228
+            });
229
+        }
230
+
231
+        tabs.push({
232
+            label: t('welcomepage.recentList'),
233
+            content: <RecentList />,
234
+            defaultSelected: !CalendarList
235
+        });
236
+
237
+        return (
238
+            <div className = 'tab-container' >
239
+                <div className = 'welcome-page-settings'>
240
+                    <SettingsButton />
241
+                </div>
242
+                <Tabs tabs = { tabs } />
243
+            </div>);
244
+    }
245
+
214 246
     /**
215 247
      * Sets the internal reference to the HTMLDivElement used to hold the
216 248
      * welcome page content.

+ 2
- 2
react/features/welcome/components/WelcomePageLists.js Ver arquivo

@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
6 6
 
7 7
 import { translate } from '../../base/i18n';
8 8
 import { PagedList } from '../../base/react';
9
-import { MeetingList } from '../../calendar-sync';
9
+import { CalendarList } from '../../calendar-sync';
10 10
 import { RecentList } from '../../recent-list';
11 11
 
12 12
 import { setWelcomePageListsDefaultPage } from '../actions';
@@ -82,7 +82,7 @@ class WelcomePageLists extends Component<Props> {
82 82
                 title: t('welcomepage.recentList')
83 83
             },
84 84
             {
85
-                component: MeetingList,
85
+                component: CalendarList,
86 86
                 icon: android ? 'event_note' : IOS_CALENDAR_ICON,
87 87
                 title: t('welcomepage.calendar')
88 88
             }

Carregando…
Cancelar
Salvar