Merge pull request #3416 from zbettenbuk/calendar-invite
[RN] Add calendar invite
This commit is contained in:
commit
37ff77cd5b
|
@ -648,6 +648,8 @@
|
|||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Add a meeting link",
|
||||
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
|
||||
"confirmAddLinkTitle": "Calendar",
|
||||
"join": "Join",
|
||||
"joinTooltip": "Join the meeting",
|
||||
"nextMeeting": "next meeting",
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Linking } from 'react-native';
|
|||
|
||||
import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
|
@ -180,6 +181,17 @@ export class App extends AbstractApp {
|
|||
_onLinkingURL({ url }) {
|
||||
super._openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return (
|
||||
<DialogContainer />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/responsive-ui';
|
||||
import '../../chat';
|
||||
import '../../room-lock';
|
||||
|
@ -39,4 +40,17 @@ export class App extends AbstractApp {
|
|||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer() {
|
||||
return (
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<DialogContainer />
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ export default class BaseApp extends Component<*, State> {
|
|||
{ this._createMainElement(component) }
|
||||
<SoundCollection />
|
||||
{ this._createExtraElement() }
|
||||
{ this._renderDialogContainer() }
|
||||
</Fragment>
|
||||
</Provider>
|
||||
</I18nextProvider>
|
||||
|
@ -235,4 +236,11 @@ export default class BaseApp extends Component<*, State> {
|
|||
this.setState({ route }, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderDialogContainer: () => React$Element<*>
|
||||
}
|
||||
|
|
|
@ -22,7 +22,12 @@ export class DialogContainer extends Component {
|
|||
/**
|
||||
* The props to pass to the component that will be rendered.
|
||||
*/
|
||||
_componentProps: PropTypes.object
|
||||
_componentProps: PropTypes.object,
|
||||
|
||||
/**
|
||||
* True if the UI is in a compact state where we don't show dialogs.
|
||||
*/
|
||||
_reducedUI: PropTypes.bool
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -32,10 +37,13 @@ export class DialogContainer extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _component: component } = this.props;
|
||||
const {
|
||||
_component: component,
|
||||
_reducedUI: reducedUI
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
component
|
||||
component && !reducedUI
|
||||
? React.createElement(component, this.props._componentProps)
|
||||
: null);
|
||||
}
|
||||
|
@ -49,15 +57,18 @@ export class DialogContainer extends Component {
|
|||
* @private
|
||||
* @returns {{
|
||||
* _component: React.Component,
|
||||
* _componentProps: Object
|
||||
* _componentProps: Object,
|
||||
* _reducedUI: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_component: stateFeaturesBaseDialog.component,
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps
|
||||
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||
_reducedUI: reducedUI
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { dialog as styles } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Children of the component.
|
||||
*/
|
||||
children: string | React$Node
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic dialog content container to provide the same styling for all custom
|
||||
* dialogs.
|
||||
*/
|
||||
export default class DialogContent extends Component<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
const childrenComponent = typeof children === 'string'
|
||||
? <Text>{ children }</Text>
|
||||
: children;
|
||||
|
||||
return (
|
||||
<View style = { styles.dialogContainer }>
|
||||
{ childrenComponent }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
// @flow
|
||||
|
||||
export { default as BottomSheet } from './BottomSheet';
|
||||
export { default as DialogContainer } from './DialogContainer';
|
||||
export { default as Dialog } from './Dialog';
|
||||
export { default as DialogContainer } from './DialogContainer';
|
||||
export { default as DialogContent } from './DialogContent';
|
||||
export { default as StatelessDialog } from './StatelessDialog';
|
||||
export { default as DialogWithTabs } from './DialogWithTabs';
|
||||
export { default as AbstractDialogTab } from './AbstractDialogTab';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { ColorPalette, createStyleSheet } from '../../styles';
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of {@code Dialog}.
|
||||
|
@ -13,6 +13,14 @@ export const dialog = createStyleSheet({
|
|||
color: ColorPalette.blue
|
||||
},
|
||||
|
||||
/**
|
||||
* Unified container for a consistent Dialog style.
|
||||
*/
|
||||
dialogContainer: {
|
||||
paddingHorizontal: BoxModel.padding,
|
||||
paddingVertical: 1.5 * BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the {@code Text} in a {@code Dialog} button which is
|
||||
* disabled.
|
||||
|
|
|
@ -29,6 +29,12 @@ type Props = {
|
|||
*/
|
||||
onRefresh: Function,
|
||||
|
||||
/**
|
||||
* Function to be invoked when a secondary action is performed on an item.
|
||||
* The item's ID is passed.
|
||||
*/
|
||||
onSecondaryAction: Function,
|
||||
|
||||
/**
|
||||
* Function to override the rendered default empty list component.
|
||||
*/
|
||||
|
@ -153,6 +159,23 @@ class NavigateSectionList extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
_onSecondaryAction: Object => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used in the secondaryAction callback of the
|
||||
* items.
|
||||
*
|
||||
* @param {string} id - The id of the item that secondary action was
|
||||
* performed on.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onSecondaryAction(id) {
|
||||
return () => {
|
||||
this.props.onSecondaryAction(id);
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem: Object => Object;
|
||||
|
||||
/**
|
||||
|
@ -165,7 +188,7 @@ class NavigateSectionList extends Component<Props> {
|
|||
*/
|
||||
_renderItem(listItem, key: string = '') {
|
||||
const { item } = listItem;
|
||||
const { url } = item;
|
||||
const { id, url } = item;
|
||||
|
||||
// XXX The value of title cannot be undefined; otherwise, react-native
|
||||
// will throw a TypeError: Cannot read property of undefined. While it's
|
||||
|
@ -180,7 +203,9 @@ class NavigateSectionList extends Component<Props> {
|
|||
<NavigateSectionListItem
|
||||
item = { item }
|
||||
key = { key }
|
||||
onPress = { this._onPress(url) } />
|
||||
onPress = { url ? this._onPress(url) : undefined }
|
||||
secondaryAction = {
|
||||
url ? undefined : this._onSecondaryAction(id) } />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,12 @@ type Props = {
|
|||
/**
|
||||
* Function to be invoked when an Item is pressed. The Item's URL is passed.
|
||||
*/
|
||||
onPress: Function
|
||||
onPress: ?Function,
|
||||
|
||||
/**
|
||||
* Function to be invoked when secondary action was performed on an Item.
|
||||
*/
|
||||
secondaryAction: ?Function
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,6 +105,24 @@ export default class NavigateSectionListItem extends Component<Props> {
|
|||
return lines && lines.length ? lines.map(this._renderItemLine) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary action label.
|
||||
*
|
||||
* @private
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderSecondaryAction() {
|
||||
const { secondaryAction } = this.props;
|
||||
|
||||
return (
|
||||
<Container
|
||||
onClick = { secondaryAction }
|
||||
style = { styles.secondaryActionContainer }>
|
||||
<Text style = { styles.secondaryActionLabel }>+</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of this component.
|
||||
*
|
||||
|
@ -135,6 +158,7 @@ export default class NavigateSectionListItem extends Component<Props> {
|
|||
</Text>
|
||||
{this._renderItemLines(lines)}
|
||||
</Container>
|
||||
{ this.props.secondaryAction && this._renderSecondaryAction() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ const HEADER_COLOR = ColorPalette.blue;
|
|||
// Header height is from Android guidelines. Also, this looks good.
|
||||
const HEADER_HEIGHT = 56;
|
||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||
const SECONDARY_ACTION_BUTTON_SIZE = 30;
|
||||
|
||||
export const HEADER_PADDING = BoxModel.padding;
|
||||
export const STATUSBAR_COLOR = ColorPalette.blueHighlight;
|
||||
|
@ -266,6 +267,21 @@ const SECTION_LIST_STYLES = {
|
|||
color: OVERLAY_FONT_COLOR
|
||||
},
|
||||
|
||||
secondaryActionContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.blue,
|
||||
borderRadius: 3,
|
||||
height: SECONDARY_ACTION_BUTTON_SIZE,
|
||||
justifyContent: 'center',
|
||||
margin: BoxModel.margin * 0.5,
|
||||
marginRight: BoxModel.margin,
|
||||
width: SECONDARY_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
secondaryActionLabel: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
touchableView: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
REFRESH_CALENDAR,
|
||||
SET_CALENDAR_AUTHORIZATION,
|
||||
SET_CALENDAR_EVENTS
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sends an action to refresh the entry list (fetches new data).
|
||||
*
|
||||
* @param {boolean} forcePermission - Whether to force to re-ask for
|
||||
* the permission or not.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {{
|
||||
* type: REFRESH_CALENDAR,
|
||||
* forcePermission: boolean,
|
||||
* isInteractive: boolean
|
||||
* }}
|
||||
*/
|
||||
export function refreshCalendar(
|
||||
forcePermission: boolean = false, isInteractive: boolean = true) {
|
||||
return {
|
||||
type: REFRESH_CALENDAR,
|
||||
forcePermission,
|
||||
isInteractive
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action to signal that a calendar access has been requested. For more
|
||||
* info, see {@link SET_CALENDAR_AUTHORIZATION}.
|
||||
*
|
||||
* @param {string | undefined} authorization - The result of the last calendar
|
||||
* authorization request.
|
||||
* @returns {{
|
||||
* type: SET_CALENDAR_AUTHORIZATION,
|
||||
* authorization: ?string
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarAuthorization(authorization: ?string) {
|
||||
return {
|
||||
type: SET_CALENDAR_AUTHORIZATION,
|
||||
authorization
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action to update the current calendar list in redux.
|
||||
*
|
||||
* @param {Array<Object>} events - The new list.
|
||||
* @returns {{
|
||||
* type: SET_CALENDAR_EVENTS,
|
||||
* events: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarEvents(events: Array<Object>) {
|
||||
return {
|
||||
type: SET_CALENDAR_EVENTS,
|
||||
events
|
||||
};
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// @flow
|
||||
|
||||
import { getDefaultURL } from '../app';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { generateRoomWithoutSeparator } from '../welcome';
|
||||
|
||||
import { refreshCalendar } from './actions';
|
||||
import { addLinkToCalendarEntry } from './functions.native';
|
||||
|
||||
import {
|
||||
UpdateCalendarEventDialog
|
||||
} from './components';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Asks confirmation from the user to add a Jitsi link to the calendar event.
|
||||
*
|
||||
* @param {string} eventId - The event id.
|
||||
* @returns {{
|
||||
* type: OPEN_DIALOG,
|
||||
* component: React.Component,
|
||||
* componentProps: (Object | undefined)
|
||||
* }}
|
||||
*/
|
||||
export function openUpdateCalendarEventDialog(eventId: string) {
|
||||
return openDialog(UpdateCalendarEventDialog, { eventId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates calendar event by generating new invite URL and editing the event
|
||||
* adding some descriptive text and location.
|
||||
*
|
||||
* @param {string} eventId - The event id.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateCalendarEvent(eventId: string) {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
const defaultUrl = getDefaultURL(getState);
|
||||
const roomName = generateRoomWithoutSeparator();
|
||||
|
||||
addLinkToCalendarEntry(getState(), eventId, `${defaultUrl}/${roomName}`)
|
||||
.finally(() => {
|
||||
dispatch(refreshCalendar(false, false));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -2,14 +2,12 @@
|
|||
|
||||
import { loadGoogleAPI } from '../google-api';
|
||||
|
||||
import { refreshCalendar, setCalendarEvents } from './actions';
|
||||
import { createCalendarConnectedEvent, sendAnalytics } from '../analytics';
|
||||
|
||||
import {
|
||||
CLEAR_CALENDAR_INTEGRATION,
|
||||
REFRESH_CALENDAR,
|
||||
SET_CALENDAR_AUTH_STATE,
|
||||
SET_CALENDAR_AUTHORIZATION,
|
||||
SET_CALENDAR_EVENTS,
|
||||
SET_CALENDAR_INTEGRATION,
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
SET_LOADING_CALENDAR_EVENTS
|
||||
|
@ -17,6 +15,8 @@ import {
|
|||
import { _getCalendarIntegration, isCalendarEnabled } from './functions';
|
||||
import { generateRoomWithoutSeparator } from '../welcome';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
|
@ -88,25 +88,18 @@ export function clearCalendarIntegration() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sends an action to refresh the entry list (fetches new data).
|
||||
* Asks confirmation from the user to add a Jitsi link to the calendar event.
|
||||
*
|
||||
* @param {boolean} forcePermission - Whether to force to re-ask for
|
||||
* the permission or not.
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {{
|
||||
* type: REFRESH_CALENDAR,
|
||||
* forcePermission: boolean,
|
||||
* isInteractive: boolean
|
||||
* }}
|
||||
* NOTE: Currently there is no confirmation prompted on web, so this is just
|
||||
* a relaying method to avoid flow problems.
|
||||
*
|
||||
* @param {string} eventId - The event id.
|
||||
* @param {string} calendarId - The calendar id.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function refreshCalendar(
|
||||
forcePermission: boolean = false, isInteractive: boolean = true) {
|
||||
return {
|
||||
type: REFRESH_CALENDAR,
|
||||
forcePermission,
|
||||
isInteractive
|
||||
};
|
||||
export function openUpdateCalendarEventDialog(
|
||||
eventId: string, calendarId: string) {
|
||||
return updateCalendarEvent(eventId, calendarId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,40 +119,6 @@ export function setCalendarAPIAuthState(newState: ?Object) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action to signal that a calendar access has been requested. For more
|
||||
* info, see {@link SET_CALENDAR_AUTHORIZATION}.
|
||||
*
|
||||
* @param {string | undefined} authorization - The result of the last calendar
|
||||
* authorization request.
|
||||
* @returns {{
|
||||
* type: SET_CALENDAR_AUTHORIZATION,
|
||||
* authorization: ?string
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarAuthorization(authorization: ?string) {
|
||||
return {
|
||||
type: SET_CALENDAR_AUTHORIZATION,
|
||||
authorization
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action to update the current calendar list in redux.
|
||||
*
|
||||
* @param {Array<Object>} events - The new list.
|
||||
* @returns {{
|
||||
* type: SET_CALENDAR_EVENTS,
|
||||
* events: Array<Object>
|
||||
* }}
|
||||
*/
|
||||
export function setCalendarEvents(events: Array<Object>) {
|
||||
return {
|
||||
type: SET_CALENDAR_EVENTS,
|
||||
events
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an action to update the current calendar profile email state in redux.
|
||||
*
|
||||
|
@ -243,29 +202,6 @@ export function signIn(calendarType: string): Function {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to get current profile data linked to the current calendar
|
||||
* integration that is in use.
|
||||
*
|
||||
* @param {string} calendarType - The calendar integration to which the profile
|
||||
* should be updated.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateProfile(calendarType: string): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const integration = _getCalendarIntegration(calendarType);
|
||||
|
||||
if (!integration) {
|
||||
return Promise.reject('No integration found');
|
||||
}
|
||||
|
||||
return dispatch(integration.getCurrentEmail())
|
||||
.then(email => {
|
||||
dispatch(setCalendarProfileEmail(email));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates calendar event by generating new invite URL and editing the event
|
||||
* adding some descriptive text and location.
|
||||
|
@ -312,3 +248,26 @@ export function updateCalendarEvent(id: string, calendarId: string): Function {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to get current profile data linked to the current calendar
|
||||
* integration that is in use.
|
||||
*
|
||||
* @param {string} calendarType - The calendar integration to which the profile
|
||||
* should be updated.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function updateProfile(calendarType: string): Function {
|
||||
return (dispatch: Dispatch<*>) => {
|
||||
const integration = _getCalendarIntegration(calendarType);
|
||||
|
||||
if (!integration) {
|
||||
return Promise.reject('No integration found');
|
||||
}
|
||||
|
||||
return dispatch(integration.getCurrentEmail())
|
||||
.then(email => {
|
||||
dispatch(setCalendarProfileEmail(email));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
||||
import { NavigateSectionList } from '../../base/react';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
|
@ -93,6 +93,7 @@ class BaseCalendarList extends Component<Props> {
|
|||
this._onJoinPress = this._onJoinPress.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._onRefresh = this._onRefresh.bind(this);
|
||||
this._onSecondaryAction = this._onSecondaryAction.bind(this);
|
||||
this._toDateString = this._toDateString.bind(this);
|
||||
this._toDisplayableItem = this._toDisplayableItem.bind(this);
|
||||
this._toDisplayableList = this._toDisplayableList.bind(this);
|
||||
|
@ -123,6 +124,7 @@ class BaseCalendarList extends Component<Props> {
|
|||
disabled = { disabled }
|
||||
onPress = { this._onPress }
|
||||
onRefresh = { this._onRefresh }
|
||||
onSecondaryAction = { this._onSecondaryAction }
|
||||
renderListEmptyComponent
|
||||
= { renderListEmptyComponent }
|
||||
sections = { this._toDisplayableList() } />
|
||||
|
@ -174,6 +176,20 @@ class BaseCalendarList extends Component<Props> {
|
|||
this.props.dispatch(refreshCalendar(true));
|
||||
}
|
||||
|
||||
_onSecondaryAction: string => void;
|
||||
|
||||
/**
|
||||
* Handles the list's secondary action.
|
||||
*
|
||||
* @private
|
||||
* @param {string} id - The ID of the item on which the secondary action was
|
||||
* performed.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSecondaryAction(id) {
|
||||
this.props.dispatch(openUpdateCalendarEventDialog(id, ''));
|
||||
}
|
||||
|
||||
_toDateString: Object => string;
|
||||
|
||||
/**
|
||||
|
@ -208,6 +224,7 @@ class BaseCalendarList extends Component<Props> {
|
|||
: (<AddMeetingUrlButton
|
||||
calendarId = { event.calendarId }
|
||||
eventId = { event.id } />),
|
||||
id: event.id,
|
||||
key: `${event.id}-${event.startDate}`,
|
||||
lines: [
|
||||
event.url,
|
||||
|
|
|
@ -237,9 +237,10 @@ class ConferenceNotification extends Component<Props, State> {
|
|||
|
||||
for (const event of _eventList) {
|
||||
const eventUrl
|
||||
= getURLWithoutParamsNormalized(new URL(event.url));
|
||||
= event.url
|
||||
&& getURLWithoutParamsNormalized(new URL(event.url));
|
||||
|
||||
if (eventUrl !== _currentConferenceURL) {
|
||||
if (eventUrl && eventUrl !== _currentConferenceURL) {
|
||||
if ((!eventToShow
|
||||
&& event.startDate > now
|
||||
&& event.startDate < now + ALERT_MILLISECONDS)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Dialog, DialogContent } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { updateCalendarEvent } from '../actions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the event to be updated.
|
||||
*/
|
||||
eventId: string,
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component for the add Jitsi link confirm dialog.
|
||||
*/
|
||||
class UpdateCalendarEventDialog extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code UpdateCalendarEventDialog} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
okTitleKey = 'dialog.confirm'
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'calendarSync.confirmAddLinkTitle'
|
||||
width = 'small'>
|
||||
<DialogContent>
|
||||
{ this.props.t('calendarSync.confirmAddLink') }
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Callback for the confirm button.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
this.props.dispatch(updateCalendarEvent(this.props.eventId, ''));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(UpdateCalendarEventDialog));
|
|
@ -1,3 +1,6 @@
|
|||
export { default as ConferenceNotification } from './ConferenceNotification';
|
||||
export { default as CalendarList } from './CalendarList';
|
||||
export { default as MicrosoftSignInButton } from './MicrosoftSignInButton';
|
||||
export {
|
||||
default as UpdateCalendarEventDialog
|
||||
} from './UpdateCalendarEventDialog';
|
||||
|
|
|
@ -89,40 +89,35 @@ export function _updateCalendarEntries(events: Array<Object>) {
|
|||
function _parseCalendarEntry(event, knownDomains) {
|
||||
if (event) {
|
||||
const url = _getURLFromEvent(event, knownDomains);
|
||||
const startDate = Date.parse(event.startDate);
|
||||
const endDate = Date.parse(event.endDate);
|
||||
|
||||
// we only filter events without url on mobile, this is temporary
|
||||
// till we implement event edit on mobile
|
||||
if (url || navigator.product !== 'ReactNative') {
|
||||
const startDate = Date.parse(event.startDate);
|
||||
const endDate = Date.parse(event.endDate);
|
||||
|
||||
// we want to hide all events that
|
||||
// - has no start or end date
|
||||
// - for web, if there is no url and we cannot edit the event (has
|
||||
// no calendarId)
|
||||
if (isNaN(startDate)
|
||||
|| isNaN(endDate)
|
||||
|| (navigator.product !== 'ReactNative'
|
||||
&& !url
|
||||
&& !event.calendarId)) {
|
||||
logger.debug(
|
||||
'Skipping invalid calendar event',
|
||||
event.title,
|
||||
event.startDate,
|
||||
event.endDate,
|
||||
url,
|
||||
event.calendarId
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
calendarId: event.calendarId,
|
||||
endDate,
|
||||
id: event.id,
|
||||
startDate,
|
||||
title: event.title,
|
||||
url
|
||||
};
|
||||
}
|
||||
// we want to hide all events that
|
||||
// - has no start or end date
|
||||
// - for web, if there is no url and we cannot edit the event (has
|
||||
// no calendarId)
|
||||
if (isNaN(startDate)
|
||||
|| isNaN(endDate)
|
||||
|| (navigator.product !== 'ReactNative'
|
||||
&& !url
|
||||
&& !event.calendarId)) {
|
||||
logger.debug(
|
||||
'Skipping invalid calendar event',
|
||||
event.title,
|
||||
event.startDate,
|
||||
event.endDate,
|
||||
url,
|
||||
event.calendarId
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
calendarId: event.calendarId,
|
||||
endDate,
|
||||
id: event.id,
|
||||
startDate,
|
||||
title: event.title,
|
||||
url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { NativeModules } from 'react-native';
|
||||
// @flow
|
||||
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
import RNCalendarEvents from 'react-native-calendar-events';
|
||||
|
||||
import { getShareInfoText } from '../invite';
|
||||
|
||||
import { setCalendarAuthorization } from './actions';
|
||||
import { FETCH_END_DAYS, FETCH_START_DAYS } from './constants';
|
||||
import { _updateCalendarEntries } from './functions';
|
||||
|
@ -9,6 +13,39 @@ export * from './functions.any';
|
|||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Adds a Jitsi link to a calendar entry.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {string} id - The ID of the calendar entry.
|
||||
* @param {string} link - The link to add info with.
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export function addLinkToCalendarEntry(
|
||||
state: Object, id: string, link: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getShareInfoText(state, link, true).then(shareInfoText => {
|
||||
RNCalendarEvents.findEventById(id).then(event => {
|
||||
const updateText = `${event.description}\n\n${shareInfoText}`;
|
||||
const updateObject = {
|
||||
id: event.id,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
notes: updateText
|
||||
},
|
||||
android: {
|
||||
description: updateText
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
RNCalendarEvents.saveEvent(event.title, updateObject)
|
||||
.then(resolve, reject);
|
||||
}, reject);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the calendar feature is enabled by the app. For
|
||||
* example, Apple through its App Store requires
|
||||
|
|
|
@ -8,7 +8,6 @@ import { connect as reactReduxConnect } from 'react-redux';
|
|||
|
||||
import { appNavigate } from '../../app';
|
||||
import { connect, disconnect } from '../../base/connection';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { getParticipantCount } from '../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../base/react';
|
||||
import {
|
||||
|
@ -315,11 +314,7 @@ class Conference extends Component<Props> {
|
|||
this._renderConferenceNotification()
|
||||
}
|
||||
|
||||
{/*
|
||||
* The dialogs are in the topmost stacking layers.
|
||||
*/
|
||||
this.props._reducedUI || <DialogContainer />
|
||||
}
|
||||
<NotificationsContainer />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
|
|||
|
||||
import { obtainConfig } from '../../base/config';
|
||||
import { connect, disconnect } from '../../base/connection';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Filmstrip } from '../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../invite';
|
||||
|
@ -226,7 +225,6 @@ class Conference extends Component<Props> {
|
|||
{ filmstripOnly || <Toolbox /> }
|
||||
{ filmstripOnly || <SidePanel /> }
|
||||
|
||||
<DialogContainer />
|
||||
<NotificationsContainer />
|
||||
|
||||
<CalleeInfoContainer />
|
||||
|
|
|
@ -438,7 +438,7 @@ export function getShareInfoText(
|
|||
|
||||
if (!dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
|
||||
// URLs for fetching dial in numbers not defined
|
||||
return Promise.reject();
|
||||
return Promise.resolve(infoText);
|
||||
}
|
||||
|
||||
numbersPromise = Promise.all([
|
||||
|
|
|
@ -7,7 +7,6 @@ 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 { Platform, Watermarks } from '../../base/react';
|
||||
import { CalendarList } from '../../calendar-sync';
|
||||
|
@ -168,9 +167,6 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
ref = { this._setAdditionalContentRef } />
|
||||
: null }
|
||||
</div>
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<DialogContainer />
|
||||
</AtlasKitThemeProvider>
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue