mirror of https://github.com/jitsi/jitsi-meet
parent
d62974b433
commit
f2cb15ba44
@ -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'; |
||||
|
||||
@ -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' |
||||
} |
||||
}); |
||||
Loading…
Reference in new issue