浏览代码

[WiP] Calendar integration ui (#3395)

Calendar integration ui
j8
yanas 6 年前
父节点
当前提交
f2cb15ba44
没有帐户链接到提交者的电子邮件

+ 21
- 6
css/_navigate_section_list.scss 查看文件

13
     float: left;
13
     float: left;
14
 }
14
 }
15
 .navigate-section-list-tile {
15
 .navigate-section-list-tile {
16
-    height: 90px;
17
-    width: 260px;
18
-    border-radius: 4px;
19
     background-color: #1754A9;
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
     margin-right: 8px;
22
     margin-right: 8px;
21
     padding: 16px;
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
 .navigate-section-tile-body {
34
 .navigate-section-tile-body {
27
     @extend %navigate-section-list-tile-text;
35
     @extend %navigate-section-list-tile-text;
28
     font-weight: normal;
36
     font-weight: normal;
29
 }
37
 }
38
+.navigate-section-list-tile-info {
39
+    flex: 1;
40
+}
30
 .navigate-section-tile-title {
41
 .navigate-section-tile-title {
31
     @extend %navigate-section-list-tile-text;
42
     @extend %navigate-section-list-tile-text;
32
     font-weight: bold;
43
     font-weight: bold;
40
     position: relative;
51
     position: relative;
41
     margin-top: 36px;
52
     margin-top: 36px;
42
     margin-bottom: 36px;
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 查看文件

45
             font-size: 1rem;
45
             font-size: 1rem;
46
             font-weight: 400;
46
             font-weight: 400;
47
             line-height: 24px;
47
             line-height: 24px;
48
+            margin-bottom: 20px;
48
         }
49
         }
49
 
50
 
50
         #enter_room {
51
         #enter_room {
62
                 width: 100%;
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
     .welcome-page-button {
75
     .welcome-page-button {
68
         font-size: 16px;
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
     .welcome-watermark {
90
     .welcome-watermark {
72
         position: absolute;
91
         position: absolute;
73
         width: 100%;
92
         width: 100%;

+ 1
- 1
interface_config.js 查看文件

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

+ 7
- 4
lang/main.json 查看文件

57
             "video": "Video"
57
             "video": "Video"
58
         },
58
         },
59
         "calendar": "Calendar",
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
         "go": "GO",
62
         "go": "GO",
61
         "join": "JOIN",
63
         "join": "JOIN",
62
         "privacy": "Privacy",
64
         "privacy": "Privacy",
639
         "startWithVideoMuted": "Start with video muted"
641
         "startWithVideoMuted": "Start with video muted"
640
     },
642
     },
641
     "calendarSync": {
643
     "calendarSync": {
642
-        "later": "Later",
643
-        "next": "Upcoming",
644
+        "addMeetingURL": "Add a meeting link",
645
+        "today": "Today",
644
         "nextMeeting": "next meeting",
646
         "nextMeeting": "next meeting",
645
-        "now": "Now",
647
+        "noEvents": "There are no upcoming events scheduled.",
646
         "ongoingMeeting": "ongoing meeting",
648
         "ongoingMeeting": "ongoing meeting",
647
         "permissionButton": "Open settings",
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
     "recentList": {
653
     "recentList": {
651
         "joinPastMeeting": "Join A Past Meeting"
654
         "joinPastMeeting": "Join A Past Meeting"

+ 5
- 0
react/features/base/react/Types.js 查看文件

12
      */
12
      */
13
     colorBase: string,
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
      * Item title
21
      * Item title
17
      */
22
      */

+ 7
- 5
react/features/base/react/components/NavigateSectionList.js 查看文件

87
      */
87
      */
88
     render() {
88
     render() {
89
         const {
89
         const {
90
-            renderListEmptyComponent = this._renderListEmptyComponent,
90
+            renderListEmptyComponent = this._renderListEmptyComponent(),
91
             sections
91
             sections
92
         } = this.props;
92
         } = this.props;
93
 
93
 
128
      * @returns {Function}
128
      * @returns {Function}
129
      */
129
      */
130
     _onPress(url) {
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
     _onRefresh: () => void;
140
     _onRefresh: () => void;

+ 44
- 17
react/features/base/react/components/web/NavigateSectionListItem.js 查看文件

6
 import Text from './Text';
6
 import Text from './Text';
7
 import type { Item } from '../../Types';
7
 import type { Item } from '../../Types';
8
 
8
 
9
+/**
10
+ * The type of the React {@code Component} props of
11
+ * {@link NavigateSectionListItem}.
12
+ */
9
 type Props = {
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
      * Function to be invoked when an item is pressed. The item's URL is passed.
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
      * A item containing data to be rendered
36
      * A item containing data to be rendered
18
      */
37
      */
19
     item: Item
38
     item: Item
20
-}
39
+};
21
 
40
 
22
 /**
41
 /**
23
  * Implements a React/Web {@link Component} for displaying an item in a
42
  * Implements a React/Web {@link Component} for displaying an item in a
25
  *
44
  *
26
  * @extends Component
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
      * Renders the content of this component.
51
      * Renders the content of this component.
31
      *
52
      *
32
      * @returns {ReactElement}
53
      * @returns {ReactElement}
33
      */
54
      */
34
     render() {
55
     render() {
35
-        const { lines, title } = this.props.item;
56
+        const { elementAfter, lines, title } = this.props.item;
36
         const { onPress } = this.props;
57
         const { onPress } = this.props;
37
 
58
 
38
         /**
59
         /**
52
             duration = lines[1];
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
         return (
79
         return (
56
             <Container
80
             <Container
57
-                className = 'navigate-section-list-tile'
81
+                className = { rootClassName }
58
                 onClick = { onPress }>
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
             </Container>
98
             </Container>
72
         );
99
         );
73
     }
100
     }

+ 25
- 17
react/features/base/react/components/web/SectionList.js 查看文件

7
 
7
 
8
 type Props = {
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
      * Used to extract a unique key for a given item at the specified index.
16
      * 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.
17
      * Key is used for caching and as the react key to track item re-ordering.
49
      */
54
      */
50
     render() {
55
     render() {
51
         const {
56
         const {
57
+            ListEmptyComponent,
52
             renderSectionHeader,
58
             renderSectionHeader,
53
             renderItem,
59
             renderItem,
54
             sections,
60
             sections,
56
         } = this.props;
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
         if (sections) {
67
         if (sections) {
62
             return (
68
             return (
63
                 <Container
69
                 <Container
64
                     className = 'navigate-section-list'>
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
                 </Container>
92
                 </Container>
85
             );
93
             );

+ 13
- 0
react/features/calendar-sync/actionTypes.js 查看文件

74
  * @public
74
  * @public
75
  */
75
  */
76
 export const SET_CALENDAR_PROFILE_EMAIL = Symbol('SET_CALENDAR_PROFILE_EMAIL');
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 查看文件

9
     SET_CALENDAR_AUTHORIZATION,
9
     SET_CALENDAR_AUTHORIZATION,
10
     SET_CALENDAR_EVENTS,
10
     SET_CALENDAR_EVENTS,
11
     SET_CALENDAR_INTEGRATION,
11
     SET_CALENDAR_INTEGRATION,
12
-    SET_CALENDAR_PROFILE_EMAIL
12
+    SET_CALENDAR_PROFILE_EMAIL,
13
+    SET_LOADING_CALENDAR_EVENTS
13
 } from './actionTypes';
14
 } from './actionTypes';
14
 import { _getCalendarIntegration, isCalendarEnabled } from './functions';
15
 import { _getCalendarIntegration, isCalendarEnabled } from './functions';
15
 import { generateRoomWithoutSeparator } from '../welcome';
16
 import { generateRoomWithoutSeparator } from '../welcome';
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
  * Sets the calendar integration type to be used by web and signals that the
195
  * Sets the calendar integration type to be used by web and signals that the
178
  * integration is ready to be used.
196
  * integration is ready to be used.
211
             .then(() => dispatch(integration.signIn()))
229
             .then(() => dispatch(integration.signIn()))
212
             .then(() => dispatch(setIntegrationReady(calendarType)))
230
             .then(() => dispatch(setIntegrationReady(calendarType)))
213
             .then(() => dispatch(updateProfile(calendarType)))
231
             .then(() => dispatch(updateProfile(calendarType)))
232
+            .then(() => dispatch(refreshCalendar()))
214
             .catch(error => {
233
             .catch(error => {
215
                 logger.error(
234
                 logger.error(
216
                     'Error occurred while signing into calendar integration',
235
                     'Error occurred while signing into calendar integration',

react/features/calendar-sync/components/MeetingList.native.js → react/features/calendar-sync/components/AbstractCalendarList.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
-import { Text, TouchableOpacity, View } from 'react-native';
5
 import { connect } from 'react-redux';
4
 import { connect } from 'react-redux';
6
 
5
 
7
 import { appNavigate } from '../../app';
6
 import { appNavigate } from '../../app';
8
 import { getLocalizedDateFormatter, translate } from '../../base/i18n';
7
 import { getLocalizedDateFormatter, translate } from '../../base/i18n';
9
 import { NavigateSectionList } from '../../base/react';
8
 import { NavigateSectionList } from '../../base/react';
10
-import { openSettings } from '../../mobile/permissions';
11
 
9
 
12
 import { refreshCalendar } from '../actions';
10
 import { refreshCalendar } from '../actions';
13
 import { isCalendarEnabled } from '../functions';
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
 type Props = {
19
 type Props = {
20
 
20
 
21
-    /**
22
-     * The current state of the calendar access permission.
23
-     */
24
-    _authorization: ?string,
25
-
26
     /**
21
     /**
27
      * The calendar event list.
22
      * The calendar event list.
28
      */
23
      */
38
      */
33
      */
39
     dispatch: Function,
34
     dispatch: Function,
40
 
35
 
36
+    /**
37
+     *
38
+     */
39
+    renderListEmptyComponent: Function,
40
+
41
     /**
41
     /**
42
      * The translate function.
42
      * The translate function.
43
      */
43
      */
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
      * Default values for the component's props.
52
      * Default values for the component's props.
53
      */
53
      */
75
     }
75
     }
76
 
76
 
77
     /**
77
     /**
78
-     * Initializes a new {@code MeetingList} instance.
78
+     * Initializes a new {@code CalendarList} instance.
79
      *
79
      *
80
      * @inheritdoc
80
      * @inheritdoc
81
      */
81
      */
83
         super(props);
83
         super(props);
84
 
84
 
85
         // Bind event handlers so they are only bound once per instance.
85
         // Bind event handlers so they are only bound once per instance.
86
-        this._getRenderListEmptyComponent
87
-            = this._getRenderListEmptyComponent.bind(this);
88
         this._onPress = this._onPress.bind(this);
86
         this._onPress = this._onPress.bind(this);
89
         this._onRefresh = this._onRefresh.bind(this);
87
         this._onRefresh = this._onRefresh.bind(this);
90
         this._toDateString = this._toDateString.bind(this);
88
         this._toDateString = this._toDateString.bind(this);
91
         this._toDisplayableItem = this._toDisplayableItem.bind(this);
89
         this._toDisplayableItem = this._toDisplayableItem.bind(this);
92
         this._toDisplayableList = this._toDisplayableList.bind(this);
90
         this._toDisplayableList = this._toDisplayableList.bind(this);
91
+        this._toTimeString = this._toTimeString.bind(this);
93
     }
92
     }
94
 
93
 
95
     /**
94
     /**
98
      * @inheritdoc
97
      * @inheritdoc
99
      */
98
      */
100
     render() {
99
     render() {
101
-        const { disabled } = this.props;
100
+        const { disabled, renderListEmptyComponent } = this.props;
102
 
101
 
103
         return (
102
         return (
104
             <NavigateSectionList
103
             <NavigateSectionList
106
                 onPress = { this._onPress }
105
                 onPress = { this._onPress }
107
                 onRefresh = { this._onRefresh }
106
                 onRefresh = { this._onRefresh }
108
                 renderListEmptyComponent
107
                 renderListEmptyComponent
109
-                    = { this._getRenderListEmptyComponent() }
108
+                    = { renderListEmptyComponent }
110
                 sections = { this._toDisplayableList() } />
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
     _onPress: string => Function;
113
     _onPress: string => Function;
150
 
114
 
151
     /**
115
     /**
174
     _toDateString: Object => string;
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
      * @param {Object} event - The event.
143
      * @param {Object} event - The event.
180
      * @private
144
      * @private
182
      */
146
      */
183
     _toDateString(event) {
147
     _toDateString(event) {
184
         const startDateTime
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
     _toDisplayableItem: Object => Object;
154
     _toDisplayableItem: Object => Object;
200
      */
162
      */
201
     _toDisplayableItem(event) {
163
     _toDisplayableItem(event) {
202
         return {
164
         return {
165
+            elementAfter: event.url ? undefined : (
166
+                <AddMeetingUrlButton
167
+                    calendarId = { event.calendarId }
168
+                    eventId = { event.id } />
169
+            ),
203
             key: `${event.id}-${event.startDate}`,
170
             key: `${event.id}-${event.startDate}`,
204
             lines: [
171
             lines: [
205
                 event.url,
172
                 event.url,
206
-                this._toDateString(event)
173
+                this._toTimeString(event)
207
             ],
174
             ],
208
             title: event.title,
175
             title: event.title,
209
             url: event.url
176
             url: event.url
221
     _toDisplayableList() {
188
     _toDisplayableList() {
222
         const { _eventList, t } = this.props;
189
         const { _eventList, t } = this.props;
223
 
190
 
224
-        const now = Date.now();
191
+        const now = new Date();
225
 
192
 
226
         const { createSection } = NavigateSectionList;
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
         for (const event of _eventList) {
197
         for (const event of _eventList) {
232
             const displayableEvent = this._toDisplayableItem(event);
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
  *
250
  *
263
  * @param {Object} state - The redux state.
251
  * @param {Object} state - The redux state.
264
  * @returns {{
252
  * @returns {{
265
- *     _authorization: ?string,
266
  *     _eventList: Array<Object>
253
  *     _eventList: Array<Object>
267
  * }}
254
  * }}
268
  */
255
  */
269
 function _mapStateToProps(state: Object) {
256
 function _mapStateToProps(state: Object) {
270
-    const { authorization, events } = state['features/calendar-sync'];
271
-
272
     return {
257
     return {
273
-        _authorization: authorization,
274
-        _eventList: events
258
+        _eventList: state['features/calendar-sync'].events
275
     };
259
     };
276
 }
260
 }
277
 
261
 
278
 export default isCalendarEnabled()
262
 export default isCalendarEnabled()
279
-    ? translate(connect(_mapStateToProps)(MeetingList))
263
+    ? translate(connect(_mapStateToProps)(AbstractCalendarList))
280
     : undefined;
264
     : undefined;

+ 23
- 0
react/features/calendar-sync/components/AddMeetingUrlButton.native.js 查看文件

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 查看文件

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 查看文件

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 查看文件

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 查看文件


+ 1
- 1
react/features/calendar-sync/components/index.js 查看文件

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

+ 1
- 1
react/features/calendar-sync/components/styles.js 查看文件

4
 
4
 
5
 /**
5
 /**
6
  * The styles of the React {@code Component}s of the feature meeting-list i.e.
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
 export default createStyleSheet({
9
 export default createStyleSheet({
10
 
10
 

+ 5
- 1
react/features/calendar-sync/functions.web.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { setLoadingCalendarEvents } from './actions';
3
 export * from './functions.any';
4
 export * from './functions.any';
4
 
5
 
5
 import {
6
 import {
56
         return;
57
         return;
57
     }
58
     }
58
 
59
 
60
+    dispatch(setLoadingCalendarEvents(true));
61
+
59
     dispatch(integration.load())
62
     dispatch(integration.load())
60
         .then(() => dispatch(integration._isSignedIn()))
63
         .then(() => dispatch(integration._isSignedIn()))
61
         .then(signedIn => {
64
         .then(signedIn => {
72
             getState
75
             getState
73
         }, events))
76
         }, events))
74
         .catch(error =>
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 查看文件

10
     SET_CALENDAR_AUTHORIZATION,
10
     SET_CALENDAR_AUTHORIZATION,
11
     SET_CALENDAR_EVENTS,
11
     SET_CALENDAR_EVENTS,
12
     SET_CALENDAR_INTEGRATION,
12
     SET_CALENDAR_INTEGRATION,
13
-    SET_CALENDAR_PROFILE_EMAIL
13
+    SET_CALENDAR_PROFILE_EMAIL,
14
+    SET_LOADING_CALENDAR_EVENTS
14
 } from './actionTypes';
15
 } from './actionTypes';
15
 import { isCalendarEnabled } from './functions';
16
 import { isCalendarEnabled } from './functions';
16
 
17
 
96
 
97
 
97
         case SET_CALENDAR_PROFILE_EMAIL:
98
         case SET_CALENDAR_PROFILE_EMAIL:
98
             return set(state, 'profileEmail', action.email);
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
         return state;
105
         return state;

+ 0
- 159
react/features/recent-list/components/styles.js 查看文件

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 查看文件

2
 
2
 
3
 import Button from '@atlaskit/button';
3
 import Button from '@atlaskit/button';
4
 import { FieldTextStateless } from '@atlaskit/field-text';
4
 import { FieldTextStateless } from '@atlaskit/field-text';
5
+import Tabs from '@atlaskit/tabs';
5
 import { AtlasKitThemeProvider } from '@atlaskit/theme';
6
 import { AtlasKitThemeProvider } from '@atlaskit/theme';
6
 import React from 'react';
7
 import React from 'react';
7
 import { connect } from 'react-redux';
8
 import { connect } from 'react-redux';
8
 
9
 
9
 import { DialogContainer } from '../../base/dialog';
10
 import { DialogContainer } from '../../base/dialog';
10
 import { translate } from '../../base/i18n';
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
 import { RecentList } from '../../recent-list';
14
 import { RecentList } from '../../recent-list';
13
-import { openSettingsDialog } from '../../settings';
15
+import { SettingsButton } from '../../settings';
14
 
16
 
15
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
17
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
16
 
18
 
66
 
68
 
67
         // Bind event handlers so they are only bound once per instance.
69
         // Bind event handlers so they are only bound once per instance.
68
         this._onFormSubmit = this._onFormSubmit.bind(this);
70
         this._onFormSubmit = this._onFormSubmit.bind(this);
69
-        this._onOpenSettings = this._onOpenSettings.bind(this);
70
         this._onRoomChange = this._onRoomChange.bind(this);
71
         this._onRoomChange = this._onRoomChange.bind(this);
71
         this._setAdditionalContentRef
72
         this._setAdditionalContentRef
72
             = this._setAdditionalContentRef.bind(this);
73
             = this._setAdditionalContentRef.bind(this);
159
                                 { t('welcomepage.go') }
160
                                 { t('welcomepage.go') }
160
                             </Button>
161
                             </Button>
161
                         </div>
162
                         </div>
162
-                        <RecentList />
163
+                        { this._renderTabs() }
163
                     </div>
164
                     </div>
164
                     { showAdditionalContent
165
                     { showAdditionalContent
165
                         ? <div
166
                         ? <div
187
         this._onJoin();
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
      * Overrides the super to account for the differences in the argument types
192
      * Overrides the super to account for the differences in the argument types
202
      * provided by HTML and React Native text inputs.
193
      * provided by HTML and React Native text inputs.
211
         super._onRoomChange(event.target.value);
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
      * Sets the internal reference to the HTMLDivElement used to hold the
247
      * Sets the internal reference to the HTMLDivElement used to hold the
216
      * welcome page content.
248
      * welcome page content.

+ 2
- 2
react/features/welcome/components/WelcomePageLists.js 查看文件

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

正在加载...
取消
保存