feat(overlay): native page reload dialog (#12667)

feat(overlay): native feature removal + replaced PageReloadOverlay with PageReloadDialog
This commit is contained in:
Calinteodor 2023-02-14 11:50:46 +02:00 committed by GitHub
parent 22ded30b61
commit 3cb0df579c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 415 additions and 538 deletions

View File

@ -141,7 +141,7 @@ import {
showNotification,
showWarningNotification
} from './react/features/notifications';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor';
import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
import { isPrejoinPageVisible } from './react/features/prejoin/functions';

View File

@ -30,8 +30,6 @@ import {
// @ts-ignore
import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
// @ts-ignore
import { setFatalError } from '../overlay';
import { addTrackStateToURL, getDefaultURL } from './functions.native';
import logger from './logger';
@ -177,7 +175,6 @@ export function maybeRedirectToWelcomePage(options: any) { // eslint-disable-lin
*/
export function reloadNow() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];

View File

@ -20,7 +20,6 @@ import {
import { isVpaasMeeting } from '../jaas/functions';
import { clearNotifications, showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { setFatalError } from '../overlay/actions';
import { isWelcomePageEnabled } from '../welcome/functions';
import {
@ -222,7 +221,6 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea
*/
export function reloadNow() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];

View File

@ -1,10 +1,7 @@
// @flow
import React, { Fragment } from 'react';
import React from 'react';
import { BaseApp } from '../../base/app';
import { toURLString } from '../../base/util';
import { OverlayContainer } from '../../overlay';
import { appNavigate } from '../actions';
import { getDefaultURL } from '../functions';
@ -73,23 +70,7 @@ export class AbstractApp extends BaseApp<Props, *> {
}
}
/**
* Creates an extra {@link ReactElement}s to be added (unconditionally)
* alongside the main element.
*
* @abstract
* @protected
* @returns {ReactElement}
*/
_createExtraElement() {
return (
<Fragment>
<OverlayContainer />
</Fragment>
);
}
_createMainElement: (React$Element<*>, Object) => ?React$Element<*>;
_createMainElement: (React.ReactElement, Object) => ?React.ReactElement;
/**
* Gets the default URL to be opened when this {@code App} mounts.

View File

@ -1,12 +1,11 @@
// @flow
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import React, { Fragment } from 'react';
import GlobalStyles from '../../base/ui/components/GlobalStyles.web';
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web';
import DialogContainer from '../../base/ui/components/web/DialogContainer';
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
import OverlayContainer from '../../overlay/components/web/OverlayContainer';
import { AbstractApp } from './AbstractApp';
@ -14,12 +13,30 @@ import { AbstractApp } from './AbstractApp';
import '../middlewares';
import '../reducers';
/**
* Root app {@code Component} on Web/React.
*
* @augments AbstractApp
*/
export class App extends AbstractApp {
/**
* Creates an extra {@link ReactElement}s to be added (unconditionally)
* alongside the main element.
*
* @abstract
* @protected
* @returns {ReactElement}
*/
_createExtraElement() {
return (
<Fragment>
<OverlayContainer />
</Fragment>
);
}
/**
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
* the top most component.

View File

@ -1,43 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Container, Text } from '../../react';
import { type StyleType } from '../../styles';
import styles from './styles';
type Props = {
/**
* Children of the component.
*/
children: string | React$Node,
style: ?StyleType
};
/**
* 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, style } = this.props;
const childrenComponent = typeof children === 'string'
? <Text style = { style }>{ children }</Text>
: children;
return (
<Container style = { styles.dialogContainer }>
{ childrenComponent }
</Container>
);
}
}

View File

@ -1,5 +1,3 @@
// @flow
export * from './_';
export { default as DialogContent } from './DialogContent';

View File

@ -0,0 +1,226 @@
/* eslint-disable lines-around-comment */
// @ts-ignore
import { randomInt } from '@jitsi/js-utils/random';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import type { Dispatch } from 'redux';
import { appNavigate, reloadNow } from '../../../../app/actions.native';
import { IReduxState } from '../../../../app/types';
import { translate } from '../../../i18n/functions';
import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.native';
import { connect } from '../../../redux/functions';
// @ts-ignore
import logger from '../../logger';
// @ts-ignore
import ConfirmDialog from './ConfirmDialog';
/**
* The type of the React {@code Component} props of
* {@link PageReloadDialog}.
*/
interface IPageReloadDialogProps extends WithTranslation {
dispatch: Dispatch<any>;
isNetworkFailure: boolean;
reason: string;
}
/**
* The type of the React {@code Component} state of
* {@link PageReloadDialog}.
*/
interface IPageReloadDialogState {
message: string;
timeLeft: number;
timeoutSeconds: number;
title: string;
}
/**
* Implements a React Component that is shown before the
* conference is reloaded.
* Shows a warning message and counts down towards the re-load.
*/
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
// @ts-ignore
_interval: IntervalID;
/**
* Initializes a new PageReloadOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props: IPageReloadDialogProps) {
super(props);
const timeoutSeconds = 10 + randomInt(0, 20);
let message, title;
if (this.props.isNetworkFailure) {
title = 'dialog.conferenceDisconnectTitle';
message = 'dialog.conferenceDisconnectMsg';
} else {
title = 'dialog.conferenceReloadTitle';
message = 'dialog.conferenceReloadMsg';
}
this.state = {
message,
timeLeft: timeoutSeconds,
timeoutSeconds,
title
};
this._onCancel = this._onCancel.bind(this);
this._onReloadNow = this._onReloadNow.bind(this);
}
/**
* React Component method that executes once component is mounted.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const { dispatch } = this.props;
const { timeLeft } = this.state;
logger.info(
`The conference will be reloaded after ${
this.state.timeoutSeconds} seconds.`);
this._interval
= setInterval(
() => {
if (timeLeft === 0) {
if (this._interval) {
clearInterval(this._interval);
this._interval = undefined;
}
dispatch(reloadNow());
} else {
this.setState(prevState => {
return {
timeLeft: prevState.timeLeft - 1
};
});
}
},
1000);
}
/**
* Clears the timer interval.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
if (this._interval) {
clearInterval(this._interval);
this._interval = undefined;
}
}
/**
* Handle clicking of the "Cancel" button. It will navigate back to the
* welcome page.
*
* @private
* @returns {boolean}
*/
_onCancel() {
clearInterval(this._interval);
this.props.dispatch(appNavigate(undefined));
return true;
}
/**
* Handle clicking on the "Reload Now" button. It will navigate to the same
* conference URL as before immediately, without waiting for the timer to
* kick in.
*
* @private
* @returns {boolean}
*/
_onReloadNow() {
clearInterval(this._interval);
this.props.dispatch(reloadNow());
return true;
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
const { message, timeLeft, title } = this.state;
return (
<ConfirmDialog
cancelLabel = 'dialog.Cancel'
confirmLabel = 'dialog.rejoinNow'
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
onCancel = { this._onCancel }
onSubmit = { this._onReloadNow }
title = { title } />
);
}
}
/**
* Maps (parts of) the redux state to the associated component's props.
*
* @param {Object} state - The redux state.
* @protected
* @returns {{
* message: string,
* reason: string,
* title: string
* }}
*/
function mapStateToProps(state: IReduxState) {
const { error: conferenceError } = state['features/base/conference'];
const { error: configError } = state['features/base/config'];
const { error: connectionError } = state['features/base/connection'];
const { fatalError } = state['features/overlay'];
const fatalConnectionError
// @ts-ignore
= connectionError && isFatalJitsiConnectionError(connectionError);
const fatalConfigError = fatalError === configError;
const isNetworkFailure = fatalConfigError || fatalConnectionError;
let reason;
if (conferenceError) {
reason = `error.conference.${conferenceError.name}`;
} else if (connectionError) {
reason = `error.conference.${connectionError.name}`;
} else if (configError) {
reason = `error.config.${configError.name}`;
} else {
logger.error('No reload reason defined!');
}
return {
isNetworkFailure,
reason
};
}
export default translate(connect(mapStateToProps)(PageReloadDialog));

View File

@ -5,6 +5,7 @@ export { default as ConfirmDialog } from './ConfirmDialog';
export { default as DialogContainer } from './DialogContainer';
export { default as AlertDialog } from './AlertDialog';
export { default as InputDialog } from './InputDialog';
export { default as PageReloadDialog } from './PageReloadDialog';
// NOTE: Some dialogs reuse the style of these base classes for consistency
// and as we're in a /native namespace, it's safe to export the styles.

View File

@ -1,14 +0,0 @@
import { BoxModel, createStyleSheet } from '../../styles';
/**
* The React {@code Component} styles of {@code Dialog}.
*/
export default createStyleSheet({
/**
* Unified container for a consistent Dialog style.
*/
dialogContainer: {
paddingHorizontal: BoxModel.padding,
paddingVertical: 1.5 * BoxModel.padding
}
});

View File

@ -1,5 +0,0 @@
/**
* Placeholder styles for web to be able to use cross platform components
* unmodified such as {@code DialogContent}.
*/
export default {};

View File

@ -1,9 +1,12 @@
/* eslint-disable lines-around-comment */
import { IStateful } from '../app/types';
import { toState } from '../redux/functions';
// @ts-ignore
import JitsiMeetJS from './_';
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;

View File

@ -16,6 +16,7 @@ import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { LobbyScreen } from '../../../lobby';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { getOverlayToRender } from '../../../overlay/functions.web';
import { ParticipantsPane } from '../../../participants-pane/components/web';
import Prejoin from '../../../prejoin/components/web/Prejoin';
import { isPrejoinPageVisible } from '../../../prejoin/functions';
@ -33,6 +34,7 @@ import type { AbstractProps } from '../AbstractConference';
import ConferenceInfo from './ConferenceInfo';
import { default as Notice } from './Notice';
declare var APP: Object;
declare var interfaceConfig: Object;
@ -59,6 +61,11 @@ type Props = AbstractProps & {
*/
_backgroundAlpha: number,
/**
* Are any overlays visible?
*/
_isAnyOverlayVisible: boolean,
/**
* The CSS class to apply to the root of {@link Conference} to modify the
* application layout.
@ -198,6 +205,7 @@ class Conference extends AbstractConference<Props, *> {
*/
render() {
const {
_isAnyOverlayVisible,
_layoutClassName,
_notificationsVisible,
_overflowDrawer,
@ -234,7 +242,7 @@ class Conference extends AbstractConference<Props, *> {
{ _showPrejoin || _showLobby || <Toolbox /> }
{_notificationsVisible && (_overflowDrawer
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
? <JitsiPortal className = 'notification-portal'>
{this.renderNotificationsContainer({ portal: true })}
</JitsiPortal>
@ -383,6 +391,7 @@ function _mapStateToProps(state) {
return {
...abstractMapStateToProps(state),
_backgroundAlpha: backgroundAlpha,
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
_overflowDrawer: overflowDrawer,

View File

@ -1,7 +1,7 @@
import { IStateful } from '../base/app/types';
import { toState } from '../base/redux/functions';
import { areThereNotifications } from '../notifications/functions';
import { getOverlayToRender } from '../overlay/functions';
/**
* Tells whether or not the notifications should be displayed within
@ -12,10 +12,7 @@ import { getOverlayToRender } from '../overlay/functions';
*/
export function shouldDisplayNotifications(stateful: IStateful) {
const state = toState(stateful);
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
const { calleeInfoVisible } = state['features/invite'];
return areThereNotifications(state)
&& !isAnyOverlayVisible
&& !calleeInfoVisible;
return areThereNotifications(state) && !calleeInfoVisible;
}

View File

@ -40,7 +40,6 @@ import { getLocalTracks, isLocalTrackMuted, toggleScreensharing } from '../../ba
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat';
import { openChat } from '../../chat/actions';
import { closeChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.any';
import { SET_PAGE_RELOAD_OVERLAY_CANCELED } from '../../overlay/actionTypes';
import { setRequestingSubtitles } from '../../subtitles';
import { muteLocal } from '../../video-menu/actions';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
@ -214,16 +213,6 @@ MiddlewareRegistry.register(store => next => action => {
}
break;
case SET_PAGE_RELOAD_OVERLAY_CANCELED:
sendEvent(
store,
CONFERENCE_TERMINATED,
/* data */ {
error: _toErrorString(action.error),
url: _normalizeUrl(store.getState()['features/base/connection'].locationURL)
});
break;
case SET_VIDEO_MUTED:
sendEvent(
store,

View File

@ -1,14 +1,18 @@
// @flow
/* eslint-disable lines-around-comment */
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SafeAreaView, Text, View } from 'react-native';
// @ts-ignore
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { LoadingIndicator } from '../../../base/react';
// @ts-ignore
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
// @ts-ignore
import { TEXT_COLOR, navigationStyles } from './styles';
const ConnectingPage = () => {
const { t } = useTranslation();

View File

@ -1,16 +1,25 @@
/* eslint-disable lines-around-comment */
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { StatusBar } from 'react-native';
import { connect } from '../../../base/redux';
import { DialInSummary } from '../../../invite';
import { IReduxState } from '../../../app/types';
import { connect } from '../../../base/redux/functions';
// @ts-ignore
import DialInSummary from '../../../invite/components/dial-in-summary/native/DialInSummary';
import Prejoin from '../../../prejoin/components/native/Prejoin';
// @ts-ignore
import WelcomePage from '../../../welcome/components/WelcomePage';
import { isWelcomePageEnabled } from '../../../welcome/functions';
// @ts-ignore
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
// @ts-ignore
import { rootNavigationRef } from '../rootNavigationContainerRef';
// @ts-ignore
import { screen } from '../routes';
// @ts-ignore
import {
conferenceNavigationContainerScreenOptions,
connectingScreenOptions,
@ -18,6 +27,7 @@ import {
navigationContainerTheme,
preJoinScreenOptions,
welcomeScreenOptions
// @ts-ignore
} from '../screenOptions';
import ConnectingPage from './ConnectingPage';
@ -32,13 +42,13 @@ type Props = {
/**
* Redux dispatch function.
*/
dispatch: Function,
dispatch: Function;
/**
* Is welcome page available?
*/
isWelcomePageAvailable: boolean
}
isWelcomePageAvailable: boolean;
};
const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) => {
@ -100,7 +110,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function mapStateToProps(state: Object) {
function mapStateToProps(state: IReduxState) {
return {
isWelcomePageAvailable: isWelcomePageEnabled(state)
};

View File

@ -13,26 +13,3 @@
*/
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
/**
* Adjust the state of the fatal error which shows/hides the reload screen. See
* action methods's description for more info about each of the fields.
*
* {
* type: SET_FATAL_ERROR,
* fatalError: ?Object
* }
* @public
*/
export const SET_FATAL_ERROR = 'SET_FATAL_ERROR';
/**
* The type of the Redux action which signals that the overlay was canceled.
*
* {
* type: export const SET_PAGE_RELOAD_OVERLAY_CANCELED
* }
* @public
*/
export const SET_PAGE_RELOAD_OVERLAY_CANCELED
= 'SET_PAGE_RELOAD_OVERLAY_CANCELED';

View File

@ -0,0 +1,31 @@
/* eslint-disable max-len */
// @ts-ignore
import { PageReloadDialog, openDialog } from '../base/dialog';
/**
* Signals that the prompt for media permission is visible or not.
*
* @param {boolean} _isVisible - If the value is true - the prompt for media
* permission is visible otherwise the value is false/undefined.
* @param {string} _browser - The name of the current browser.
* @public
* @returns {{
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
* browser: {string},
* isVisible: {boolean}
* }}
*/
export function mediaPermissionPromptVisibilityChanged(_isVisible: boolean, _browser: string) {
// Dummy.
}
/**
* Opens {@link PageReloadDialog}.
*
* @returns {Function}
*/
export function openPageReloadDialog() {
return openDialog(PageReloadDialog);
}

View File

@ -1,62 +0,0 @@
import {
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
SET_FATAL_ERROR,
SET_PAGE_RELOAD_OVERLAY_CANCELED
} from './actionTypes';
/**
* Signals that the prompt for media permission is visible or not.
*
* @param {boolean} isVisible - If the value is true - the prompt for media
* permission is visible otherwise the value is false/undefined.
* @param {string} browser - The name of the current browser.
* @public
* @returns {{
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
* browser: {string},
* isVisible: {boolean}
* }}
*/
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
return {
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
browser,
isVisible
};
}
/**
* The action indicates that an unrecoverable error has occurred and the reload
* screen will be displayed or hidden.
*
* @param {Object} fatalError - A critical error which was not claimed by any
* feature for error recovery (the recoverable flag was not set). If
* {@code undefined} then any fatal error currently stored will be discarded.
* @returns {{
* type: SET_FATAL_ERROR,
* fatalError: ?Error
* }}
*/
export function setFatalError(fatalError?: Object) {
return {
type: SET_FATAL_ERROR,
fatalError
};
}
/**
* The action indicates that the overlay was canceled.
*
* @param {Object} error - The error that caused the display of the overlay.
*
* @returns {{
* type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
* error: ?Error
* }}
*/
export function setPageReloadOverlayCanceled(error: Object) {
return {
type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
error
};
}

View File

@ -0,0 +1,32 @@
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
/**
* Signals that the prompt for media permission is visible or not.
*
* @param {boolean} isVisible - If the value is true - the prompt for media
* permission is visible otherwise the value is false/undefined.
* @param {string} browser - The name of the current browser.
* @public
* @returns {{
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
* browser: {string},
* isVisible: {boolean}
* }}
*/
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
return {
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
browser,
isVisible
};
}
/**
* Opens {@link PageReloadDialog}.
*
* @returns {Function}
*/
export function openPageReloadDialog() {
// Dummy
}

View File

@ -1,3 +0,0 @@
// @flow
export * from './native';

View File

@ -1,3 +0,0 @@
// @flow
export * from './web';

View File

@ -1,4 +0,0 @@
// @flow
export { default as OverlayContainer } from './OverlayContainer';
export * from './_';

View File

@ -1,38 +0,0 @@
// @flow
import React, { Component, type Node } from 'react';
import { SafeAreaView, View } from 'react-native';
import styles from './styles';
/**
* The type of the React {@code Component} props of {@code OverlayFrame}.
*/
type Props = {
/**
* The children components to be displayed into the overlay frame.
*/
children: Node,
};
/**
* Implements a React component to act as the frame for overlays.
*/
export default class OverlayFrame extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<View style = { styles.container }>
<SafeAreaView style = { styles.safeContainer } >
{ this.props.children }
</SafeAreaView>
</View>
);
}
}

View File

@ -1,95 +0,0 @@
// @flow
import React from 'react';
import { appNavigate, reloadNow } from '../../../app/actions';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { setFatalError, setPageReloadOverlayCanceled } from '../../actions';
import AbstractPageReloadOverlay, {
type Props,
abstractMapStateToProps
} from '../AbstractPageReloadOverlay';
import OverlayFrame from './OverlayFrame';
/**
* Implements a React Component for page reload overlay. Shown before the
* conference is reloaded. Shows a warning message and counts down towards the
* reload.
*/
class PageReloadOverlay extends AbstractPageReloadOverlay<Props> {
_interval: IntervalID;
/**
* Initializes a new PageReloadOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
this._onCancel = this._onCancel.bind(this);
this._onReloadNow = this._onReloadNow.bind(this);
}
_onCancel: () => void;
/**
* Handle clicking of the "Cancel" button. It will navigate back to the
* welcome page.
*
* @private
* @returns {void}
*/
_onCancel() {
clearInterval(this._interval);
this.props.dispatch(setPageReloadOverlayCanceled(this.props.error));
this.props.dispatch(setFatalError(undefined));
this.props.dispatch(appNavigate(undefined));
}
_onReloadNow: () => void;
/**
* Handle clicking on the "Reload Now" button. It will navigate to the same
* conference URL as before immediately, without waiting for the timer to
* kick in.
*
* @private
* @returns {void}
*/
_onReloadNow() {
clearInterval(this._interval);
this.props.dispatch(reloadNow());
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
const { message, timeLeft, title } = this.state;
return (
<OverlayFrame>
<ConfirmDialog
cancelLabel = 'dialog.Cancel'
confirmLabel = 'dialog.rejoinNow'
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
onCancel = { this._onCancel }
onSubmit = { this._onReloadNow }
title = { title } />
</OverlayFrame>
);
}
}
export default translate(connect(abstractMapStateToProps)(PageReloadOverlay));

View File

@ -1,4 +0,0 @@
// @flow
export { default as OverlayFrame } from './OverlayFrame';
export { default as PageReloadOverlay } from './PageReloadOverlay';

View File

@ -1,24 +0,0 @@
// @flow
import { StyleSheet } from 'react-native';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
/**
* The React {@code Component} styles of the overlay feature.
*/
export default {
/**
* Style for a backdrop overlay covering the screen the the overlay is
* rendered.
*/
container: {
...StyleSheet.absoluteFillObject,
backgroundColor: BaseTheme.palette.ui00
},
safeContainer: {
flex: 1
}
};

View File

@ -7,15 +7,15 @@ import type { Dispatch } from 'redux';
import {
createPageReloadScheduledEvent,
sendAnalytics
} from '../../analytics';
import { reloadNow } from '../../app/actions';
} from '../../../analytics';
import { reloadNow } from '../../../app/actions';
import {
isFatalJitsiConferenceError,
isFatalJitsiConnectionError
} from '../../base/lib-jitsi-meet/functions';
import logger from '../logger';
} from '../../../base/lib-jitsi-meet/functions';
import logger from '../../logger';
import ReloadButton from './web/ReloadButton';
import ReloadButton from './ReloadButton';
declare var APP: Object;
@ -91,6 +91,7 @@ type State = {
*/
export default class AbstractPageReloadOverlay<P: Props>
extends Component<P, State> {
/**
* Determines whether this overlay needs to be rendered (according to a
* specific redux state). Called by {@link OverlayContainer}.
@ -100,34 +101,18 @@ export default class AbstractPageReloadOverlay<P: Props>
* {@code false}, otherwise.
*/
static needsRender(state: Object) {
// FIXME web does not rely on the 'recoverable' flag set on an error
// action, but on a predefined list of fatal errors. Because of that
// the value of 'fatalError' which relies on the flag should not be used
// on web yet (until conference/connection and their errors handling is
// not unified).
return typeof APP === 'undefined'
? Boolean(state['features/overlay'].fatalError)
: this.needsRenderWeb(state);
}
const { error: conferenceError } = state['features/base/conference'];
const { error: configError } = state['features/base/config'];
const { error: connectionError } = state['features/base/connection'];
/**
* Determines whether this overlay needs to be rendered (according to a
* specific redux state). Called by {@link OverlayContainer}.
*
* @param {Object} state - The redux state.
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
* {@code false}, otherwise.
*/
static needsRenderWeb(state: Object) {
const conferenceError = state['features/base/conference'].error;
const configError = state['features/base/config'].error;
const connectionError = state['features/base/connection'].error;
const jitsiConnectionError
return (
(connectionError && isFatalJitsiConnectionError(connectionError))
|| (conferenceError
&& isFatalJitsiConferenceError(conferenceError))
|| configError);
// @ts-ignore
= connectionError && isFatalJitsiConnectionError(connectionError);
const jitsiConferenceError
= conferenceError && isFatalJitsiConferenceError(conferenceError);
return jitsiConnectionError || jitsiConferenceError || configError;
}
_interval: ?IntervalID;

View File

@ -28,6 +28,6 @@ export default class AbstractSuspendedOverlay extends Component<Props> {
* {@code false}, otherwise.
*/
static needsRender(state: Object) {
return state['features/power-monitor'].suspendDetected;
return state['features/power-monitor']?.suspendDetected;
}
}

View File

@ -1,11 +1,9 @@
// @flow
import React, { Component } from 'react';
import { connect } from '../../base/redux';
import { getOverlayToRender } from '../functions';
import { IReduxState } from '../../../app/types';
import { connect } from '../../../base/redux/functions';
import { getOverlayToRender } from '../../functions.web';
declare var interfaceConfig: Object;
/**
* The type of the React {@link Component} props of {@code OverlayContainer}.
@ -16,8 +14,8 @@ type Props = {
* The React {@link Component} type of overlay to be rendered by the
* associated {@code OverlayContainer}.
*/
overlay: ?React$ComponentType<*>
}
overlay: any;
};
/**
* Implements a React {@link Component} that will display the correct overlay
@ -48,7 +46,7 @@ class OverlayContainer extends Component<Props> {
* overlay: ?Object
* }}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
/**
* The React {@link Component} type of overlay to be rendered by the

View File

@ -1,8 +1,4 @@
// @flow
import React, { Component } from 'react';
declare var interfaceConfig: Object;
import React, { Component, ReactChildren } from 'react';
/**
* The type of the React {@code Component} props of {@link OverlayFrame}.
@ -12,18 +8,18 @@ type Props = {
/**
* The children components to be displayed into the overlay frame.
*/
children: React$Node,
children: ReactChildren;
/**
* Indicates the css style of the overlay. If true, then lighter; darker,
* otherwise.
*/
isLightOverlay?: boolean,
isLightOverlay?: boolean;
/**
* The style property.
*/
style: Object
style: Object;
};
/**

View File

@ -2,13 +2,13 @@
import React from 'react';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { translate } from '../../../base/i18n/functions';
import { connect } from '../../../base/redux/functions';
import AbstractPageReloadOverlay, {
type Props,
abstractMapStateToProps
} from '../AbstractPageReloadOverlay';
} from './AbstractPageReloadOverlay';
import OverlayFrame from './OverlayFrame';
/**

View File

@ -1,7 +0,0 @@
// @flow
export { default as OverlayFrame } from './OverlayFrame';
export { default as PageReloadOverlay } from './PageReloadOverlay';
export { default as SuspendedOverlay } from './SuspendedOverlay';
export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';

View File

@ -1,7 +1,13 @@
/* eslint-disable lines-around-comment */
import { IReduxState } from '../app/types';
import { getOverlays } from './overlays';
// @ts-ignore
import PageReloadOverlay from './components/web/PageReloadOverlay';
// @ts-ignore
import SuspendedOverlay from './components/web/SuspendedOverlay';
// @ts-ignore
import UserMediaPermissionsOverlay from './components/web/UserMediaPermissionsOverlay';
/**
* Returns the overlay to be currently rendered.
*
@ -9,7 +15,13 @@ import { getOverlays } from './overlays';
* @returns {?React$ComponentType<*>}
*/
export function getOverlayToRender(state: IReduxState) {
for (const overlay of getOverlays()) {
const overlays = [
PageReloadOverlay,
SuspendedOverlay,
UserMediaPermissionsOverlay
];
for (const overlay of overlays) {
// react-i18n / react-redux wrap components and thus we cannot access
// the wrapped component's static methods directly.
// @ts-ignore
@ -22,13 +34,3 @@ export function getOverlayToRender(state: IReduxState) {
return undefined;
}
/**
* Returns the visibility of the media permissions prompt.
*
* @param {IReduxState} state - The Redux state.
* @returns {boolean}
*/
export function getMediaPermissionPromptVisibility(state: IReduxState) {
return state['features/overlay'].isMediaPermissionPromptVisible;
}

View File

@ -1,5 +0,0 @@
// @flow
export * from './actions';
export * from './components';
export * from './functions';

View File

@ -1,12 +1,15 @@
/* eslint-disable lines-around-comment */
import { IStore } from '../app/types';
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
import {
isFatalJitsiConferenceError,
isFatalJitsiConnectionError
} from '../base/lib-jitsi-meet/functions';
} from '../base/lib-jitsi-meet/functions.any';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { setFatalError } from './actions';
import { openPageReloadDialog } from './actions';
/**
* Error type. Basically like Error, but augmented with a recoverable property.
@ -47,12 +50,11 @@ const ERROR_TYPES = {
/**
* Gets the error type and whether it's fatal or not.
*
* @param {Function} getState - The redux function for fetching the current state.
* @param {Object} state - The redux state.
* @param {Object|string} error - The error to process.
* @returns {void}
*/
const getErrorExtraInfo = (getState: IStore['getState'], error: ErrorType) => {
const state = getState();
const getErrorExtraInfo = (state: any, error: ErrorType) => {
const { error: conferenceError } = state['features/base/conference'];
const { error: configError } = state['features/base/config'];
const { error: connectionError } = state['features/base/connection'];
@ -92,20 +94,26 @@ StateListenerRegistry.register(
return configError || connectionError || conferenceError;
},
/* listener */ (error: ErrorType, { dispatch, getState }) => {
/* listener */ (error: ErrorType, store: IStore) => {
const state = store.getState();
if (!error) {
return;
}
// eslint-disable-next-line no-negated-condition
if (typeof APP !== 'undefined') {
APP.API.notifyError({
...error,
...getErrorExtraInfo(getState, error)
...getErrorExtraInfo(state, error)
});
}
if (NON_OVERLAY_ERRORS.indexOf(error.name) === -1 && typeof error.recoverable === 'undefined') {
dispatch(setFatalError(error));
setTimeout(() => {
// @ts-ignore
store.dispatch(openPageReloadDialog());
}, 500);
}
}
);

View File

@ -1,15 +0,0 @@
import { ReactElement } from 'react';
// @ts-ignore
import { PageReloadOverlay } from './components/native';
/**
* Returns the list of available platform specific overlays.
*
* @returns {Array<ReactElement>}
*/
export function getOverlays(): Array<ReactElement> {
return [
PageReloadOverlay
];
}

View File

@ -1,20 +0,0 @@
import {
PageReloadOverlay,
SuspendedOverlay,
UserMediaPermissionsOverlay
// @ts-ignore
} from './components/web';
/**
* Returns the list of available platform specific overlays.
*
* @returns {Array<Object>}
*/
export function getOverlays(): Array<Object> {
return [
PageReloadOverlay,
SuspendedOverlay,
UserMediaPermissionsOverlay
];
}

View File

@ -1,17 +1,13 @@
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from '../base/config/actionTypes';
import ReducerRegistry from '../base/redux/ReducerRegistry';
import { assign, set } from '../base/redux/functions';
import { assign } from '../base/redux/functions';
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
import {
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
SET_FATAL_ERROR
} from './actionTypes';
export interface IOverlayState {
browser?: string;
fatalError?: Error;
isMediaPermissionPromptVisible?: boolean;
loadConfigOverlayVisible?: boolean;
}
/**
@ -21,18 +17,8 @@ export interface IOverlayState {
*/
ReducerRegistry.register<IOverlayState>('features/overlay', (state = {}, action): IOverlayState => {
switch (action.type) {
case CONFIG_WILL_LOAD:
return _setShowLoadConfigOverlay(state, Boolean(action.room));
case LOAD_CONFIG_ERROR:
case SET_CONFIG:
return _setShowLoadConfigOverlay(state, false);
case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED:
return _mediaPermissionPromptVisibilityChanged(state, action);
case SET_FATAL_ERROR:
return _setFatalError(state, action);
}
return state;
@ -56,29 +42,3 @@ function _mediaPermissionPromptVisibilityChanged(
isMediaPermissionPromptVisible: isVisible
});
}
/**
* Sets the {@code LoadConfigOverlay} overlay visible or not.
*
* @param {Object} state - The redux state of the feature overlay.
* @param {boolean} show - Whether to show or not the overlay.
* @returns {Object} The new state of the feature overlay after the reduction of
* the specified action.
*/
function _setShowLoadConfigOverlay(state: IOverlayState, show?: boolean) {
return set(state, 'loadConfigOverlayVisible', show);
}
/**
* Reduces a specific redux action {@code SET_FATAL_ERROR} of the feature
* overlay.
*
* @param {Object} state - The redux state of the feature overlay.
* @param {Error} fatalError - If the value is set it indicates that a fatal
* error has occurred and that the reload screen is to be displayed.
* @returns {Object}
* @private
*/
function _setFatalError(state: IOverlayState, { fatalError }: { fatalError?: Error; }) {
return set(state, 'fatalError', fatalError);
}