Sfoglia il codice sorgente

feat(welcome-page): Redesign. (#3559)

* feat(welcome-page): Redesign.

* Style adjustments.
master
Hristo Terezov 6 anni fa
parent
commit
b30008e3a5

+ 117
- 0
css/_meetings_list.scss Vedi File

@@ -0,0 +1,117 @@
1
+.meetings-list {
2
+    font-size: 14px;
3
+    color: #253858;
4
+    line-height: 20px;
5
+    text-align: left;
6
+    text-overflow: ellipsis;
7
+    display: flex;
8
+    flex-direction: column;
9
+    position: relative;
10
+    width: 100%;
11
+    height: 100%;
12
+    overflow: auto;
13
+
14
+    .meetings-list-empty {
15
+        text-align: center;
16
+        align-items: center;
17
+        justify-content: center;
18
+        display: flex;
19
+        flex-grow: 1;
20
+        flex-direction: column;
21
+
22
+        .description {
23
+            font-size: 16px;
24
+            padding: 20px;
25
+        }
26
+    }
27
+
28
+    .button {
29
+        background: #0074E0;
30
+        border-radius: 4px;
31
+        color: #FFFFFF;
32
+        display: flex;
33
+        justify-content: center;
34
+        align-items: center;
35
+        padding: 5px 10px;
36
+        cursor: pointer;
37
+    }
38
+
39
+    .item {
40
+        background: rgba(255,255,255,0.50);
41
+        box-sizing: border-box;
42
+        display: inline-flex;
43
+        margin-top: 5px;
44
+        min-height: 92px;
45
+        width: 100%;
46
+        word-break: break-word;
47
+        display: flex;
48
+        flex-direction: row;
49
+        text-align: left;
50
+
51
+        &:first-child {
52
+            margin-top: 0px;
53
+        }
54
+
55
+        .left-column {
56
+            display: flex;
57
+            flex-direction: column;
58
+            width: 140px;
59
+            flex-grow: 0;
60
+            padding-left: 30px;
61
+            padding-top: 25px;
62
+
63
+            .date {
64
+                font-weight: bold;
65
+                padding-bottom: 5px;
66
+            }
67
+        }
68
+
69
+        .right-column {
70
+            display: flex;
71
+            flex-direction: column;
72
+            flex-grow: 1;
73
+            padding-left: 30px;
74
+            padding-top: 25px;
75
+
76
+            .title {
77
+                font-size: 16px;
78
+                font-weight: bold;
79
+                padding-bottom: 5px;
80
+            }
81
+        }
82
+
83
+        .actions {
84
+            display: flex;
85
+            align-items: center;
86
+            justify-content: center;
87
+            flex-grow: 0;
88
+            padding-right: 30px;
89
+        }
90
+
91
+        &.with-click-handler {
92
+            cursor: pointer;
93
+        }
94
+
95
+        &.with-click-handler:hover {
96
+            background-color: #75A7E7;
97
+        }
98
+
99
+        .add-button {
100
+            width: 30px;
101
+            height: 30px;
102
+            padding: 0px;
103
+        }
104
+
105
+        i {
106
+            cursor: inherit;
107
+        }
108
+
109
+        .join-button {
110
+            display: none;
111
+        }
112
+
113
+        &:hover .join-button {
114
+            display: block
115
+        }
116
+    }
117
+}

+ 2
- 2
css/_variables.scss Vedi File

@@ -144,7 +144,7 @@ $watermarkHeight: 74px;
144 144
 /**
145 145
  * Welcome page variables.
146 146
  */
147
-$welcomePageDescriptionColor: #E6EDFA;
147
+$welcomePageDescriptionColor: #fff;
148 148
 $welcomePageFontFamily: inherit;
149
-$welcomePageHeaderBackground: #1D69D4;
149
+$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
150 150
 $welcomePageTitleColor: #fff;

+ 90
- 14
css/_welcome_page.scss Vedi File

@@ -4,7 +4,7 @@ body.welcome-page {
4 4
 }
5 5
 
6 6
 .welcome {
7
-    background-color: $welcomePageHeaderBackground;
7
+    background-image: $welcomePageHeaderBackground;
8 8
     display: flex;
9 9
     flex-direction: column;
10 10
     font-family: $welcomePageFontFamily;
@@ -24,8 +24,8 @@ body.welcome-page {
24 24
         .header-text {
25 25
             display: flex;
26 26
             flex-direction: column;
27
-            margin-top: $watermarkHeight + 80;
28
-            margin-bottom: 36px;
27
+            margin-top: $watermarkHeight + 35;
28
+            margin-bottom: 35px;
29 29
             max-width: calc(100% - 40px);
30 30
             width: 650px;
31 31
             z-index: $zindex2;
@@ -35,7 +35,6 @@ body.welcome-page {
35 35
             color: $welcomePageTitleColor;
36 36
             font-size: 2.5rem;
37 37
             font-weight: 500;
38
-            letter-spacing: 0;
39 38
             line-height: 1.18;
40 39
             margin-bottom: 16px;
41 40
         }
@@ -49,41 +48,118 @@ body.welcome-page {
49 48
         }
50 49
 
51 50
         #enter_room {
52
-            align-items: center;
53 51
             display: flex;
52
+            align-items: center;
54 53
             max-width: calc(100% - 40px);
55
-            margin-bottom: 20px;
56
-            position: relative;
57
-            width: 650px;
54
+            width: 680px;
58 55
             z-index: $zindex2;
56
+            background-color: #fff;
57
+            padding: 25px 30px;
59 58
 
60
-            .enter-room-input {
61
-                display: inline-block;
62
-                margin-right: 8px;
59
+            .enter-room-input-container {
63 60
                 width: 100%;
61
+                padding-right: 8px;
62
+                padding-bottom: 5px;
63
+                text-align: left;
64
+                color: #253858;
65
+                height: fit-content;
66
+                border-width: 0px 0px 2px 0px;
67
+                border-style: solid;
68
+                border-image: linear-gradient(to right, #dee1e6, #fff) 1;
69
+
70
+                .enter-room-title {
71
+                    font-size: 18px;
72
+                    font-weight: bold;
73
+                    padding-bottom: 5px;
74
+                }
75
+
76
+                .enter-room-input {
77
+                    border: none;
78
+                    display: inline-block;
79
+                    width: 100%;
80
+                    font-size: 14px;
81
+                }
82
+
83
+                ::placeholder {
84
+                    color: #253858;
85
+                }
64 86
             }
87
+
65 88
         }
66 89
 
67 90
         .tab-container {
68 91
             font-size: 16px;
69 92
             position: relative;
70 93
             text-align: left;
71
-            width: 650px;
94
+            min-height: 354px;
95
+            width: 710px;
96
+            background: #75A7E7;
97
+            display: flex;
98
+            flex-direction: column;
99
+
100
+            .tab-content{
101
+                margin: 5px 0px;
102
+                overflow: hidden;
103
+                flex-grow: 1;
104
+                position: relative;
105
+
106
+                > * {
107
+                    position: absolute;
108
+                }
109
+            }
110
+
111
+            .tab-buttons {
112
+                font-size: 18px;
113
+                color: #FFFFFF;
114
+                display: flex;
115
+                flex-grow: 0;
116
+                flex-direction: row;
117
+                min-height: 54px;
118
+                width: 100%;
119
+
120
+                .tab {
121
+                    text-align: center;
122
+                    background: rgba(9,30,66,0.37);
123
+                    height: 55px;
124
+                    line-height: 54px;
125
+                    flex-grow: 1;
126
+                    cursor: pointer;
127
+
128
+                    &.selected, &:hover {
129
+                        background: rgba(9,30,66,0.71);
130
+                    }
131
+
132
+                    &:last-child {
133
+                        margin-left: 1px;
134
+                    }
135
+                }
136
+            }
72 137
         }
73 138
     }
74 139
 
75 140
     .welcome-page-button {
76
-        font-size: 16px;
141
+        width: 51px;
142
+        height: 35px;
143
+        font-size: 14px;
144
+        background: #0074E0;
145
+        border-radius: 4px;
146
+        color: #FFFFFF;
147
+        text-align: center;
148
+        vertical-align: middle;
149
+        line-height: 35px;
150
+        cursor: pointer;
77 151
     }
78 152
 
79 153
     .welcome-page-settings {
80 154
         color: $welcomePageDescriptionColor;
81 155
         position: absolute;
82
-        right: 10px;
156
+        top: 32px;
157
+        right: 32px;
83 158
         z-index: $zindex2;
84 159
 
85 160
         * {
86 161
             cursor: pointer;
162
+            font-size: 32px;
87 163
         }
88 164
     }
89 165
 

+ 1
- 0
css/main.scss Vedi File

@@ -76,6 +76,7 @@
76 76
 @import 'modals/invite/add-people';
77 77
 @import 'deep-linking/main';
78 78
 @import 'transcription-subtitles';
79
+@import '_meetings_list.scss';
79 80
 @import 'navigate_section_list';
80 81
 @import 'third-party-branding/google';
81 82
 @import 'third-party-branding/microsoft';

+ 1
- 0
lang/main.json Vedi File

@@ -59,6 +59,7 @@
59 59
         "calendar": "Calendar",
60 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 61
         "connectCalendarButton": "Connect your calendar",
62
+        "enterRoomTitle": "Start a new meeting",
62 63
         "go": "GO",
63 64
         "join": "JOIN",
64 65
         "privacy": "Privacy",

+ 201
- 0
react/features/base/react/components/web/MeetingsList.js Vedi File

@@ -0,0 +1,201 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import {
6
+    getLocalizedDateFormatter,
7
+    getLocalizedDurationFormatter
8
+} from '../../../i18n';
9
+
10
+import Container from './Container';
11
+import Text from './Text';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * Indicates if the list is disabled or not.
17
+     */
18
+    disabled: boolean,
19
+
20
+    /**
21
+     * Indicates if the URL should be hidden or not.
22
+     */
23
+    hideURL: boolean,
24
+
25
+    /**
26
+     * Function to be invoked when an item is pressed. The item's URL is passed.
27
+     */
28
+    onPress: Function,
29
+
30
+    /**
31
+     * Rendered when the list is empty. Should be a rendered element.
32
+     */
33
+    listEmptyComponent: Object,
34
+
35
+    /**
36
+     * An array of meetings.
37
+     */
38
+    meetings: Array<Object>,
39
+
40
+    /**
41
+     * Defines what happens when  an item in the section list is clicked
42
+     */
43
+    onItemClick: Function
44
+};
45
+
46
+/**
47
+ * Generates a date string for a given date.
48
+ *
49
+ * @param {Object} date - The date.
50
+ * @private
51
+ * @returns {string}
52
+ */
53
+function _toDateString(date) {
54
+    return getLocalizedDateFormatter(date).format('MMM Do, YYYY');
55
+}
56
+
57
+
58
+/**
59
+ * Generates a time (interval) string for a given times.
60
+ *
61
+ * @param {Array<Date>} times - Array of times.
62
+ * @private
63
+ * @returns {string}
64
+ */
65
+function _toTimeString(times) {
66
+    if (times && times.length > 0) {
67
+        return (
68
+            times
69
+                .map(time => getLocalizedDateFormatter(time).format('LT'))
70
+                .join(' - '));
71
+    }
72
+
73
+    return undefined;
74
+}
75
+
76
+/**
77
+ * Implements a React/Web {@link Component} for displaying a list with
78
+ * meetings.
79
+ *
80
+ * @extends Component
81
+ */
82
+export default class MeetingsList extends Component<Props> {
83
+    /**
84
+     * Constructor of the MeetingsList component.
85
+     *
86
+     * @inheritdoc
87
+     */
88
+    constructor(props: Props) {
89
+        super(props);
90
+
91
+        this._onPress = this._onPress.bind(this);
92
+        this._renderItem = this._renderItem.bind(this);
93
+    }
94
+
95
+    /**
96
+     * Renders the content of this component.
97
+     *
98
+     * @returns {React.ReactNode}
99
+     */
100
+    render() {
101
+        const { listEmptyComponent, meetings } = this.props;
102
+
103
+        /**
104
+         * If there are no recent meetings we don't want to display anything
105
+         */
106
+        if (meetings) {
107
+            return (
108
+                <Container
109
+                    className = 'meetings-list'>
110
+                    {
111
+                        meetings.length === 0
112
+                            ? listEmptyComponent
113
+                            : meetings.map(this._renderItem)
114
+                    }
115
+                </Container>
116
+            );
117
+        }
118
+
119
+        return null;
120
+    }
121
+
122
+    _onPress: string => Function;
123
+
124
+    /**
125
+     * Returns a function that is used in the onPress callback of the items.
126
+     *
127
+     * @param {string} url - The URL of the item to navigate to.
128
+     * @private
129
+     * @returns {Function}
130
+     */
131
+    _onPress(url) {
132
+        const { disabled, onPress } = this.props;
133
+
134
+        if (!disabled && url && typeof onPress === 'function') {
135
+            return () => onPress(url);
136
+        }
137
+
138
+        return null;
139
+    }
140
+
141
+    _renderItem: (Object, number) => React$Node;
142
+
143
+    /**
144
+     * Renders an item for the list.
145
+     *
146
+     * @param {Object} meeting - Information about the meeting.
147
+     * @param {number} index - The index of the item.
148
+     * @returns {Node}
149
+     */
150
+    _renderItem(meeting, index) {
151
+        const {
152
+            date,
153
+            duration,
154
+            elementAfter,
155
+            time,
156
+            title,
157
+            url
158
+        } = meeting;
159
+        const { hideURL = false } = this.props;
160
+        const onPress = this._onPress(url);
161
+        const rootClassName
162
+            = `item ${
163
+                onPress ? 'with-click-handler' : 'without-click-handler'}`;
164
+
165
+        return (
166
+            <Container
167
+                className = { rootClassName }
168
+                key = { index }
169
+                onClick = { onPress }>
170
+                <Container className = 'left-column'>
171
+                    <Text className = 'date'>
172
+                        { _toDateString(date) }
173
+                    </Text>
174
+                    <Text>
175
+                        { _toTimeString(time) }
176
+                    </Text>
177
+                </Container>
178
+                <Container className = 'right-column'>
179
+                    <Text className = 'title'>
180
+                        { title }
181
+                    </Text>
182
+                    {
183
+                        hideURL || !url ? null : (
184
+                            <Text>
185
+                                { url }
186
+                            </Text>)
187
+                    }
188
+                    {
189
+                        typeof duration === 'number' ? (
190
+                            <Text>
191
+                                { getLocalizedDurationFormatter(duration) }
192
+                            </Text>) : null
193
+                    }
194
+                </Container>
195
+                <Container className = 'actions'>
196
+                    { elementAfter || null }
197
+                </Container>
198
+            </Container>
199
+        );
200
+    }
201
+}

+ 1
- 0
react/features/base/react/components/web/index.js Vedi File

@@ -1,5 +1,6 @@
1 1
 export { default as Container } from './Container';
2 2
 export { default as LoadingIndicator } from './LoadingIndicator';
3
+export { default as MeetingsList } from './MeetingsList';
3 4
 export { default as MultiSelectAutocomplete } from './MultiSelectAutocomplete';
4 5
 export { default as NavigateSectionListEmptyComponent } from
5 6
     './NavigateSectionListEmptyComponent';

+ 4
- 7
react/features/calendar-sync/components/AddMeetingUrlButton.web.js Vedi File

@@ -1,6 +1,5 @@
1 1
 // @flow
2 2
 
3
-import Button from '@atlaskit/button';
4 3
 import React, { Component } from 'react';
5 4
 import { connect } from 'react-redux';
6 5
 import Tooltip from '@atlaskit/tooltip';
@@ -65,12 +64,11 @@ class AddMeetingUrlButton extends Component<Props> {
65 64
     render() {
66 65
         return (
67 66
             <Tooltip content = { this.props.t('calendarSync.addMeetingURL') }>
68
-                <Button
69
-                    appearance = 'primary'
70
-                    onClick = { this._onClick }
71
-                    type = 'button'>
67
+                <div
68
+                    className = 'button add-button'
69
+                    onClick = { this._onClick }>
72 70
                     <i className = { 'icon-add' } />
73
-                </Button>
71
+                </div>
74 72
             </Tooltip>
75 73
         );
76 74
     }
@@ -92,4 +90,3 @@ class AddMeetingUrlButton extends Component<Props> {
92 90
 }
93 91
 
94 92
 export default translate(connect()(AddMeetingUrlButton));
95
-

+ 1
- 1
react/features/calendar-sync/components/CalendarList.native.js Vedi File

@@ -79,7 +79,7 @@ class CalendarList extends AbstractPage<Props> {
79 79
             CalendarListContent
80 80
                 ? <CalendarListContent
81 81
                     disabled = { disabled }
82
-                    renderListEmptyComponent
82
+                    listEmptyComponent
83 83
                         = { this._getRenderListEmptyComponent() } />
84 84
                 : null
85 85
         );

+ 13
- 20
react/features/calendar-sync/components/CalendarList.web.js Vedi File

@@ -1,6 +1,5 @@
1 1
 // @flow
2 2
 
3
-import Button from '@atlaskit/button';
4 3
 import Spinner from '@atlaskit/spinner';
5 4
 import React from 'react';
6 5
 import { connect } from 'react-redux';
@@ -82,7 +81,7 @@ class CalendarList extends AbstractPage<Props> {
82 81
             CalendarListContent
83 82
                 ? <CalendarListContent
84 83
                     disabled = { disabled }
85
-                    renderListEmptyComponent
84
+                    listEmptyComponent
86 85
                         = { this._getRenderListEmptyComponent() } />
87 86
                 : null
88 87
         );
@@ -102,21 +101,18 @@ class CalendarList extends AbstractPage<Props> {
102 101
 
103 102
         if (_hasIntegrationSelected && _hasLoadedEvents) {
104 103
             return (
105
-                <div className = 'navigate-section-list-empty'>
104
+                <div className = 'meetings-list-empty'>
106 105
                     <div>{ t('calendarSync.noEvents') }</div>
107
-                    <Button
108
-                        appearance = 'primary'
109
-                        className = 'calendar-button'
110
-                        id = 'connect_calendar_button'
111
-                        onClick = { this._onRefreshEvents }
112
-                        type = 'button'>
106
+                    <div
107
+                        className = 'button'
108
+                        onClick = { this._onRefreshEvents }>
113 109
                         { t('calendarSync.refresh') }
114
-                    </Button>
110
+                    </div>
115 111
                 </div>
116 112
             );
117 113
         } else if (_hasIntegrationSelected && !_hasLoadedEvents) {
118 114
             return (
119
-                <div className = 'navigate-section-list-empty'>
115
+                <div className = 'meetings-list-empty'>
120 116
                     <Spinner
121 117
                         invertColor = { true }
122 118
                         isCompleting = { false }
@@ -126,20 +122,17 @@ class CalendarList extends AbstractPage<Props> {
126 122
         }
127 123
 
128 124
         return (
129
-            <div className = 'navigate-section-list-empty'>
130
-                <p className = 'header-text-description'>
125
+            <div className = 'meetings-list-empty'>
126
+                <p className = 'description'>
131 127
                     { t('welcomepage.connectCalendarText', {
132 128
                         app: interfaceConfig.APP_NAME
133 129
                     }) }
134 130
                 </p>
135
-                <Button
136
-                    appearance = 'primary'
137
-                    className = 'calendar-button'
138
-                    id = 'connect_calendar_button'
139
-                    onClick = { this._onOpenSettings }
140
-                    type = 'button'>
131
+                <div
132
+                    className = 'button'
133
+                    onClick = { this._onOpenSettings }>
141 134
                     { t('welcomepage.connectCalendarButton') }
142
-                </Button>
135
+                </div>
143 136
             </div>
144 137
         );
145 138
     }

react/features/calendar-sync/components/CalendarListContent.js → react/features/calendar-sync/components/CalendarListContent.native.js Vedi File

@@ -15,8 +15,6 @@ import { NavigateSectionList } from '../../base/react';
15 15
 import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
16 16
 import { isCalendarEnabled } from '../functions';
17 17
 
18
-import AddMeetingUrlButton from './AddMeetingUrlButton';
19
-import JoinButton from './JoinButton';
20 18
 
21 19
 /**
22 20
  * The type of the React {@code Component} props of
@@ -42,7 +40,7 @@ type Props = {
42 40
     /**
43 41
      *
44 42
      */
45
-    renderListEmptyComponent: Function,
43
+    listEmptyComponent: React$Node,
46 44
 
47 45
     /**
48 46
      * The translate function.
@@ -70,7 +68,6 @@ class CalendarListContent extends Component<Props> {
70 68
         super(props);
71 69
 
72 70
         // Bind event handlers so they are only bound once per instance.
73
-        this._onJoinPress = this._onJoinPress.bind(this);
74 71
         this._onPress = this._onPress.bind(this);
75 72
         this._onRefresh = this._onRefresh.bind(this);
76 73
         this._onSecondaryAction = this._onSecondaryAction.bind(this);
@@ -97,7 +94,7 @@ class CalendarListContent extends Component<Props> {
97 94
      * @inheritdoc
98 95
      */
99 96
     render() {
100
-        const { disabled, renderListEmptyComponent } = this.props;
97
+        const { disabled, listEmptyComponent } = this.props;
101 98
 
102 99
         return (
103 100
             <NavigateSectionList
@@ -106,27 +103,11 @@ class CalendarListContent extends Component<Props> {
106 103
                 onRefresh = { this._onRefresh }
107 104
                 onSecondaryAction = { this._onSecondaryAction }
108 105
                 renderListEmptyComponent
109
-                    = { renderListEmptyComponent }
106
+                    = { listEmptyComponent }
110 107
                 sections = { this._toDisplayableList() } />
111 108
         );
112 109
     }
113 110
 
114
-    _onJoinPress: (Object, string) => Function;
115
-
116
-    /**
117
-     * Handles the list's navigate action.
118
-     *
119
-     * @private
120
-     * @param {Object} event - The click event.
121
-     * @param {string} url - The url string to navigate to.
122
-     * @returns {void}
123
-     */
124
-    _onJoinPress(event, url) {
125
-        event.stopPropagation();
126
-
127
-        this._onPress(url, 'calendar.meeting.join');
128
-    }
129
-
130 111
     _onPress: (string, string) => Function;
131 112
 
132 113
     /**
@@ -197,13 +178,6 @@ class CalendarListContent extends Component<Props> {
197 178
      */
198 179
     _toDisplayableItem(event) {
199 180
         return {
200
-            elementAfter: event.url
201
-                ? <JoinButton
202
-                    onPress = { this._onJoinPress }
203
-                    url = { event.url } />
204
-                : (<AddMeetingUrlButton
205
-                    calendarId = { event.calendarId }
206
-                    eventId = { event.id } />),
207 181
             id: event.id,
208 182
             key: `${event.id}-${event.startDate}`,
209 183
             lines: [

+ 177
- 0
react/features/calendar-sync/components/CalendarListContent.web.js Vedi File

@@ -0,0 +1,177 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { appNavigate } from '../../app';
7
+import {
8
+    createCalendarClickedEvent,
9
+    createCalendarSelectedEvent,
10
+    sendAnalytics
11
+} from '../../analytics';
12
+import { MeetingsList } from '../../base/react';
13
+
14
+import { isCalendarEnabled } from '../functions';
15
+
16
+import AddMeetingUrlButton from './AddMeetingUrlButton';
17
+import JoinButton from './JoinButton';
18
+
19
+/**
20
+ * The type of the React {@code Component} props of
21
+ * {@link CalendarListContent}.
22
+ */
23
+type Props = {
24
+
25
+    /**
26
+     * The calendar event list.
27
+     */
28
+    _eventList: Array<Object>,
29
+
30
+    /**
31
+     * Indicates if the list is disabled or not.
32
+     */
33
+    disabled: boolean,
34
+
35
+    /**
36
+     * The Redux dispatch function.
37
+     */
38
+    dispatch: Function,
39
+
40
+    /**
41
+     *
42
+     */
43
+    listEmptyComponent: React$Node,
44
+};
45
+
46
+/**
47
+ * Component to display a list of events from a connected calendar.
48
+ */
49
+class CalendarListContent extends Component<Props> {
50
+    /**
51
+     * Default values for the component's props.
52
+     */
53
+    static defaultProps = {
54
+        _eventList: []
55
+    };
56
+
57
+    /**
58
+     * Initializes a new {@code CalendarListContent} instance.
59
+     *
60
+     * @inheritdoc
61
+     */
62
+    constructor(props) {
63
+        super(props);
64
+
65
+        // Bind event handlers so they are only bound once per instance.
66
+        this._onJoinPress = this._onJoinPress.bind(this);
67
+        this._onPress = this._onPress.bind(this);
68
+        this._toDisplayableItem = this._toDisplayableItem.bind(this);
69
+    }
70
+
71
+    /**
72
+     * Implements React's {@link Component#componentDidMount()}. Invoked
73
+     * immediately after this component is mounted.
74
+     *
75
+     * @inheritdoc
76
+     * @returns {void}
77
+     */
78
+    componentDidMount() {
79
+        sendAnalytics(createCalendarSelectedEvent());
80
+    }
81
+
82
+    /**
83
+     * Implements React's {@link Component#render}.
84
+     *
85
+     * @inheritdoc
86
+     */
87
+    render() {
88
+        const { disabled, listEmptyComponent } = this.props;
89
+        const { _eventList = [] } = this.props;
90
+        const meetings = _eventList.map(this._toDisplayableItem);
91
+
92
+        return (
93
+            <MeetingsList
94
+                disabled = { disabled }
95
+                listEmptyComponent = { listEmptyComponent }
96
+                meetings = { meetings }
97
+                onPress = { this._onPress } />
98
+        );
99
+    }
100
+
101
+    _onJoinPress: (Object, string) => Function;
102
+
103
+    /**
104
+     * Handles the list's navigate action.
105
+     *
106
+     * @private
107
+     * @param {Object} event - The click event.
108
+     * @param {string} url - The url string to navigate to.
109
+     * @returns {void}
110
+     */
111
+    _onJoinPress(event, url) {
112
+        event.stopPropagation();
113
+
114
+        this._onPress(url, 'calendar.meeting.join');
115
+    }
116
+
117
+    _onPress: (string, string) => Function;
118
+
119
+    /**
120
+     * Handles the list's navigate action.
121
+     *
122
+     * @private
123
+     * @param {string} url - The url string to navigate to.
124
+     * @param {string} analyticsEventName - Тhe name of the analytics event.
125
+     * associated with this action.
126
+     * @returns {void}
127
+     */
128
+    _onPress(url, analyticsEventName = 'calendar.meeting.tile') {
129
+        sendAnalytics(createCalendarClickedEvent(analyticsEventName));
130
+
131
+        this.props.dispatch(appNavigate(url));
132
+    }
133
+
134
+    _toDisplayableItem: Object => Object;
135
+
136
+    /**
137
+     * Creates a displayable object from an event.
138
+     *
139
+     * @param {Object} event - The calendar event.
140
+     * @private
141
+     * @returns {Object}
142
+     */
143
+    _toDisplayableItem(event) {
144
+        return {
145
+            elementAfter: event.url
146
+                ? <JoinButton
147
+                    onPress = { this._onJoinPress }
148
+                    url = { event.url } />
149
+                : (<AddMeetingUrlButton
150
+                    calendarId = { event.calendarId }
151
+                    eventId = { event.id } />),
152
+            date: event.startDate,
153
+            time: [ event.startDate, event.endDate ],
154
+            description: event.url,
155
+            title: event.title,
156
+            url: event.url
157
+        };
158
+    }
159
+}
160
+
161
+/**
162
+ * Maps redux state to component props.
163
+ *
164
+ * @param {Object} state - The redux state.
165
+ * @returns {{
166
+ *     _eventList: Array<Object>
167
+ * }}
168
+ */
169
+function _mapStateToProps(state: Object) {
170
+    return {
171
+        _eventList: state['features/calendar-sync'].events
172
+    };
173
+}
174
+
175
+export default isCalendarEnabled()
176
+    ? connect(_mapStateToProps)(CalendarListContent)
177
+    : undefined;

+ 4
- 8
react/features/calendar-sync/components/JoinButton.web.js Vedi File

@@ -1,6 +1,5 @@
1 1
 // @flow
2 2
 
3
-import Button from '@atlaskit/button';
4 3
 import React, { Component } from 'react';
5 4
 import Tooltip from '@atlaskit/tooltip';
6 5
 
@@ -58,13 +57,11 @@ class JoinButton extends Component<Props> {
58 57
         return (
59 58
             <Tooltip
60 59
                 content = { t('calendarSync.joinTooltip') }>
61
-                <Button
62
-                    appearance = 'primary'
63
-                    className = 'join-button'
64
-                    onClick = { this._onClick }
65
-                    type = 'button'>
60
+                <div
61
+                    className = 'button join-button'
62
+                    onClick = { this._onClick }>
66 63
                     { t('calendarSync.join') }
67
-                </Button>
64
+                </div>
68 65
             </Tooltip>
69 66
         );
70 67
     }
@@ -84,4 +81,3 @@ class JoinButton extends Component<Props> {
84 81
 }
85 82
 
86 83
 export default translate(JoinButton);
87
-

+ 102
- 0
react/features/recent-list/components/AbstractRecentList.js Vedi File

@@ -0,0 +1,102 @@
1
+// @flow
2
+import React from 'react';
3
+
4
+import {
5
+    createRecentClickedEvent,
6
+    createRecentSelectedEvent,
7
+    sendAnalytics
8
+} from '../../analytics';
9
+import { appNavigate } from '../../app';
10
+import {
11
+    AbstractPage,
12
+    Container,
13
+    Text
14
+} from '../../base/react';
15
+
16
+import styles from './styles';
17
+
18
+/**
19
+ * The type of the React {@code Component} props of {@link AbstractRecentList}
20
+ */
21
+type Props = {
22
+
23
+    /**
24
+     * The redux store's {@code dispatch} function.
25
+     */
26
+    dispatch: Dispatch<*>,
27
+
28
+    /**
29
+     * The translate function.
30
+     */
31
+    t: Function
32
+};
33
+
34
+/**
35
+ * An abstract component for the recent list.
36
+ *
37
+ */
38
+export default class AbstractRecentList<P: Props> extends AbstractPage<P> {
39
+    /**
40
+     * Initializes a new {@code RecentList} instance.
41
+     *
42
+     * @inheritdoc
43
+     */
44
+    constructor(props: P) {
45
+        super(props);
46
+
47
+        this._onPress = this._onPress.bind(this);
48
+    }
49
+
50
+    /**
51
+     * Implements React's {@link Component#componentDidMount()}. Invoked
52
+     * immediately after this component is mounted.
53
+     *
54
+     * @inheritdoc
55
+     * @returns {void}
56
+     */
57
+    componentDidMount() {
58
+        sendAnalytics(createRecentSelectedEvent());
59
+    }
60
+
61
+    _getRenderListEmptyComponent: () => React$Node;
62
+
63
+    /**
64
+     * Returns a list empty component if a custom one has to be rendered instead
65
+     * of the default one in the {@link NavigateSectionList}.
66
+     *
67
+     * @private
68
+     * @returns {React$Component}
69
+     */
70
+    _getRenderListEmptyComponent() {
71
+        const { t } = this.props;
72
+
73
+        return (
74
+            <Container
75
+                className = 'meetings-list-empty'
76
+                style = { styles.emptyListContainer }>
77
+                <Text
78
+                    className = 'description'
79
+                    style = { styles.emptyListText }>
80
+                    { t('welcomepage.recentListEmpty') }
81
+                </Text>
82
+            </Container>
83
+        );
84
+    }
85
+
86
+    _onPress: string => {};
87
+
88
+    /**
89
+     * Handles the list's navigate action.
90
+     *
91
+     * @private
92
+     * @param {string} url - The url string to navigate to.
93
+     * @returns {void}
94
+     */
95
+    _onPress(url) {
96
+        const { dispatch } = this.props;
97
+
98
+        sendAnalytics(createRecentClickedEvent('recent.meeting.tile'));
99
+
100
+        dispatch(appNavigate(url));
101
+    }
102
+}

react/features/recent-list/components/RecentList.js → react/features/recent-list/components/RecentList.native.js Vedi File

@@ -2,25 +2,14 @@
2 2
 import React from 'react';
3 3
 import { connect } from 'react-redux';
4 4
 
5
-import {
6
-    createRecentClickedEvent,
7
-    createRecentSelectedEvent,
8
-    sendAnalytics
9
-} from '../../analytics';
10
-import { appNavigate, getDefaultURL } from '../../app';
5
+import { getDefaultURL } from '../../app';
11 6
 import { translate } from '../../base/i18n';
12
-import {
13
-    AbstractPage,
14
-    Container,
15
-    NavigateSectionList,
16
-    Text
17
-} from '../../base/react';
7
+import { NavigateSectionList } from '../../base/react';
18 8
 import type { Section } from '../../base/react';
19 9
 
20 10
 import { deleteRecentListEntry } from '../actions';
21 11
 import { isRecentListEnabled, toDisplayableList } from '../functions';
22
-
23
-import styles from './styles';
12
+import AbstractRecentList from './AbstractRecentList';
24 13
 
25 14
 /**
26 15
  * The type of the React {@code Component} props of {@link RecentList}
@@ -54,10 +43,13 @@ type Props = {
54 43
 };
55 44
 
56 45
 /**
57
- * The cross platform container rendering the list of the recently joined rooms.
46
+ * A class that renders the list of the recently joined rooms.
58 47
  *
59 48
  */
60
-class RecentList extends AbstractPage<Props> {
49
+class RecentList extends AbstractRecentList<Props> {
50
+    _getRenderListEmptyComponent: () => React$Node;
51
+    _onPress: string => {};
52
+
61 53
     /**
62 54
      * Initializes a new {@code RecentList} instance.
63 55
      *
@@ -67,18 +59,6 @@ class RecentList extends AbstractPage<Props> {
67 59
         super(props);
68 60
 
69 61
         this._onDelete = this._onDelete.bind(this);
70
-        this._onPress = this._onPress.bind(this);
71
-    }
72
-
73
-    /**
74
-     * Implements React's {@link Component#componentDidMount()}. Invoked
75
-     * immediately after this component is mounted.
76
-     *
77
-     * @inheritdoc
78
-     * @returns {void}
79
-     */
80
-    componentDidMount() {
81
-        sendAnalytics(createRecentSelectedEvent());
82 62
     }
83 63
 
84 64
     /**
@@ -90,7 +70,12 @@ class RecentList extends AbstractPage<Props> {
90 70
         if (!isRecentListEnabled()) {
91 71
             return null;
92 72
         }
93
-        const { disabled, t, _defaultServerURL, _recentList } = this.props;
73
+        const {
74
+            disabled,
75
+            t,
76
+            _defaultServerURL,
77
+            _recentList
78
+        } = this.props;
94 79
         const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
95 80
         const slideActions = [ {
96 81
             backgroundColor: 'red',
@@ -109,31 +94,6 @@ class RecentList extends AbstractPage<Props> {
109 94
         );
110 95
     }
111 96
 
112
-    _getRenderListEmptyComponent: () => Object;
113
-
114
-    /**
115
-     * Returns a list empty component if a custom one has to be rendered instead
116
-     * of the default one in the {@link NavigateSectionList}.
117
-     *
118
-     * @private
119
-     * @returns {React$Component}
120
-     */
121
-    _getRenderListEmptyComponent() {
122
-        const { t } = this.props;
123
-
124
-        return (
125
-            <Container
126
-                className = 'navigate-section-list-empty'
127
-                style = { styles.emptyListContainer }>
128
-                <Text
129
-                    className = 'header-text-description'
130
-                    style = { styles.emptyListText }>
131
-                    { t('welcomepage.recentListEmpty') }
132
-                </Text>
133
-            </Container>
134
-        );
135
-    }
136
-
137 97
     _onDelete: Object => void
138 98
 
139 99
     /**
@@ -146,23 +106,6 @@ class RecentList extends AbstractPage<Props> {
146 106
     _onDelete(itemId) {
147 107
         this.props.dispatch(deleteRecentListEntry(itemId));
148 108
     }
149
-
150
-    _onPress: string => Function;
151
-
152
-    /**
153
-     * Handles the list's navigate action.
154
-     *
155
-     * @private
156
-     * @param {string} url - The url string to navigate to.
157
-     * @returns {void}
158
-     */
159
-    _onPress(url) {
160
-        const { dispatch } = this.props;
161
-
162
-        sendAnalytics(createRecentClickedEvent('recent.meeting.tile'));
163
-
164
-        dispatch(appNavigate(url));
165
-    }
166 109
 }
167 110
 
168 111
 /**

+ 99
- 0
react/features/recent-list/components/RecentList.web.js Vedi File

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

react/features/recent-list/components/styles.js → react/features/recent-list/components/styles.native.js Vedi File


+ 1
- 0
react/features/recent-list/components/styles.web.js Vedi File

@@ -0,0 +1 @@
1
+export default {};

+ 0
- 80
react/features/recent-list/functions.any.js Vedi File

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

+ 79
- 1
react/features/recent-list/functions.native.js Vedi File

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

+ 14
- 26
react/features/recent-list/functions.web.js Vedi File

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

+ 76
- 0
react/features/welcome/components/Tab.js Vedi File

@@ -0,0 +1,76 @@
1
+// @flow
2
+import React, { Component } from 'react';
3
+
4
+/**
5
+ * The type of the React {@code Component} props of {@link Tab}
6
+ */
7
+type Props = {
8
+
9
+    /**
10
+     * The index of the tab.
11
+     */
12
+    index: number,
13
+
14
+    /**
15
+     * Indicates if the tab is selected or not.
16
+     */
17
+    isSelected: boolean,
18
+
19
+    /**
20
+     * The label of the tab.
21
+     */
22
+    label: string,
23
+
24
+    /**
25
+     * Handler for selecting the tab.
26
+     */
27
+    onSelect: Function
28
+}
29
+
30
+/**
31
+ * A React component that implements tabs.
32
+ *
33
+ */
34
+export default class Tab extends Component<Props> {
35
+    /**
36
+     * Initializes a new {@code Tab} instance.
37
+     *
38
+     * @inheritdoc
39
+     */
40
+    constructor(props: Props) {
41
+        super(props);
42
+
43
+        this._onSelect = this._onSelect.bind(this);
44
+    }
45
+
46
+    _onSelect: () => {};
47
+
48
+    /**
49
+     * Selects a tab.
50
+     *
51
+     * @returns {void}
52
+     */
53
+    _onSelect() {
54
+        const { index, onSelect } = this.props;
55
+
56
+        onSelect(index);
57
+    }
58
+
59
+    /**
60
+     * Implements the React Components's render method.
61
+     *
62
+     * @inheritdoc
63
+     */
64
+    render() {
65
+        const { index, isSelected, label } = this.props;
66
+        const className = `tab${isSelected ? ' selected' : ''}`;
67
+
68
+        return (
69
+            <div
70
+                className = { className }
71
+                key = { index }
72
+                onClick = { this._onSelect }>
73
+                { label }
74
+            </div>);
75
+    }
76
+}

+ 63
- 0
react/features/welcome/components/Tabs.js Vedi File

@@ -0,0 +1,63 @@
1
+// @flow
2
+import React, { Component } from 'react';
3
+
4
+import Tab from './Tab';
5
+
6
+/**
7
+ * The type of the React {@code Component} props of {@link Tabs}
8
+ */
9
+type Props = {
10
+
11
+    /**
12
+     * Handler for selecting the tab.
13
+     */
14
+    onSelect: Function,
15
+
16
+    /**
17
+     * The index of the selected tab.
18
+     */
19
+    selected: number,
20
+
21
+    /**
22
+     * Tabs information.
23
+     */
24
+    tabs: Object
25
+};
26
+
27
+/**
28
+ * A React component that implements tabs.
29
+ *
30
+ */
31
+export default class Tabs extends Component<Props> {
32
+    /**
33
+     * Implements the React Components's render method.
34
+     *
35
+     * @inheritdoc
36
+     */
37
+    render() {
38
+        const { onSelect, selected, tabs } = this.props;
39
+        const { content } = tabs[selected];
40
+
41
+        return (
42
+            <div className = 'tab-container'>
43
+                <div className = 'tab-content'>
44
+                    { content }
45
+                </div>
46
+                { tabs.length > 1 ? (
47
+                    <div className = 'tab-buttons'>
48
+                        {
49
+                            tabs.map((tab, index) => (
50
+                                <Tab
51
+                                    index = { index }
52
+                                    isSelected = { index === selected }
53
+                                    key = { index }
54
+                                    label = { tab.label }
55
+                                    onSelect = { onSelect } />
56
+                            ))
57
+                        }
58
+                    </div>) : null
59
+                }
60
+            </div>
61
+        );
62
+    }
63
+}

+ 51
- 60
react/features/welcome/components/WelcomePage.web.js Vedi File

@@ -1,9 +1,5 @@
1 1
 /* global interfaceConfig */
2 2
 
3
-import Button from '@atlaskit/button';
4
-import { FieldTextStateless } from '@atlaskit/field-text';
5
-import Tabs from '@atlaskit/tabs';
6
-import { AtlasKitThemeProvider } from '@atlaskit/theme';
7 3
 import React from 'react';
8 4
 import { connect } from 'react-redux';
9 5
 
@@ -14,6 +10,7 @@ import { RecentList } from '../../recent-list';
14 10
 import { SettingsButton, SETTINGS_TABS } from '../../settings';
15 11
 
16 12
 import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
13
+import Tabs from './Tabs';
17 14
 
18 15
 /**
19 16
  * The Web container rendering the welcome page.
@@ -118,58 +115,60 @@ class WelcomePage extends AbstractWelcomePage {
118 115
         const showAdditionalContent = this._shouldShowAdditionalContent();
119 116
 
120 117
         return (
121
-            <AtlasKitThemeProvider mode = 'light'>
122
-                <div
123
-                    className = { `welcome ${showAdditionalContent
124
-                        ? 'with-content' : 'without-content'}` }
125
-                    id = 'welcome_page'>
126
-                    <div className = 'welcome-watermark'>
127
-                        <Watermarks />
118
+            <div
119
+                className = { `welcome ${showAdditionalContent
120
+                    ? 'with-content' : 'without-content'}` }
121
+                id = 'welcome_page'>
122
+                <div className = 'welcome-watermark'>
123
+                    <Watermarks />
124
+                </div>
125
+                <div className = 'header'>
126
+                    <div className = 'welcome-page-settings'>
127
+                        <SettingsButton
128
+                            defaultTab = { SETTINGS_TABS.CALENDAR } />
128 129
                     </div>
129
-                    <div className = 'header'>
130
-                        <div className = 'header-image' />
131
-                        <div className = 'header-text'>
132
-                            <h1 className = 'header-text-title'>
133
-                                { t('welcomepage.title') }
134
-                            </h1>
135
-                            <p className = 'header-text-description'>
136
-                                { t('welcomepage.appDescription',
137
-                                    { app: APP_NAME }) }
138
-                            </p>
139
-                        </div>
140
-                        <div id = 'enter_room'>
141
-                            <form
142
-                                className = 'enter-room-input'
143
-                                onSubmit = { this._onFormSubmit }>
144
-                                <FieldTextStateless
130
+                    <div className = 'header-image' />
131
+                    <div className = 'header-text'>
132
+                        <h1 className = 'header-text-title'>
133
+                            { t('welcomepage.title') }
134
+                        </h1>
135
+                        <p className = 'header-text-description'>
136
+                            { t('welcomepage.appDescription',
137
+                                { app: APP_NAME }) }
138
+                        </p>
139
+                    </div>
140
+                    <div id = 'enter_room'>
141
+                        <div className = 'enter-room-input-container'>
142
+                            <div className = 'enter-room-title'>
143
+                                { t('welcomepage.enterRoomTitle') }
144
+                            </div>
145
+                            <form onSubmit = { this._onFormSubmit }>
146
+                                <input
145 147
                                     autoFocus = { true }
148
+                                    className = 'enter-room-input'
146 149
                                     id = 'enter_room_field'
147
-                                    isLabelHidden = { true }
148
-                                    label = 'enter_room_field'
149 150
                                     onChange = { this._onRoomChange }
150
-                                    placeholder = { this.state.roomPlaceholder }
151
-                                    shouldFitContainer = { true }
151
+                                    placeholder
152
+                                        = { this.state.roomPlaceholder }
152 153
                                     type = 'text'
153 154
                                     value = { this.state.room } />
154 155
                             </form>
155
-                            <Button
156
-                                appearance = 'primary'
157
-                                className = 'welcome-page-button'
158
-                                id = 'enter_room_button'
159
-                                onClick = { this._onJoin }
160
-                                type = 'button'>
161
-                                { t('welcomepage.go') }
162
-                            </Button>
163 156
                         </div>
164
-                        { this._renderTabs() }
157
+                        <div
158
+                            className = 'welcome-page-button'
159
+                            id = 'enter_room_button'
160
+                            onClick = { this._onJoin }>
161
+                            { t('welcomepage.go') }
162
+                        </div>
165 163
                     </div>
166
-                    { showAdditionalContent
167
-                        ? <div
168
-                            className = 'welcome-page-content'
169
-                            ref = { this._setAdditionalContentRef } />
170
-                        : null }
164
+                    { this._renderTabs() }
171 165
                 </div>
172
-            </AtlasKitThemeProvider>
166
+                { showAdditionalContent
167
+                    ? <div
168
+                        className = 'welcome-page-content'
169
+                        ref = { this._setAdditionalContentRef } />
170
+                    : null }
171
+            </div>
173 172
         );
174 173
     }
175 174
 
@@ -203,14 +202,12 @@ class WelcomePage extends AbstractWelcomePage {
203 202
     /**
204 203
      * Callback invoked when the desired tab to display should be changed.
205 204
      *
206
-     * @param {Object} tab - The configuration passed into atlaskit tabs to
207
-     * describe how to display the selected tab.
208 205
      * @param {number} tabIndex - The index of the tab within the array of
209 206
      * displayed tabs.
210 207
      * @private
211 208
      * @returns {void}
212 209
      */
213
-    _onTabSelected(tab, tabIndex) { // eslint-disable-line no-unused-vars
210
+    _onTabSelected(tabIndex) {
214 211
         this.setState({ selectedTab: tabIndex });
215 212
     }
216 213
 
@@ -241,20 +238,14 @@ class WelcomePage extends AbstractWelcomePage {
241 238
 
242 239
         tabs.push({
243 240
             label: t('welcomepage.recentList'),
244
-            content: <RecentList />,
245
-            defaultSelected: !CalendarList
241
+            content: <RecentList />
246 242
         });
247 243
 
248 244
         return (
249
-            <div className = 'tab-container' >
250
-                <div className = 'welcome-page-settings'>
251
-                    <SettingsButton defaultTab = { SETTINGS_TABS.CALENDAR } />
252
-                </div>
253
-                <Tabs
254
-                    onSelect = { this._onTabSelected }
255
-                    selected = { this.state.selectedTab }
256
-                    tabs = { tabs } />
257
-            </div>);
245
+            <Tabs
246
+                onSelect = { this._onTabSelected }
247
+                selected = { this.state.selectedTab }
248
+                tabs = { tabs } />);
258 249
     }
259 250
 
260 251
     /**

Loading…
Annulla
Salva