[RN] Implement Notifications on mobile
This commit is contained in:
parent
00f18e9369
commit
ffd0827354
|
@ -27,6 +27,9 @@
|
|||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e5cd";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -15,6 +15,7 @@
|
|||
<glyph unicode="" glyph-name="navigate_next" d="M426 768l256-256-256-256-60 60 196 196-196 196z" />
|
||||
<glyph unicode="" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
|
||||
<glyph unicode="" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
|
||||
<glyph unicode="" glyph-name="close" d="M810 750l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
|
||||
<glyph unicode="" glyph-name="menu" d="M128 768h768v-86h-768v86zM128 470v84h768v-84h-768zM128 256v86h768v-86h-768z" />
|
||||
<glyph unicode="" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
|
||||
<glyph unicode="" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
|
||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -24,5 +24,16 @@ export const ColorPalette = {
|
|||
darkGrey: '#555555',
|
||||
green: '#40b183',
|
||||
red: '#D00000',
|
||||
white: 'white'
|
||||
white: 'white',
|
||||
|
||||
/**
|
||||
* These are colors from the atlaskit to be used on mobile, when needed.
|
||||
*
|
||||
* FIXME: Maybe a better solution would be good, or a native packaging of
|
||||
* the respective atlaskit components.
|
||||
*/
|
||||
G400: '#00875A', // Slime
|
||||
N500: '#42526E', // McFanning
|
||||
R400: '#DE350B', // Red dirt
|
||||
Y200: '#FFC400' // Pub mix
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ import { createDesiredLocalTracks } from '../../base/tracks';
|
|||
import { ConferenceNotification } from '../../calendar-sync';
|
||||
import { Filmstrip } from '../../filmstrip';
|
||||
import { LargeVideo } from '../../large-video';
|
||||
import { NotificationsContainer } from '../../notifications';
|
||||
import { setToolboxVisible, Toolbox } from '../../toolbox';
|
||||
|
||||
import ConferenceIndicators from './ConferenceIndicators';
|
||||
|
@ -267,6 +268,8 @@ class Conference extends Component<Props> {
|
|||
this._renderConferenceNotification()
|
||||
}
|
||||
|
||||
<NotificationsContainer />
|
||||
|
||||
{/*
|
||||
* The dialogs are in the topmost stacking layers.
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
|
||||
import { isFilmstripVisible } from '../../functions';
|
||||
|
||||
import LocalThumbnail from './LocalThumbnail';
|
||||
import styles from './styles';
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
@ -188,7 +190,7 @@ class Filmstrip extends Component<Props> {
|
|||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participants = state['features/base/participants'];
|
||||
const { enabled, visible } = state['features/filmstrip'];
|
||||
const { enabled } = state['features/filmstrip'];
|
||||
|
||||
return {
|
||||
/**
|
||||
|
@ -215,7 +217,7 @@ function _mapStateToProps(state) {
|
|||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: visible && participants.length > 1
|
||||
_visible: isFilmstripVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ColorPalette } from '../../base/styles';
|
||||
import { FILMSTRIP_SIZE } from '../constants';
|
||||
|
||||
/**
|
||||
* Size for the Avatar.
|
||||
|
@ -44,12 +45,15 @@ export default {
|
|||
...filmstrip,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
height: 90
|
||||
height: FILMSTRIP_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the wide {@link Filmstrip} version which displays thumbnails
|
||||
* in a column on the short size of the screen.
|
||||
*
|
||||
* NOTE: width is calculated based on the children, but it should also align
|
||||
* to {@code FILMSTRIP_SIZE}.
|
||||
*/
|
||||
filmstripWide: {
|
||||
...filmstrip,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* The height of the filmstrip in narrow aspect ratio, or width in wide.
|
||||
*/
|
||||
export const FILMSTRIP_SIZE = 90;
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
/**
|
||||
* Returns true if the filmstrip on mobile is visible, false otherwise.
|
||||
*
|
||||
* NOTE: Filmstrip on mobile behaves differently to web, and is only visible
|
||||
* when there are at least 2 participants.
|
||||
*
|
||||
* @param {Object | Function} stateful - The Object or Function that can be
|
||||
* resolved to a Redux state object with the toState function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFilmstripVisible(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const { length: participantCount } = state['features/base/participants'];
|
||||
|
||||
return state['features/filmstrip'].visible && participantCount > 1;
|
||||
}
|
|
@ -4,9 +4,25 @@ import {
|
|||
getParticipantCount,
|
||||
getPinnedParticipant
|
||||
} from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Returns true if the filmstrip on mobile is visible, false otherwise.
|
||||
*
|
||||
* NOTE: Filmstrip on web behaves differently to mobile, much simpler, but so
|
||||
* function lies here only for the sake of consistency and to avoid flow errors
|
||||
* on import.
|
||||
*
|
||||
* @param {Object | Function} stateful - The Object or Function that can be
|
||||
* resolved to a Redux state object with the toState function.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFilmstripVisible(stateful: Object | Function) {
|
||||
return toState(stateful)['features/filmstrip'].visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the remote video thumbnails should be displayed/visible in
|
||||
* the filmstrip.
|
|
@ -1,6 +1,7 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* The type of (redux) action which signals that all the stored notifications
|
||||
* need to be cleared.
|
||||
*
|
||||
* {
|
||||
* type: CLEAR_NOTIFICATIONS
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_NOTIFICATIONS = Symbol('CLEAR_NOTIFICATIONS');
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a specific notification should
|
||||
* not be displayed anymore.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
CLEAR_NOTIFICATIONS,
|
||||
HIDE_NOTIFICATION,
|
||||
SET_NOTIFICATIONS_ENABLED,
|
||||
SHOW_NOTIFICATION
|
||||
|
@ -6,6 +9,19 @@ import {
|
|||
|
||||
import { NOTIFICATION_TYPE } from './constants';
|
||||
|
||||
/**
|
||||
* Clears (removes) all the notifications.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLEAR_NOTIFICATIONS
|
||||
* }}
|
||||
*/
|
||||
export function clearNotifications() {
|
||||
return {
|
||||
type: CLEAR_NOTIFICATIONS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the notification with the passed in id.
|
||||
*
|
||||
|
@ -16,7 +32,7 @@ import { NOTIFICATION_TYPE } from './constants';
|
|||
* uid: number
|
||||
* }}
|
||||
*/
|
||||
export function hideNotification(uid) {
|
||||
export function hideNotification(uid: number) {
|
||||
return {
|
||||
type: HIDE_NOTIFICATION,
|
||||
uid
|
||||
|
@ -32,7 +48,7 @@ export function hideNotification(uid) {
|
|||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setNotificationsEnabled(enabled) {
|
||||
export function setNotificationsEnabled(enabled: boolean) {
|
||||
return {
|
||||
type: SET_NOTIFICATIONS_ENABLED,
|
||||
enabled
|
||||
|
@ -45,7 +61,7 @@ export function setNotificationsEnabled(enabled) {
|
|||
* @param {Object} props - The props needed to show the notification component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showErrorNotification(props) {
|
||||
export function showErrorNotification(props: Object) {
|
||||
return showNotification({
|
||||
...props,
|
||||
appearance: NOTIFICATION_TYPE.ERROR
|
||||
|
@ -65,7 +81,7 @@ export function showErrorNotification(props) {
|
|||
* uid: number
|
||||
* }}
|
||||
*/
|
||||
export function showNotification(props = {}, timeout) {
|
||||
export function showNotification(props: Object = {}, timeout: ?number) {
|
||||
return {
|
||||
type: SHOW_NOTIFICATION,
|
||||
props,
|
||||
|
@ -80,7 +96,7 @@ export function showNotification(props = {}, timeout) {
|
|||
* @param {Object} props - The props needed to show the notification component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showWarningNotification(props) {
|
||||
export function showWarningNotification(props: Object) {
|
||||
return showNotification({
|
||||
...props,
|
||||
appearance: NOTIFICATION_TYPE.WARNING
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import { NOTIFICATION_TYPE } from '../constants';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Display appearance for the component, passed directly to the
|
||||
* notification.
|
||||
*/
|
||||
appearance: string,
|
||||
|
||||
/**
|
||||
* The text to display in the body of the notification. If not passed
|
||||
* in, the passed in descriptionKey will be used.
|
||||
*/
|
||||
defaultTitleKey: string,
|
||||
|
||||
/**
|
||||
* A description string that can be used in addition to the prop
|
||||
* descriptionKey.
|
||||
*/
|
||||
description: string,
|
||||
|
||||
/**
|
||||
* The translation arguments that may be necessary for the description.
|
||||
*/
|
||||
descriptionArguments: Object,
|
||||
|
||||
/**
|
||||
* The translation key to use as the body of the notification.
|
||||
*/
|
||||
descriptionKey: string,
|
||||
|
||||
/**
|
||||
* Whether the support link should be hidden in the case of an error
|
||||
* message.
|
||||
*/
|
||||
hideErrorSupportLink: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the dismiss button should be displayed.
|
||||
*/
|
||||
isDismissAllowed: boolean,
|
||||
|
||||
/**
|
||||
* Callback invoked when the user clicks to dismiss the notification.
|
||||
*/
|
||||
onDismissed: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The text to display at the top of the notification. If not passed in,
|
||||
* the passed in titleKey will be used.
|
||||
*/
|
||||
title: string,
|
||||
|
||||
/**
|
||||
* The translation arguments that may be necessary for the title.
|
||||
*/
|
||||
titleArguments: Object,
|
||||
|
||||
/**
|
||||
* The translation key to display as the title of the notification if
|
||||
* no title is provided.
|
||||
*/
|
||||
titleKey: string,
|
||||
|
||||
/**
|
||||
* The unique identifier for the notification.
|
||||
*/
|
||||
uid: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class for {@code Notification} component.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class AbstractNotification<P: Props> extends Component<P> {
|
||||
/**
|
||||
* Default values for {@code Notification} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
appearance: NOTIFICATION_TYPE.NORMAL,
|
||||
isDismissAllowed: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Notification} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>
|
||||
|
||||
/**
|
||||
* Returns the description array to be displayed.
|
||||
*
|
||||
* @protected
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
_getDescription() {
|
||||
const {
|
||||
description,
|
||||
descriptionArguments,
|
||||
descriptionKey,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
const descriptionArray = [];
|
||||
|
||||
descriptionKey
|
||||
&& descriptionArray.push(t(descriptionKey, descriptionArguments));
|
||||
|
||||
description && descriptionArray.push(description);
|
||||
|
||||
return descriptionArray;
|
||||
}
|
||||
|
||||
_onDismissed: () => void;
|
||||
|
||||
/**
|
||||
* Callback to dismiss the notification.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed() {
|
||||
this.props.onDismissed(this.props.uid);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import { getOverlayToRender } from '../../overlay';
|
||||
|
||||
import { hideNotification } from '../actions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The notifications to be displayed, with the first index being the
|
||||
* notification at the top and the rest shown below it in order.
|
||||
*/
|
||||
_notifications: Array<Object>,
|
||||
|
||||
/**
|
||||
* Invoked to update the redux store in order to remove notifications.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class for {@code NotificationsContainer} component.
|
||||
*/
|
||||
export default class AbstractNotificationsContainer<P: Props>
|
||||
extends Component<P> {
|
||||
/**
|
||||
* A timeout id returned by setTimeout.
|
||||
*/
|
||||
_notificationDismissTimeout: ?TimeoutID;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractNotificationsContainer} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
* The timeout set for automatically dismissing a displayed
|
||||
* notification. This value is set on the instance and not state to
|
||||
* avoid additional re-renders.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
this._notificationDismissTimeout = null;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout if the currently displayed notification has changed.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps: P) {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
if (_notifications.length) {
|
||||
const notification = _notifications[0];
|
||||
const previousNotification
|
||||
= prevProps._notifications.length
|
||||
? prevProps._notifications[0]
|
||||
: undefined;
|
||||
|
||||
if (notification !== previousNotification) {
|
||||
this._clearNotificationDismissTimeout();
|
||||
|
||||
if (notification) {
|
||||
const { timeout, uid } = notification;
|
||||
|
||||
this._notificationDismissTimeout = setTimeout(() => {
|
||||
// Perform a no-op if a timeout is not specified.
|
||||
if (Number.isInteger(timeout)) {
|
||||
this._onDismissed(uid);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
} else if (this._notificationDismissTimeout) {
|
||||
// Clear timeout when all notifications are cleared (e.g external
|
||||
// call to clear them)
|
||||
this._clearNotificationDismissTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any dismissal timeout that is still active.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this._clearNotificationDismissTimeout();
|
||||
}
|
||||
|
||||
_onDismissed: number => void;
|
||||
|
||||
/**
|
||||
* Clears the running notification dismiss timeout, if any.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_clearNotificationDismissTimeout() {
|
||||
this._notificationDismissTimeout
|
||||
&& clearTimeout(this._notificationDismissTimeout);
|
||||
|
||||
this._notificationDismissTimeout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an action to remove the notification from the redux store so it
|
||||
* stops displaying.
|
||||
*
|
||||
* @param {number} uid - The id of the notification to be removed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed(uid) {
|
||||
this._clearNotificationDismissTimeout();
|
||||
|
||||
this.props.dispatch(hideNotification(uid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated NotificationsContainer's
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _notifications: Array
|
||||
* }}
|
||||
*/
|
||||
export function _abstractMapStateToProps(state: Object) {
|
||||
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
||||
const { enabled, notifications } = state['features/notifications'];
|
||||
|
||||
return {
|
||||
_notifications: enabled && !isAnyOverlayVisible ? notifications : []
|
||||
};
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { Icon } from '../../base/font-icons';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { NOTIFICATION_TYPE } from '../constants';
|
||||
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from './AbstractNotification';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
appearance,
|
||||
isDismissAllowed,
|
||||
t,
|
||||
title,
|
||||
titleArguments,
|
||||
titleKey
|
||||
} = this.props;
|
||||
|
||||
let notificationStyle;
|
||||
|
||||
switch (appearance) {
|
||||
case NOTIFICATION_TYPE.ERROR:
|
||||
notificationStyle = styles.notificationTypeError;
|
||||
break;
|
||||
case NOTIFICATION_TYPE.NORMAL:
|
||||
notificationStyle = styles.notificationTypeNormal;
|
||||
break;
|
||||
case NOTIFICATION_TYPE.SUCCESS:
|
||||
notificationStyle = styles.notificationTypeSuccess;
|
||||
break;
|
||||
case NOTIFICATION_TYPE.WARNING:
|
||||
notificationStyle = styles.notificationTypeWarning;
|
||||
break;
|
||||
case NOTIFICATION_TYPE.INFO:
|
||||
default:
|
||||
notificationStyle = styles.notificationTypeInfo;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.notification,
|
||||
notificationStyle
|
||||
] }>
|
||||
<View style = { styles.contentColumn }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notificationTitle }>
|
||||
<Text style = { styles.titleText }>
|
||||
{
|
||||
title || t(titleKey, titleArguments)
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notificationContent }>
|
||||
{
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
this._getDescription().map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
style = { styles.contentText }>
|
||||
{ line }
|
||||
</Text>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
{
|
||||
isDismissAllowed
|
||||
&& <View style = { styles.actionColumn }>
|
||||
<TouchableOpacity onPress = { this._onDismissed }>
|
||||
<Icon
|
||||
name = { 'close' }
|
||||
style = { styles.dismissIcon } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>;
|
||||
|
||||
_onDismissed: () => void;
|
||||
}
|
||||
|
||||
export default translate(Notification);
|
|
@ -5,13 +5,16 @@ import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
|
|||
import ErrorIcon from '@atlaskit/icon/glyph/error';
|
||||
import WarningIcon from '@atlaskit/icon/glyph/warning';
|
||||
import { colors } from '@atlaskit/theme';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { NOTIFICATION_TYPE } from '../constants';
|
||||
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from './AbstractNotification';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
|
@ -32,110 +35,7 @@ const ICON_COLOR = {
|
|||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class Notification extends Component<*> {
|
||||
/**
|
||||
* Default values for {@code Notification} component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code Notification} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Display appearance for the component, passed directly to
|
||||
* {@code Flag}.
|
||||
*/
|
||||
appearance: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The text to display in the body of the notification. If not passed
|
||||
* in, the passed in descriptionKey will be used.
|
||||
*/
|
||||
defaultTitleKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* A description string that can be used in addition to the prop
|
||||
* descriptionKey.
|
||||
*/
|
||||
description: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The translation arguments that may be necessary for the description.
|
||||
*/
|
||||
descriptionArguments: PropTypes.object,
|
||||
|
||||
/**
|
||||
* The translation key to use as the body of the notification.
|
||||
*/
|
||||
descriptionKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether the support link should be hidden in the case of an error
|
||||
* message.
|
||||
*/
|
||||
hideErrorSupportLink: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether or not the dismiss button should be displayed. This is passed
|
||||
* in by {@code FlagGroup}.
|
||||
*/
|
||||
isDismissAllowed: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Callback invoked when the user clicks to dismiss the notification.
|
||||
* this is passed in by {@code FlagGroup}.
|
||||
*/
|
||||
onDismissed: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: PropTypes.func,
|
||||
|
||||
/**
|
||||
* The text to display at the top of the notification. If not passed in,
|
||||
* the passed in titleKey will be used.
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The translation arguments that may be necessary for the title.
|
||||
*/
|
||||
titleArguments: PropTypes.object,
|
||||
|
||||
/**
|
||||
* The translation key to display as the title of the notification if
|
||||
* no title is provided.
|
||||
*/
|
||||
titleKey: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The unique identifier for the notification. Passed back by the
|
||||
* {@code Flag} component in the onDismissed callback.
|
||||
*/
|
||||
uid: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Notification} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -168,6 +68,8 @@ class Notification extends Component<*> {
|
|||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>
|
||||
|
||||
_onDismissed: () => void;
|
||||
|
||||
/**
|
||||
|
@ -178,32 +80,15 @@ class Notification extends Component<*> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderDescription() {
|
||||
const {
|
||||
description,
|
||||
descriptionArguments,
|
||||
descriptionKey,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ descriptionKey
|
||||
? t(descriptionKey, descriptionArguments) : null }
|
||||
{ description || null }
|
||||
{
|
||||
this._getDescription()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls back into {@code FlagGroup} to dismiss the notification.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed() {
|
||||
this.props.onDismissed(this.props.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the support page.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { FILMSTRIP_SIZE, isFilmstripVisible } from '../../filmstrip';
|
||||
import { HANGUP_BUTTON_SIZE } from '../../toolbox';
|
||||
|
||||
import AbstractNotificationsContainer, {
|
||||
_abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from './AbstractNotificationsContainer';
|
||||
import Notification from './Notification';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* True if the {@code Filmstrip} is visible, false otherwise.
|
||||
*/
|
||||
_filmstripVisible: boolean,
|
||||
|
||||
/**
|
||||
* True if the {@ćode Toolbox} is visible, false otherwise.
|
||||
*/
|
||||
_toolboxVisible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The margin of the container to be kept from other components.
|
||||
*/
|
||||
const CONTAINER_MARGIN = 10;
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays notifications and handles
|
||||
* automatic dismissmal after a notification is shown for a defined timeout
|
||||
* period.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
if (!_notifications || !_notifications.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notificationOverlay }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.notificationContainer,
|
||||
this._getContainerStyle()
|
||||
] }>
|
||||
{
|
||||
_notifications.map(notification => {
|
||||
const { props, uid } = notification;
|
||||
|
||||
return (
|
||||
<Notification
|
||||
{ ...props }
|
||||
key = { uid }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { uid } />
|
||||
|
||||
);
|
||||
})
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a style object that is to be used for the notification
|
||||
* container.
|
||||
*
|
||||
* @private
|
||||
* @returns {?Object}
|
||||
*/
|
||||
_getContainerStyle() {
|
||||
const { _filmstripVisible, _toolboxVisible } = this.props;
|
||||
|
||||
// The filmstrip only affects the position if we're on a narrow view.
|
||||
const _narrow = isNarrowAspectRatio(this);
|
||||
|
||||
let bottom = 0;
|
||||
let right = 0;
|
||||
|
||||
// The container needs additional distance from bottom when the
|
||||
// filmstrip or the toolbox is visible.
|
||||
_filmstripVisible && !_narrow && (right += FILMSTRIP_SIZE);
|
||||
_filmstripVisible && _narrow && (bottom += FILMSTRIP_SIZE);
|
||||
_toolboxVisible && (bottom += HANGUP_BUTTON_SIZE);
|
||||
|
||||
bottom += CONTAINER_MARGIN;
|
||||
|
||||
return {
|
||||
bottom,
|
||||
right
|
||||
};
|
||||
}
|
||||
|
||||
_onDismissed: number => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated NotificationsContainer's
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _filmstripVisible: boolean,
|
||||
* _notifications: Array,
|
||||
* _showNotifications: boolean,
|
||||
* _toolboxVisible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
_toolboxVisible: state['features/toolbox'].visible
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(
|
||||
makeAspectRatioAware(NotificationsContainer));
|
|
@ -1,11 +1,14 @@
|
|||
// @flow
|
||||
|
||||
import { FlagGroup } from '@atlaskit/flag';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { hideNotification } from '../actions';
|
||||
|
||||
import { Notification } from './';
|
||||
import AbstractNotificationsContainer, {
|
||||
_abstractMapStateToProps as _mapStateToProps,
|
||||
type Props
|
||||
} from './AbstractNotificationsContainer';
|
||||
import Notification from './Notification';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays notifications and handles
|
||||
|
@ -14,91 +17,7 @@ import { Notification } from './';
|
|||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class NotificationsContainer extends Component {
|
||||
/**
|
||||
* {@code NotificationsContainer} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The notifications to be displayed, with the first index being the
|
||||
* notification at the top and the rest shown below it in order.
|
||||
*/
|
||||
_notifications: PropTypes.array,
|
||||
|
||||
/**
|
||||
* Whether or not notifications should be displayed at all. If not,
|
||||
* notifications will be dismissed immediately.
|
||||
*/
|
||||
_showNotifications: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Invoked to update the redux store in order to remove notifications.
|
||||
*/
|
||||
dispatch: PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code NotificationsContainer} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
* The timeout set for automatically dismissing a displayed
|
||||
* notification. This value is set on the instance and not state to
|
||||
* avoid additional re-renders.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
this._notificationDismissTimeout = null;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout if the currently displayed notification has changed.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidUpdate() {
|
||||
const { _notifications, _showNotifications } = this.props;
|
||||
|
||||
if (_notifications.length) {
|
||||
const notification = _notifications[0];
|
||||
|
||||
if (!_showNotifications || this._notificationDismissTimeout) {
|
||||
|
||||
// No-op because there should already be a notification that
|
||||
// is waiting for dismissal.
|
||||
} else {
|
||||
const { timeout, uid } = notification;
|
||||
|
||||
this._notificationDismissTimeout = setTimeout(() => {
|
||||
// Perform a no-op if a timeout is not specified.
|
||||
if (Number.isInteger(timeout)) {
|
||||
this._onDismissed(uid);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any dismissal timeout that is still active.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._notificationDismissTimeout);
|
||||
}
|
||||
class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -114,20 +33,7 @@ class NotificationsContainer extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an action to remove the notification from the redux store so it
|
||||
* stops displaying.
|
||||
*
|
||||
* @param {number} flagUid - The id of the notification to be removed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed(flagUid) {
|
||||
clearTimeout(this._notificationDismissTimeout);
|
||||
this._notificationDismissTimeout = null;
|
||||
|
||||
this.props.dispatch(hideNotification(flagUid));
|
||||
}
|
||||
_onDismissed: number => void;
|
||||
|
||||
/**
|
||||
* Renders notifications to display as ReactElements. An empty array will
|
||||
|
@ -137,11 +43,7 @@ class NotificationsContainer extends Component {
|
|||
* @returns {ReactElement[]}
|
||||
*/
|
||||
_renderFlags() {
|
||||
const { _notifications, _showNotifications } = this.props;
|
||||
|
||||
if (!_showNotifications) {
|
||||
return [];
|
||||
}
|
||||
const { _notifications } = this.props;
|
||||
|
||||
return _notifications.map(notification => {
|
||||
const { props, uid } = notification;
|
||||
|
@ -161,37 +63,4 @@ class NotificationsContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated NotificationsContainer's
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _notifications: Array
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
// TODO: Per existing behavior, notifications should not display when an
|
||||
// overlay is visible. This logic for checking overlay display can likely be
|
||||
// simplified.
|
||||
const {
|
||||
connectionEstablished,
|
||||
haveToReload,
|
||||
isMediaPermissionPromptVisible,
|
||||
suspendDetected
|
||||
} = state['features/overlay'];
|
||||
const isAnyOverlayVisible = (connectionEstablished && haveToReload)
|
||||
|| isMediaPermissionPromptVisible
|
||||
|| suspendDetected
|
||||
|| state['features/base/jwt'].calleeInfoVisible;
|
||||
|
||||
const { enabled, notifications } = state['features/notifications'];
|
||||
|
||||
return {
|
||||
_notifications: notifications,
|
||||
_showNotifications: enabled && !isAnyOverlayVisible
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NotificationsContainer);
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { BoxModel, createStyleSheet, ColorPalette } from '../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature notifications.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
|
||||
/**
|
||||
* The content (left) column of the notification.
|
||||
*/
|
||||
contentColumn: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* Test style of the notification.
|
||||
*/
|
||||
contentText: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss icon style.
|
||||
*/
|
||||
dismissIcon: {
|
||||
alignSelf: 'center',
|
||||
color: ColorPalette.white,
|
||||
fontSize: 16,
|
||||
padding: 1.5 * BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost view of a single notification.
|
||||
*/
|
||||
notification: {
|
||||
borderRadius: 5,
|
||||
flexDirection: 'row',
|
||||
marginTop: 0.5 * BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost container of a list of notifications.
|
||||
*/
|
||||
notificationContainer: {
|
||||
alignItems: 'flex-start',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
padding: 2 * BoxModel.padding,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for the message (without title).
|
||||
*/
|
||||
notificationContent: {
|
||||
flexDirection: 'column',
|
||||
paddingVertical: 0.5 * BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* A full screen overlay to help to position the container.
|
||||
*/
|
||||
notificationOverlay: {
|
||||
...StyleSheet.absoluteFillObject
|
||||
},
|
||||
|
||||
/**
|
||||
* The View containing the title.
|
||||
*/
|
||||
notificationTitle: {
|
||||
paddingVertical: 0.5 * BoxModel.padding
|
||||
},
|
||||
|
||||
/**
|
||||
* Background settings for different notification types.
|
||||
*/
|
||||
|
||||
notificationTypeError: {
|
||||
backgroundColor: ColorPalette.R400
|
||||
},
|
||||
|
||||
notificationTypeInfo: {
|
||||
backgroundColor: ColorPalette.N500
|
||||
},
|
||||
|
||||
notificationTypeNormal: {
|
||||
// NOTE: Mobile has black background when the large video doesn't render
|
||||
// a stream, so we avoid using black as the background of the normal
|
||||
// type notifications.
|
||||
backgroundColor: ColorPalette.N500
|
||||
},
|
||||
|
||||
notificationTypeSuccess: {
|
||||
backgroundColor: ColorPalette.G400
|
||||
},
|
||||
|
||||
notificationTypeWarning: {
|
||||
backgroundColor: ColorPalette.Y200
|
||||
},
|
||||
|
||||
/**
|
||||
* Title text style.
|
||||
*/
|
||||
titleText: {
|
||||
color: ColorPalette.white,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
});
|
|
@ -2,4 +2,5 @@ export * from './actions';
|
|||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/* @flow */
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
|
||||
import { clearNotifications } from './actions';
|
||||
|
||||
/**
|
||||
* StateListenerRegistry provides a reliable way to detect the leaving of a
|
||||
* conference, where we need to clean up the notifications.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => getCurrentConference(state),
|
||||
/* listener */ (conference, { dispatch }) => {
|
||||
if (!conference) {
|
||||
dispatch(clearNotifications());
|
||||
}
|
||||
}
|
||||
);
|
|
@ -1,6 +1,7 @@
|
|||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CLEAR_NOTIFICATIONS,
|
||||
HIDE_NOTIFICATION,
|
||||
SET_NOTIFICATIONS_ENABLED,
|
||||
SHOW_NOTIFICATION
|
||||
|
@ -28,6 +29,11 @@ const DEFAULT_STATE = {
|
|||
ReducerRegistry.register('features/notifications',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CLEAR_NOTIFICATIONS:
|
||||
return {
|
||||
...state,
|
||||
notifications: []
|
||||
};
|
||||
case HIDE_NOTIFICATION:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -3,40 +3,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
|
||||
import PageReloadOverlay from './PageReloadOverlay';
|
||||
import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
|
||||
import SuspendedOverlay from './SuspendedOverlay';
|
||||
import UserMediaPermissionsFilmstripOnlyOverlay
|
||||
from './UserMediaPermissionsFilmstripOnlyOverlay';
|
||||
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
|
||||
import { getOverlayToRender } from '../functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The lazily-initialized list of overlay React {@link Component} types used The
|
||||
* user interface is filmstrip-only.
|
||||
*
|
||||
* XXX The value is meant to be compile-time defined so it does not contradict
|
||||
* our coding style to not have global values that are runtime defined and
|
||||
* merely works around side effects of circular imports.
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
let _filmstripOnlyOverlays;
|
||||
|
||||
/**
|
||||
* The lazily-initialized list of overlay React {@link Component} types used The
|
||||
* user interface is not filmstrip-only.
|
||||
*
|
||||
* XXX The value is meant to be compile-time defined so it does not contradict
|
||||
* our coding style to not have global values that are runtime defined and
|
||||
* merely works around side effects of circular imports.
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
let _nonFilmstripOnlyOverlays;
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@code OverlayContainer}.
|
||||
*/
|
||||
|
@ -68,44 +38,6 @@ class OverlayContainer extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of overlay React {@link Component} types to be rendered by
|
||||
* {@code OverlayContainer}. The list is lazily initialized the first time it is
|
||||
* required in order to works around side effects of circular imports.
|
||||
*
|
||||
* @param {boolean} filmstripOnly - The indicator which determines whether the
|
||||
* user interface is filmstrip-only.
|
||||
* @returns {Array} The list of overlay React {@code Component} types to be
|
||||
* rendered by {@code OverlayContainer}.
|
||||
*/
|
||||
function _getOverlays(filmstripOnly) {
|
||||
let overlays;
|
||||
|
||||
if (filmstripOnly) {
|
||||
if (!(overlays = _filmstripOnlyOverlays)) {
|
||||
overlays = _filmstripOnlyOverlays = [
|
||||
PageReloadFilmstripOnlyOverlay,
|
||||
SuspendedFilmstripOnlyOverlay,
|
||||
UserMediaPermissionsFilmstripOnlyOverlay
|
||||
];
|
||||
}
|
||||
} else if (!(overlays = _nonFilmstripOnlyOverlays)) {
|
||||
overlays = _nonFilmstripOnlyOverlays = [
|
||||
PageReloadOverlay
|
||||
];
|
||||
|
||||
// Mobile only has a PageReloadOverlay.
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
overlays.push(...[
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return overlays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated {@code OverlayContainer}'s
|
||||
* props.
|
||||
|
@ -117,30 +49,12 @@ function _getOverlays(filmstripOnly) {
|
|||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
// XXX In the future interfaceConfig is expected to not be a global variable
|
||||
// but a redux state like config. Hence, the variable filmStripOnly
|
||||
// naturally belongs here in preparation for the future.
|
||||
const filmstripOnly
|
||||
= typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly;
|
||||
let overlay;
|
||||
|
||||
for (const o of _getOverlays(filmstripOnly)) {
|
||||
// react-i18n / react-redux wrap components and thus we cannot access
|
||||
// the wrapped component's static methods directly.
|
||||
const component = o.WrappedComponent || o;
|
||||
|
||||
if (component.needsRender(state)) {
|
||||
overlay = o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* The React {@link Component} type of overlay to be rendered by the
|
||||
* associated {@code OverlayContainer}.
|
||||
*/
|
||||
overlay
|
||||
overlay: getOverlayToRender(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,15 @@
|
|||
export { default as OverlayContainer } from './OverlayContainer';
|
||||
export {
|
||||
default as PageReloadFilmstripOnlyOverlay
|
||||
} from './PageReloadFilmstripOnlyOverlay';
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
export {
|
||||
default as SuspendedFilmstripOnlyOverlay
|
||||
} from './SuspendedFilmstripOnlyOverlay';
|
||||
export { default as SuspendedOverlay } from './SuspendedOverlay';
|
||||
export {
|
||||
default as UserMediaPermissionsFilmstripOnlyOverlay
|
||||
} from './UserMediaPermissionsFilmstripOnlyOverlay';
|
||||
export {
|
||||
default as UserMediaPermissionsOverlay
|
||||
} from './UserMediaPermissionsOverlay';
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
PageReloadFilmstripOnlyOverlay,
|
||||
PageReloadOverlay,
|
||||
SuspendedFilmstripOnlyOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsFilmstripOnlyOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
} from './components';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Returns the list of available overlays that might be rendered.
|
||||
*
|
||||
* @private
|
||||
* @returns {Array<?React$ComponentType<*>>}
|
||||
*/
|
||||
function _getOverlays() {
|
||||
const filmstripOnly
|
||||
= typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly;
|
||||
let overlays;
|
||||
|
||||
if (filmstripOnly) {
|
||||
overlays = [
|
||||
PageReloadFilmstripOnlyOverlay,
|
||||
SuspendedFilmstripOnlyOverlay,
|
||||
UserMediaPermissionsFilmstripOnlyOverlay
|
||||
];
|
||||
} else {
|
||||
overlays = [
|
||||
PageReloadOverlay
|
||||
];
|
||||
}
|
||||
|
||||
// Mobile only has a PageReloadOverlay.
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
overlays.push(...[
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
]);
|
||||
}
|
||||
|
||||
return overlays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overlay to be currently rendered.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {?React$ComponentType<*>}
|
||||
*/
|
||||
export function getOverlayToRender(state: Object) {
|
||||
for (const overlay of _getOverlays()) {
|
||||
// react-i18n / react-redux wrap components and thus we cannot access
|
||||
// the wrapped component's static methods directly.
|
||||
const component = overlay.WrappedComponent || overlay;
|
||||
|
||||
if (component.needsRender(state)) {
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export * from './actions';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
|
|
@ -3,6 +3,8 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles';
|
||||
|
||||
import { HANGUP_BUTTON_SIZE } from '../../constants';
|
||||
|
||||
// Toolbox, toolbar:
|
||||
|
||||
/**
|
||||
|
@ -44,8 +46,8 @@ const styles = createStyleSheet({
|
|||
...toolbarButton,
|
||||
backgroundColor: ColorPalette.red,
|
||||
borderRadius: 30,
|
||||
height: 60,
|
||||
width: 60
|
||||
height: HANGUP_BUTTON_SIZE,
|
||||
width: HANGUP_BUTTON_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* The size of the hangup button. As that is the largest button, it defines
|
||||
* the size of the {@code ToolBox}, so other components may relate to that.
|
||||
*/
|
||||
export const HANGUP_BUTTON_SIZE = 60;
|
|
@ -1,6 +1,7 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
|
Loading…
Reference in New Issue