parent
d62974b433
commit
f2cb15ba44
|
@ -13,20 +13,31 @@
|
|||
float: left;
|
||||
}
|
||||
.navigate-section-list-tile {
|
||||
height: 90px;
|
||||
width: 260px;
|
||||
border-radius: 4px;
|
||||
background-color: #1754A9;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
height: 100px;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
padding: 16px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
|
||||
&.with-click-handler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #1a5dbb;
|
||||
}
|
||||
}
|
||||
.navigate-section-tile-body {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: normal;
|
||||
}
|
||||
.navigate-section-list-tile-info {
|
||||
flex: 1;
|
||||
}
|
||||
.navigate-section-tile-title {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: bold;
|
||||
|
@ -40,4 +51,8 @@
|
|||
position: relative;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ body.welcome-page {
|
|||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
|
@ -62,12 +63,30 @@ body.welcome-page {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.welcome-page-settings {
|
||||
color: $welcomePageDescriptionColor;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
z-index: $zindex2;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
|
|
@ -52,7 +52,7 @@ var interfaceConfig = {
|
|||
'tileview'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile' ],
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
|
||||
// Determines how the video would fit the screen. 'both' would fit the whole
|
||||
// screen, 'height' would fit the original video height to the height of the
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
"video": "Video"
|
||||
},
|
||||
"calendar": "Calendar",
|
||||
"connectCalendarText": "Connect your calendar to view all your meetings in __app__. Plus, add __app__ meetings to your calendar and start them with one click.",
|
||||
"connectCalendarButton": "Connect your calendar",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"privacy": "Privacy",
|
||||
|
@ -639,13 +641,14 @@
|
|||
"startWithVideoMuted": "Start with video muted"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "Later",
|
||||
"next": "Upcoming",
|
||||
"addMeetingURL": "Add a meeting link",
|
||||
"today": "Today",
|
||||
"nextMeeting": "next meeting",
|
||||
"now": "Now",
|
||||
"noEvents": "There are no upcoming events scheduled.",
|
||||
"ongoingMeeting": "ongoing meeting",
|
||||
"permissionButton": "Open settings",
|
||||
"permissionMessage": "The Calendar permission is required to see your meetings in the app."
|
||||
"permissionMessage": "The Calendar permission is required to see your meetings in the app.",
|
||||
"refresh": "Refresh calendar"
|
||||
},
|
||||
"recentList": {
|
||||
"joinPastMeeting": "Join A Past Meeting"
|
||||
|
|
|
@ -12,6 +12,11 @@ export type Item = {
|
|||
*/
|
||||
colorBase: string,
|
||||
|
||||
/**
|
||||
* An optional react element to append to the end of the Item.
|
||||
*/
|
||||
elementAfter?: ?ComponentType<any>,
|
||||
|
||||
/**
|
||||
* Item title
|
||||
*/
|
||||
|
|
|
@ -87,7 +87,7 @@ class NavigateSectionList extends Component<Props> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
renderListEmptyComponent = this._renderListEmptyComponent,
|
||||
renderListEmptyComponent = this._renderListEmptyComponent(),
|
||||
sections
|
||||
} = this.props;
|
||||
|
||||
|
@ -128,11 +128,13 @@ class NavigateSectionList extends Component<Props> {
|
|||
* @returns {Function}
|
||||
*/
|
||||
_onPress(url) {
|
||||
return () => {
|
||||
const { disabled, onPress } = this.props;
|
||||
const { disabled, onPress } = this.props;
|
||||
|
||||
!disabled && url && typeof onPress === 'function' && onPress(url);
|
||||
};
|
||||
if (!disabled && url && typeof onPress === 'function') {
|
||||
return () => onPress(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onRefresh: () => void;
|
||||
|
|
|
@ -6,18 +6,37 @@ import Container from './Container';
|
|||
import Text from './Text';
|
||||
import type { Item } from '../../Types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link NavigateSectionListItem}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The icon to use for the action button.
|
||||
*/
|
||||
actionIconName: string,
|
||||
|
||||
/**
|
||||
* The function to call when the action button is clicked.
|
||||
*/
|
||||
actionOnClick: ?Function,
|
||||
|
||||
/**
|
||||
* The tooltip to attach to the action button of this list item.
|
||||
*/
|
||||
actionTooltip: string,
|
||||
|
||||
/**
|
||||
* Function to be invoked when an item is pressed. The item's URL is passed.
|
||||
*/
|
||||
onPress: Function,
|
||||
onPress: ?Function,
|
||||
|
||||
/**
|
||||
* A item containing data to be rendered
|
||||
*/
|
||||
item: Item
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React/Web {@link Component} for displaying an item in a
|
||||
|
@ -25,14 +44,16 @@ type Props = {
|
|||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class NavigateSectionListItem extends Component<Props> {
|
||||
export default class NavigateSectionListItem<P: Props>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { lines, title } = this.props.item;
|
||||
const { elementAfter, lines, title } = this.props.item;
|
||||
const { onPress } = this.props;
|
||||
|
||||
/**
|
||||
|
@ -52,22 +73,28 @@ export default class NavigateSectionListItem extends Component<Props> {
|
|||
duration = lines[1];
|
||||
}
|
||||
|
||||
const rootClassName = `navigate-section-list-tile ${
|
||||
onPress ? 'with-click-handler' : 'without-click-handler'}`;
|
||||
|
||||
return (
|
||||
<Container
|
||||
className = 'navigate-section-list-tile'
|
||||
className = { rootClassName }
|
||||
onClick = { onPress }>
|
||||
<Text
|
||||
className = 'navigate-section-tile-title'>
|
||||
{ title }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ date }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ duration }
|
||||
</Text>
|
||||
<Container className = 'navigate-section-list-tile-info'>
|
||||
<Text
|
||||
className = 'navigate-section-tile-title'>
|
||||
{ title }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ date }
|
||||
</Text>
|
||||
<Text
|
||||
className = 'navigate-section-tile-body'>
|
||||
{ duration }
|
||||
</Text>
|
||||
</Container>
|
||||
{ elementAfter || null }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@ import type { Section } from '../../Types';
|
|||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Rendered when the list is empty. Should be a rendered element.
|
||||
*/
|
||||
ListEmptyComponent: Object,
|
||||
|
||||
/**
|
||||
* Used to extract a unique key for a given item at the specified index.
|
||||
* Key is used for caching and as the react key to track item re-ordering.
|
||||
|
@ -49,6 +54,7 @@ export default class SectionList extends Component<Props> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
ListEmptyComponent,
|
||||
renderSectionHeader,
|
||||
renderItem,
|
||||
sections,
|
||||
|
@ -56,30 +62,32 @@ export default class SectionList extends Component<Props> {
|
|||
} = this.props;
|
||||
|
||||
/**
|
||||
* If there are no recent items we dont want to display anything
|
||||
* If there are no recent items we don't want to display anything
|
||||
*/
|
||||
if (sections) {
|
||||
return (
|
||||
<Container
|
||||
className = 'navigate-section-list'>
|
||||
{
|
||||
sections.map((section, sectionIndex) => (
|
||||
<Container
|
||||
key = { sectionIndex }>
|
||||
{ renderSectionHeader(section) }
|
||||
{ section.data
|
||||
.map((item, listIndex) => {
|
||||
const listItem = {
|
||||
item
|
||||
};
|
||||
sections.length === 0
|
||||
? ListEmptyComponent
|
||||
: sections.map((section, sectionIndex) => (
|
||||
<Container
|
||||
key = { sectionIndex }>
|
||||
{ renderSectionHeader(section) }
|
||||
{ section.data
|
||||
.map((item, listIndex) => {
|
||||
const listItem = {
|
||||
item
|
||||
};
|
||||
|
||||
return renderItem(listItem,
|
||||
keyExtractor(section,
|
||||
listIndex));
|
||||
}) }
|
||||
</Container>
|
||||
)
|
||||
)
|
||||
return renderItem(listItem,
|
||||
keyExtractor(section,
|
||||
listIndex));
|
||||
}) }
|
||||
</Container>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -74,3 +74,16 @@ export const SET_CALENDAR_AUTH_STATE = Symbol('SET_CALENDAR_AUTH_STATE');
|
|||
* @public
|
||||
*/
|
||||
export const SET_CALENDAR_PROFILE_EMAIL = Symbol('SET_CALENDAR_PROFILE_EMAIL');
|
||||
|
||||
/**
|
||||
* The type of Redux action which denotes whether a request is in flight to get
|
||||
* updated calendar events.
|
||||
*
|
||||
* {
|
||||
* type: SET_LOADING_CALENDAR_EVENTS,
|
||||
* isLoadingEvents: string
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_LOADING_CALENDAR_EVENTS
|
||||
= Symbol('SET_LOADING_CALENDAR_EVENTS');
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
SET_CALENDAR_AUTHORIZATION,
|
||||
SET_CALENDAR_EVENTS,
|
||||
SET_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_PROFILE_EMAIL
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
SET_LOADING_CALENDAR_EVENTS
|
||||
} from './actionTypes';
|
||||
import { _getCalendarIntegration, isCalendarEnabled } from './functions';
|
||||
import { generateRoomWithoutSeparator } from '../welcome';
|
||||
|
@ -173,6 +174,23 @@ export function setCalendarProfileEmail(newEmail: ?string) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an to denote a request in is flight to get calendar events.
|
||||
*
|
||||
* @param {boolean} isLoadingEvents - Whether or not calendar events are being
|
||||
* fetched.
|
||||
* @returns {{
|
||||
* type: SET_LOADING_CALENDAR_EVENTS,
|
||||
* isLoadingEvents: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setLoadingCalendarEvents(isLoadingEvents: boolean) {
|
||||
return {
|
||||
type: SET_LOADING_CALENDAR_EVENTS,
|
||||
isLoadingEvents
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the calendar integration type to be used by web and signals that the
|
||||
* integration is ready to be used.
|
||||
|
@ -211,6 +229,7 @@ export function signIn(calendarType: string): Function {
|
|||
.then(() => dispatch(integration.signIn()))
|
||||
.then(() => dispatch(setIntegrationReady(calendarType)))
|
||||
.then(() => dispatch(updateProfile(calendarType)))
|
||||
.then(() => dispatch(refreshCalendar()))
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
'Error occurred while signing into calendar integration',
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
||||
import { NavigateSectionList } from '../../base/react';
|
||||
import { openSettings } from '../../mobile/permissions';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||
|
||||
/**
|
||||
* The tyoe of the React {@code Component} props of {@link MeetingList}.
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractCalendarList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current state of the calendar access permission.
|
||||
*/
|
||||
_authorization: ?string,
|
||||
|
||||
/**
|
||||
* The calendar event list.
|
||||
*/
|
||||
|
@ -38,6 +33,11 @@ type Props = {
|
|||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
renderListEmptyComponent: Function,
|
||||
|
||||
/**
|
||||
* The translate function.
|
||||
*/
|
||||
|
@ -45,9 +45,9 @@ type Props = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Component to display a list of events from the (mobile) user's calendar.
|
||||
* Component to display a list of events from a connected calendar.
|
||||
*/
|
||||
class MeetingList extends Component<Props> {
|
||||
class AbstractCalendarList extends Component<Props> {
|
||||
/**
|
||||
* Default values for the component's props.
|
||||
*/
|
||||
|
@ -75,7 +75,7 @@ class MeetingList extends Component<Props> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code MeetingList} instance.
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -83,13 +83,12 @@ class MeetingList extends Component<Props> {
|
|||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._onRefresh = this._onRefresh.bind(this);
|
||||
this._toDateString = this._toDateString.bind(this);
|
||||
this._toDisplayableItem = this._toDisplayableItem.bind(this);
|
||||
this._toDisplayableList = this._toDisplayableList.bind(this);
|
||||
this._toTimeString = this._toTimeString.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,7 +97,7 @@ class MeetingList extends Component<Props> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const { disabled, renderListEmptyComponent } = this.props;
|
||||
|
||||
return (
|
||||
<NavigateSectionList
|
||||
|
@ -106,46 +105,11 @@ class MeetingList extends Component<Props> {
|
|||
onPress = { this._onPress }
|
||||
onRefresh = { this._onRefresh }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() }
|
||||
= { renderListEmptyComponent }
|
||||
sections = { this._toDisplayableList() } />
|
||||
);
|
||||
}
|
||||
|
||||
_getRenderListEmptyComponent: () => Object;
|
||||
|
||||
/**
|
||||
* Returns a list empty component if a custom one has to be rendered instead
|
||||
* of the default one in the {@link NavigateSectionList}.
|
||||
*
|
||||
* @private
|
||||
* @returns {?React$Component}
|
||||
*/
|
||||
_getRenderListEmptyComponent() {
|
||||
const { _authorization, t } = this.props;
|
||||
|
||||
// If we don't provide a list specific renderListEmptyComponent, then
|
||||
// the default empty component of the NavigateSectionList will be
|
||||
// rendered, which (atm) is a simple "Pull to refresh" message.
|
||||
if (_authorization !== 'denied') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.noPermissionMessageView }>
|
||||
<Text style = { styles.noPermissionMessageText }>
|
||||
{ t('calendarSync.permissionMessage') }
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress = { openSettings }
|
||||
style = { styles.noPermissionMessageButton } >
|
||||
<Text style = { styles.noPermissionMessageButtonText }>
|
||||
{ t('calendarSync.permissionButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_onPress: string => Function;
|
||||
|
||||
/**
|
||||
|
@ -174,7 +138,7 @@ class MeetingList extends Component<Props> {
|
|||
_toDateString: Object => string;
|
||||
|
||||
/**
|
||||
* Generates a date (interval) string for a given event.
|
||||
* Generates a date string for a given event.
|
||||
*
|
||||
* @param {Object} event - The event.
|
||||
* @private
|
||||
|
@ -182,11 +146,9 @@ class MeetingList extends Component<Props> {
|
|||
*/
|
||||
_toDateString(event) {
|
||||
const startDateTime
|
||||
= getLocalizedDateFormatter(event.startDate).format('lll');
|
||||
const endTime
|
||||
= getLocalizedDateFormatter(event.endDate).format('LT');
|
||||
= getLocalizedDateFormatter(event.startDate).format('MMM Do, YYYY');
|
||||
|
||||
return `${startDateTime} - ${endTime}`;
|
||||
return `${startDateTime}`;
|
||||
}
|
||||
|
||||
_toDisplayableItem: Object => Object;
|
||||
|
@ -200,10 +162,15 @@ class MeetingList extends Component<Props> {
|
|||
*/
|
||||
_toDisplayableItem(event) {
|
||||
return {
|
||||
elementAfter: event.url ? undefined : (
|
||||
<AddMeetingUrlButton
|
||||
calendarId = { event.calendarId }
|
||||
eventId = { event.id } />
|
||||
),
|
||||
key: `${event.id}-${event.startDate}`,
|
||||
lines: [
|
||||
event.url,
|
||||
this._toDateString(event)
|
||||
this._toTimeString(event)
|
||||
],
|
||||
title: event.title,
|
||||
url: event.url
|
||||
|
@ -221,39 +188,60 @@ class MeetingList extends Component<Props> {
|
|||
_toDisplayableList() {
|
||||
const { _eventList, t } = this.props;
|
||||
|
||||
const now = Date.now();
|
||||
const now = new Date();
|
||||
|
||||
const { createSection } = NavigateSectionList;
|
||||
const nowSection = createSection(t('calendarSync.now'), 'now');
|
||||
const nextSection = createSection(t('calendarSync.next'), 'next');
|
||||
const laterSection = createSection(t('calendarSync.later'), 'later');
|
||||
const TODAY_SECTION = 'today';
|
||||
const sectionMap = new Map();
|
||||
|
||||
for (const event of _eventList) {
|
||||
const displayableEvent = this._toDisplayableItem(event);
|
||||
const startDate = new Date(event.startDate).getDate();
|
||||
|
||||
if (event.startDate < now && event.endDate > now) {
|
||||
nowSection.data.push(displayableEvent);
|
||||
} else if (event.startDate > now) {
|
||||
if (nextSection.data.length
|
||||
&& nextSection.data[0].startDate !== event.startDate) {
|
||||
laterSection.data.push(displayableEvent);
|
||||
} else {
|
||||
nextSection.data.push(displayableEvent);
|
||||
if (startDate === now.getDate()) {
|
||||
let todaySection = sectionMap.get(TODAY_SECTION);
|
||||
|
||||
if (!todaySection) {
|
||||
todaySection
|
||||
= createSection(t('calendarSync.today'), TODAY_SECTION);
|
||||
sectionMap.set(TODAY_SECTION, todaySection);
|
||||
}
|
||||
|
||||
todaySection.data.push(displayableEvent);
|
||||
} else if (sectionMap.has(startDate)) {
|
||||
const section = sectionMap.get(startDate);
|
||||
|
||||
if (section) {
|
||||
section.data.push(displayableEvent);
|
||||
}
|
||||
} else {
|
||||
const newSection
|
||||
= createSection(this._toDateString(event), startDate);
|
||||
|
||||
sectionMap.set(startDate, newSection);
|
||||
newSection.data.push(displayableEvent);
|
||||
}
|
||||
}
|
||||
|
||||
const sectionList = [];
|
||||
return Array.from(sectionMap.values());
|
||||
}
|
||||
|
||||
for (const section of [
|
||||
nowSection,
|
||||
nextSection,
|
||||
laterSection
|
||||
]) {
|
||||
section.data.length && sectionList.push(section);
|
||||
}
|
||||
_toTimeString: Object => string;
|
||||
|
||||
return sectionList;
|
||||
/**
|
||||
* Generates a time (interval) string for a given event.
|
||||
*
|
||||
* @param {Object} event - The event.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_toTimeString(event) {
|
||||
const startDateTime
|
||||
= getLocalizedDateFormatter(event.startDate).format('lll');
|
||||
const endTime
|
||||
= getLocalizedDateFormatter(event.endDate).format('LT');
|
||||
|
||||
return `${startDateTime} - ${endTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,19 +250,15 @@ class MeetingList extends Component<Props> {
|
|||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{
|
||||
* _authorization: ?string,
|
||||
* _eventList: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { authorization, events } = state['features/calendar-sync'];
|
||||
|
||||
return {
|
||||
_authorization: authorization,
|
||||
_eventList: events
|
||||
_eventList: state['features/calendar-sync'].events
|
||||
};
|
||||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(MeetingList))
|
||||
? translate(connect(_mapStateToProps)(AbstractCalendarList))
|
||||
: undefined;
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* A React Component for adding a meeting URL to an existing calendar meeting.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class AddMeetingUrlButton extends Component<*> {
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
// Not yet implemented.
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddMeetingUrlButton;
|
|
@ -0,0 +1,86 @@
|
|||
// @flow
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { updateCalendarEvent } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AddMeetingUrlButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The calendar ID associated with the calendar event.
|
||||
*/
|
||||
calendarId: string,
|
||||
|
||||
/**
|
||||
* Invoked to add a meeting URL to a calendar event.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* The ID of the calendar event that will have a meeting URL added on click.
|
||||
*/
|
||||
eventId: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* A React Component for adding a meeting URL to an existing calendar event.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class AddMeetingUrlButton extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code AddMeetingUrlButton} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
onClick = { this._onClick }
|
||||
type = 'button'>
|
||||
{ this.props.t('calendarSync.addMeetingURL') }
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action to adding a meeting URL to a calendar event.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { calendarId, dispatch, eventId } = this.props;
|
||||
|
||||
dispatch(updateCalendarEvent(eventId, calendarId));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(AddMeetingUrlButton));
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openSettings } from '../../mobile/permissions';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
import AbstractCalendarList from './AbstractCalendarList';
|
||||
|
||||
/**
|
||||
* The tyoe of the React {@code Component} props of {@link CalendarList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current state of the calendar access permission.
|
||||
*/
|
||||
_authorization: ?string,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The translate function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display a list of events from the (mobile) user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
AbstractCalendarList
|
||||
? <AbstractCalendarList
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
_getRenderListEmptyComponent: () => Object;
|
||||
|
||||
/**
|
||||
* Returns a list empty component if a custom one has to be rendered instead
|
||||
* of the default one in the {@link NavigateSectionList}.
|
||||
*
|
||||
* @private
|
||||
* @returns {?React$Component}
|
||||
*/
|
||||
_getRenderListEmptyComponent() {
|
||||
const { _authorization, t } = this.props;
|
||||
|
||||
// If we don't provide a list specific renderListEmptyComponent, then
|
||||
// the default empty component of the NavigateSectionList will be
|
||||
// rendered, which (atm) is a simple "Pull to refresh" message.
|
||||
if (_authorization !== 'denied') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.noPermissionMessageView }>
|
||||
<Text style = { styles.noPermissionMessageText }>
|
||||
{ t('calendarSync.permissionMessage') }
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress = { openSettings }
|
||||
style = { styles.noPermissionMessageButton } >
|
||||
<Text style = { styles.noPermissionMessageButtonText }>
|
||||
{ t('calendarSync.permissionButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux state to component props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{
|
||||
* _authorization: ?string,
|
||||
* _eventList: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { authorization } = state['features/calendar-sync'];
|
||||
|
||||
return {
|
||||
_authorization: authorization
|
||||
};
|
||||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(CalendarList))
|
||||
: undefined;
|
|
@ -0,0 +1,194 @@
|
|||
// @flow
|
||||
|
||||
import Button from '@atlaskit/button';
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import AbstractCalendarList from './AbstractCalendarList';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link CalendarList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether or not a calendar may be connected for fetching calendar events.
|
||||
*/
|
||||
_hasIntegrationSelected: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not events have been fetched from a calendar.
|
||||
*/
|
||||
_hasLoadedEvents: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The translate function.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to display a list of events from the user's calendar.
|
||||
*/
|
||||
class CalendarList extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code CalendarList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
this._onOpenSettings = this._onOpenSettings.bind(this);
|
||||
this._onRefreshEvents = this._onRefreshEvents.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
|
||||
return (
|
||||
AbstractCalendarList
|
||||
? <AbstractCalendarList
|
||||
disabled = { disabled }
|
||||
renderListEmptyComponent
|
||||
= { this._getRenderListEmptyComponent() } />
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
_getRenderListEmptyComponent: () => Object;
|
||||
|
||||
/**
|
||||
* Returns a list empty component if a custom one has to be rendered instead
|
||||
* of the default one in the {@link NavigateSectionList}.
|
||||
*
|
||||
* @private
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_getRenderListEmptyComponent() {
|
||||
const { _hasIntegrationSelected, _hasLoadedEvents, t } = this.props;
|
||||
|
||||
if (_hasIntegrationSelected && _hasLoadedEvents) {
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<div>{ t('calendarSync.noEvents') }</div>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
className = 'calendar-button'
|
||||
id = 'connect_calendar_button'
|
||||
onClick = { this._onRefreshEvents }
|
||||
type = 'button'>
|
||||
{ t('calendarSync.refresh') }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (_hasIntegrationSelected && !_hasLoadedEvents) {
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<Spinner
|
||||
invertColor = { true }
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'navigate-section-list-empty'>
|
||||
<p className = 'header-text-description'>
|
||||
{ t('welcomepage.connectCalendarText', {
|
||||
app: interfaceConfig.APP_NAME
|
||||
}) }
|
||||
</p>
|
||||
<Button
|
||||
appearance = 'primary'
|
||||
className = 'calendar-button'
|
||||
id = 'connect_calendar_button'
|
||||
onClick = { this._onOpenSettings }
|
||||
type = 'button'>
|
||||
{ t('welcomepage.connectCalendarButton') }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onOpenSettings: () => void;
|
||||
|
||||
/**
|
||||
* Opens {@code SettingsDialog}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenSettings() {
|
||||
this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR));
|
||||
}
|
||||
|
||||
_onRefreshEvents: () => void;
|
||||
|
||||
|
||||
/**
|
||||
* Gets an updated list of calendar events.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onRefreshEvents() {
|
||||
this.props.dispatch(refreshCalendar(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code CalendarList} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _hasIntegrationSelected: boolean,
|
||||
* _hasLoadedEvents: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const {
|
||||
events,
|
||||
integrationType,
|
||||
isLoadingEvents
|
||||
} = state['features/calendar-sync'];
|
||||
|
||||
return {
|
||||
_hasIntegrationSelected: Boolean(integrationType),
|
||||
_hasLoadedEvents: Boolean(events) || !isLoadingEvents
|
||||
};
|
||||
}
|
||||
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(CalendarList))
|
||||
: undefined;
|
|
@ -1,3 +1,3 @@
|
|||
export { default as ConferenceNotification } from './ConferenceNotification';
|
||||
export { default as MeetingList } from './MeetingList';
|
||||
export { default as CalendarList } from './CalendarList';
|
||||
export { default as MicrosoftSignInButton } from './MicrosoftSignInButton';
|
||||
|
|
|
@ -4,7 +4,7 @@ const NOTIFICATION_SIZE = 55;
|
|||
|
||||
/**
|
||||
* The styles of the React {@code Component}s of the feature meeting-list i.e.
|
||||
* {@code MeetingList}.
|
||||
* {@code CalendarList}.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import { setLoadingCalendarEvents } from './actions';
|
||||
export * from './functions.any';
|
||||
|
||||
import {
|
||||
|
@ -56,6 +57,8 @@ export function _fetchCalendarEntries(
|
|||
return;
|
||||
}
|
||||
|
||||
dispatch(setLoadingCalendarEvents(true));
|
||||
|
||||
dispatch(integration.load())
|
||||
.then(() => dispatch(integration._isSignedIn()))
|
||||
.then(signedIn => {
|
||||
|
@ -72,7 +75,8 @@ export function _fetchCalendarEntries(
|
|||
getState
|
||||
}, events))
|
||||
.catch(error =>
|
||||
logger.error('Error fetching calendar.', error));
|
||||
logger.error('Error fetching calendar.', error))
|
||||
.then(() => dispatch(setLoadingCalendarEvents(false)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
SET_CALENDAR_AUTHORIZATION,
|
||||
SET_CALENDAR_EVENTS,
|
||||
SET_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_PROFILE_EMAIL
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
SET_LOADING_CALENDAR_EVENTS
|
||||
} from './actionTypes';
|
||||
import { isCalendarEnabled } from './functions';
|
||||
|
||||
|
@ -96,6 +97,9 @@ isCalendarEnabled()
|
|||
|
||||
case SET_CALENDAR_PROFILE_EMAIL:
|
||||
return set(state, 'profileEmail', action.email);
|
||||
|
||||
case SET_LOADING_CALENDAR_EVENTS:
|
||||
return set(state, 'isLoadingEvents', action.isLoadingEvents);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
import { createStyleSheet, BoxModel } from '../../base/styles';
|
||||
|
||||
const AVATAR_OPACITY = 0.4;
|
||||
|
||||
const AVATAR_SIZE = 65;
|
||||
|
||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||
|
||||
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Component}s of the feature recent-list i.e.
|
||||
* {@code RecentList}.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
|
||||
/**
|
||||
* The style of the actual avatar.
|
||||
*/
|
||||
avatar: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
|
||||
borderRadius: AVATAR_SIZE,
|
||||
height: AVATAR_SIZE,
|
||||
justifyContent: 'center',
|
||||
width: AVATAR_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the avatar container that makes the avatar rounded.
|
||||
*/
|
||||
avatarContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
paddingTop: 5
|
||||
},
|
||||
|
||||
/**
|
||||
* Simple {@code Text} content of the avatar (the actual initials).
|
||||
*/
|
||||
avatarContent: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontSize: 32,
|
||||
fontWeight: '100',
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* List of styles of the avatar of a remote meeting (not the default
|
||||
* server). The number of colors are limited because they should match
|
||||
* nicely.
|
||||
*/
|
||||
avatarRemoteServer1: {
|
||||
backgroundColor: `rgba(232, 105, 156, ${AVATAR_OPACITY})`
|
||||
},
|
||||
|
||||
avatarRemoteServer2: {
|
||||
backgroundColor: `rgba(255, 198, 115, ${AVATAR_OPACITY})`
|
||||
},
|
||||
|
||||
avatarRemoteServer3: {
|
||||
backgroundColor: `rgba(128, 128, 255, ${AVATAR_OPACITY})`
|
||||
},
|
||||
|
||||
avatarRemoteServer4: {
|
||||
backgroundColor: `rgba(105, 232, 194, ${AVATAR_OPACITY})`
|
||||
},
|
||||
|
||||
avatarRemoteServer5: {
|
||||
backgroundColor: `rgba(234, 255, 128, ${AVATAR_OPACITY})`
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the conference length (if rendered).
|
||||
*/
|
||||
confLength: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
|
||||
/**
|
||||
* The top level container style of the list.
|
||||
*/
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the container disabled.
|
||||
*/
|
||||
containerDisabled: {
|
||||
opacity: 0.2
|
||||
},
|
||||
|
||||
/**
|
||||
* Second line of the list (date). May be extended with server name later.
|
||||
*/
|
||||
date: {
|
||||
color: OVERLAY_FONT_COLOR
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the details container (right side) of the list.
|
||||
*/
|
||||
detailsContainer: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 2 * BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* The container for an info line with an inline icon.
|
||||
*/
|
||||
infoWithIcon: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of an inline icon in an info line.
|
||||
*/
|
||||
inlineIcon: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
marginRight: 5
|
||||
},
|
||||
|
||||
/**
|
||||
* First line of the list (room name).
|
||||
*/
|
||||
roomName: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of one single row in the list.
|
||||
*/
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
paddingBottom: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the server name component (if rendered).
|
||||
*/
|
||||
serverName: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
});
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
import Button from '@atlaskit/button';
|
||||
import { FieldTextStateless } from '@atlaskit/field-text';
|
||||
import Tabs from '@atlaskit/tabs';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Watermarks } from '../../base/react';
|
||||
import { Platform, Watermarks } from '../../base/react';
|
||||
import { CalendarList } from '../../calendar-sync';
|
||||
import { RecentList } from '../../recent-list';
|
||||
import { openSettingsDialog } from '../../settings';
|
||||
import { SettingsButton } from '../../settings';
|
||||
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
|
||||
|
@ -66,7 +68,6 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onFormSubmit = this._onFormSubmit.bind(this);
|
||||
this._onOpenSettings = this._onOpenSettings.bind(this);
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._setAdditionalContentRef
|
||||
= this._setAdditionalContentRef.bind(this);
|
||||
|
@ -159,7 +160,7 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
{ t('welcomepage.go') }
|
||||
</Button>
|
||||
</div>
|
||||
<RecentList />
|
||||
{ this._renderTabs() }
|
||||
</div>
|
||||
{ showAdditionalContent
|
||||
? <div
|
||||
|
@ -187,16 +188,6 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
this._onJoin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@code SettingsDialog}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenSettings() {
|
||||
this.props.dispatch(openSettingsDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the super to account for the differences in the argument types
|
||||
* provided by HTML and React Native text inputs.
|
||||
|
@ -211,6 +202,47 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
super._onRoomChange(event.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders tabs to show previous meetings and upcoming calendar events. The
|
||||
* tabs are purposefully hidden on mobile browsers.
|
||||
*
|
||||
* @returns {ReactElement|null}
|
||||
*/
|
||||
_renderTabs() {
|
||||
const isMobileBrowser
|
||||
= Platform.OS === 'android' || Platform.OS === 'ios';
|
||||
|
||||
if (isMobileBrowser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t } = this.props;
|
||||
|
||||
const tabs = [];
|
||||
|
||||
if (CalendarList) {
|
||||
tabs.push({
|
||||
label: t('welcomepage.calendar'),
|
||||
content: <CalendarList />,
|
||||
defaultSelected: true
|
||||
});
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
label: t('welcomepage.recentList'),
|
||||
content: <RecentList />,
|
||||
defaultSelected: !CalendarList
|
||||
});
|
||||
|
||||
return (
|
||||
<div className = 'tab-container' >
|
||||
<div className = 'welcome-page-settings'>
|
||||
<SettingsButton />
|
||||
</div>
|
||||
<Tabs tabs = { tabs } />
|
||||
</div>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLDivElement used to hold the
|
||||
* welcome page content.
|
||||
|
|
|
@ -6,7 +6,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { PagedList } from '../../base/react';
|
||||
import { MeetingList } from '../../calendar-sync';
|
||||
import { CalendarList } from '../../calendar-sync';
|
||||
import { RecentList } from '../../recent-list';
|
||||
|
||||
import { setWelcomePageListsDefaultPage } from '../actions';
|
||||
|
@ -82,7 +82,7 @@ class WelcomePageLists extends Component<Props> {
|
|||
title: t('welcomepage.recentList')
|
||||
},
|
||||
{
|
||||
component: MeetingList,
|
||||
component: CalendarList,
|
||||
icon: android ? 'event_note' : IOS_CALENDAR_ICON,
|
||||
title: t('welcomepage.calendar')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue