diff --git a/conference.js b/conference.js index dc71e9e93..28f5930b9 100644 --- a/conference.js +++ b/conference.js @@ -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'; diff --git a/react/features/app/actions.native.ts b/react/features/app/actions.native.ts index 71cc18c6f..c5fc3eb50 100644 --- a/react/features/app/actions.native.ts +++ b/react/features/app/actions.native.ts @@ -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']; diff --git a/react/features/app/actions.web.ts b/react/features/app/actions.web.ts index 474225826..b5859fd86 100644 --- a/react/features/app/actions.web.ts +++ b/react/features/app/actions.web.ts @@ -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']; diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index c179fa94d..eced400df 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -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 { } } - /** - * Creates an extra {@link ReactElement}s to be added (unconditionally) - * alongside the main element. - * - * @abstract - * @protected - * @returns {ReactElement} - */ - _createExtraElement() { - return ( - - - - ); - } - - _createMainElement: (React$Element<*>, Object) => ?React$Element<*>; + _createMainElement: (React.ReactElement, Object) => ?React.ReactElement; /** * Gets the default URL to be opened when this {@code App} mounts. diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index afe3fa496..347ae7d91 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -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 ( + + + + ); + } + /** * Overrides the parent method to inject {@link AtlasKitThemeProvider} as * the top most component. diff --git a/react/features/base/dialog/components/DialogContent.js b/react/features/base/dialog/components/DialogContent.js deleted file mode 100644 index 1de43cbe3..000000000 --- a/react/features/base/dialog/components/DialogContent.js +++ /dev/null @@ -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 { - /** - * Implements {@code Component#render}. - * - * @inheritdoc - */ - render() { - const { children, style } = this.props; - - const childrenComponent = typeof children === 'string' - ? { children } - : children; - - return ( - - { childrenComponent } - - ); - } -} diff --git a/react/features/base/dialog/components/index.js b/react/features/base/dialog/components/index.js index 2b1cf4e12..bf0c7deb0 100644 --- a/react/features/base/dialog/components/index.js +++ b/react/features/base/dialog/components/index.js @@ -1,5 +1,3 @@ // @flow export * from './_'; - -export { default as DialogContent } from './DialogContent'; diff --git a/react/features/base/dialog/components/native/PageReloadDialog.tsx b/react/features/base/dialog/components/native/PageReloadDialog.tsx new file mode 100644 index 000000000..09bf2b96c --- /dev/null +++ b/react/features/base/dialog/components/native/PageReloadDialog.tsx @@ -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; + 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 { + + // @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 ( + + ); + } +} + +/** + * 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)); diff --git a/react/features/base/dialog/components/native/index.js b/react/features/base/dialog/components/native/index.js index deb30b3d8..074e2cf15 100644 --- a/react/features/base/dialog/components/native/index.js +++ b/react/features/base/dialog/components/native/index.js @@ -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. diff --git a/react/features/base/dialog/components/styles.native.js b/react/features/base/dialog/components/styles.native.js deleted file mode 100644 index a29bf1275..000000000 --- a/react/features/base/dialog/components/styles.native.js +++ /dev/null @@ -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 - } -}); diff --git a/react/features/base/dialog/components/styles.web.js b/react/features/base/dialog/components/styles.web.js deleted file mode 100644 index e20a0d953..000000000 --- a/react/features/base/dialog/components/styles.web.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Placeholder styles for web to be able to use cross platform components - * unmodified such as {@code DialogContent}. - */ -export default {}; diff --git a/react/features/base/lib-jitsi-meet/functions.any.ts b/react/features/base/lib-jitsi-meet/functions.any.ts index cfad3c665..4f64cf101 100644 --- a/react/features/base/lib-jitsi-meet/functions.any.ts +++ b/react/features/base/lib-jitsi-meet/functions.any.ts @@ -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; diff --git a/react/features/conference/components/web/Conference.js b/react/features/conference/components/web/Conference.js index 8e112aee8..43df4397f 100644 --- a/react/features/conference/components/web/Conference.js +++ b/react/features/conference/components/web/Conference.js @@ -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 { */ render() { const { + _isAnyOverlayVisible, _layoutClassName, _notificationsVisible, _overflowDrawer, @@ -234,7 +242,7 @@ class Conference extends AbstractConference { { _showPrejoin || _showLobby || } - {_notificationsVisible && (_overflowDrawer + {_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer ? {this.renderNotificationsContainer({ portal: true })} @@ -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, diff --git a/react/features/conference/functions.any.ts b/react/features/conference/functions.any.ts index f310cc633..5d8c58ffe 100644 --- a/react/features/conference/functions.any.ts +++ b/react/features/conference/functions.any.ts @@ -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; } diff --git a/react/features/mobile/external-api/middleware.js b/react/features/mobile/external-api/middleware.js index 2b4c15882..83926aede 100644 --- a/react/features/mobile/external-api/middleware.js +++ b/react/features/mobile/external-api/middleware.js @@ -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, diff --git a/react/features/mobile/navigation/components/ConnectingPage.js b/react/features/mobile/navigation/components/ConnectingPage.tsx similarity index 85% rename from react/features/mobile/navigation/components/ConnectingPage.js rename to react/features/mobile/navigation/components/ConnectingPage.tsx index 1b4059bd8..c3d6b549c 100644 --- a/react/features/mobile/navigation/components/ConnectingPage.js +++ b/react/features/mobile/navigation/components/ConnectingPage.tsx @@ -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(); diff --git a/react/features/mobile/navigation/components/RootNavigationContainer.js b/react/features/mobile/navigation/components/RootNavigationContainer.tsx similarity index 88% rename from react/features/mobile/navigation/components/RootNavigationContainer.js rename to react/features/mobile/navigation/components/RootNavigationContainer.tsx index 54001c8bd..2f1f72afe 100644 --- a/react/features/mobile/navigation/components/RootNavigationContainer.js +++ b/react/features/mobile/navigation/components/RootNavigationContainer.tsx @@ -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) }; diff --git a/react/features/overlay/actionTypes.ts b/react/features/overlay/actionTypes.ts index 6211b9fd3..b993b18b9 100644 --- a/react/features/overlay/actionTypes.ts +++ b/react/features/overlay/actionTypes.ts @@ -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'; diff --git a/react/features/overlay/actions.native.ts b/react/features/overlay/actions.native.ts new file mode 100644 index 000000000..b33a00583 --- /dev/null +++ b/react/features/overlay/actions.native.ts @@ -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); +} diff --git a/react/features/overlay/actions.ts b/react/features/overlay/actions.ts deleted file mode 100644 index 912275476..000000000 --- a/react/features/overlay/actions.ts +++ /dev/null @@ -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 - }; -} diff --git a/react/features/overlay/actions.web.ts b/react/features/overlay/actions.web.ts new file mode 100644 index 000000000..bc215c1b3 --- /dev/null +++ b/react/features/overlay/actions.web.ts @@ -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 +} diff --git a/react/features/overlay/components/_.native.js b/react/features/overlay/components/_.native.js deleted file mode 100644 index a32ec6061..000000000 --- a/react/features/overlay/components/_.native.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow - -export * from './native'; diff --git a/react/features/overlay/components/_.web.js b/react/features/overlay/components/_.web.js deleted file mode 100644 index 40d5f4652..000000000 --- a/react/features/overlay/components/_.web.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow - -export * from './web'; diff --git a/react/features/overlay/components/index.js b/react/features/overlay/components/index.js deleted file mode 100644 index 32ec6de9e..000000000 --- a/react/features/overlay/components/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// @flow - -export { default as OverlayContainer } from './OverlayContainer'; -export * from './_'; diff --git a/react/features/overlay/components/native/OverlayFrame.js b/react/features/overlay/components/native/OverlayFrame.js deleted file mode 100644 index f1c2e8c86..000000000 --- a/react/features/overlay/components/native/OverlayFrame.js +++ /dev/null @@ -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 { - /** - * Implements React's {@link Component#render()}. - * - * @inheritdoc - * @returns {ReactElement} - */ - render() { - return ( - - - { this.props.children } - - - ); - } -} diff --git a/react/features/overlay/components/native/PageReloadOverlay.js b/react/features/overlay/components/native/PageReloadOverlay.js deleted file mode 100644 index 657c33b86..000000000 --- a/react/features/overlay/components/native/PageReloadOverlay.js +++ /dev/null @@ -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 { - _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 ( - - - - ); - } -} - -export default translate(connect(abstractMapStateToProps)(PageReloadOverlay)); diff --git a/react/features/overlay/components/native/index.js b/react/features/overlay/components/native/index.js deleted file mode 100644 index 82f341f3d..000000000 --- a/react/features/overlay/components/native/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// @flow - -export { default as OverlayFrame } from './OverlayFrame'; -export { default as PageReloadOverlay } from './PageReloadOverlay'; diff --git a/react/features/overlay/components/native/styles.js b/react/features/overlay/components/native/styles.js deleted file mode 100644 index 9b554c891..000000000 --- a/react/features/overlay/components/native/styles.js +++ /dev/null @@ -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 - } -}; diff --git a/react/features/overlay/components/AbstractPageReloadOverlay.js b/react/features/overlay/components/web/AbstractPageReloadOverlay.js similarity index 83% rename from react/features/overlay/components/AbstractPageReloadOverlay.js rename to react/features/overlay/components/web/AbstractPageReloadOverlay.js index 55084fb86..e277b7bf0 100644 --- a/react/features/overlay/components/AbstractPageReloadOverlay.js +++ b/react/features/overlay/components/web/AbstractPageReloadOverlay.js @@ -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 extends Component { + /** * 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 * {@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; diff --git a/react/features/overlay/components/web/AbstractSuspendedOverlay.js b/react/features/overlay/components/web/AbstractSuspendedOverlay.js index 63d56df3d..448375f72 100644 --- a/react/features/overlay/components/web/AbstractSuspendedOverlay.js +++ b/react/features/overlay/components/web/AbstractSuspendedOverlay.js @@ -28,6 +28,6 @@ export default class AbstractSuspendedOverlay extends Component { * {@code false}, otherwise. */ static needsRender(state: Object) { - return state['features/power-monitor'].suspendDetected; + return state['features/power-monitor']?.suspendDetected; } } diff --git a/react/features/overlay/components/OverlayContainer.js b/react/features/overlay/components/web/OverlayContainer.tsx similarity index 83% rename from react/features/overlay/components/OverlayContainer.js rename to react/features/overlay/components/web/OverlayContainer.tsx index 602c7f1eb..254b9c440 100644 --- a/react/features/overlay/components/OverlayContainer.js +++ b/react/features/overlay/components/web/OverlayContainer.tsx @@ -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 { * overlay: ?Object * }} */ -function _mapStateToProps(state) { +function _mapStateToProps(state: IReduxState) { return { /** * The React {@link Component} type of overlay to be rendered by the diff --git a/react/features/overlay/components/web/OverlayFrame.js b/react/features/overlay/components/web/OverlayFrame.tsx similarity index 86% rename from react/features/overlay/components/web/OverlayFrame.js rename to react/features/overlay/components/web/OverlayFrame.tsx index d40a3bc37..7f4ccc9ad 100644 --- a/react/features/overlay/components/web/OverlayFrame.js +++ b/react/features/overlay/components/web/OverlayFrame.tsx @@ -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; }; /** diff --git a/react/features/overlay/components/web/PageReloadOverlay.js b/react/features/overlay/components/web/PageReloadOverlay.js index 03424966c..369026b22 100644 --- a/react/features/overlay/components/web/PageReloadOverlay.js +++ b/react/features/overlay/components/web/PageReloadOverlay.js @@ -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'; /** diff --git a/react/features/overlay/components/web/index.js b/react/features/overlay/components/web/index.js deleted file mode 100644 index f29b68547..000000000 --- a/react/features/overlay/components/web/index.js +++ /dev/null @@ -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'; diff --git a/react/features/overlay/functions.ts b/react/features/overlay/functions.web.ts similarity index 55% rename from react/features/overlay/functions.ts rename to react/features/overlay/functions.web.ts index 21a59f1d8..63a95102e 100644 --- a/react/features/overlay/functions.ts +++ b/react/features/overlay/functions.web.ts @@ -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; -} diff --git a/react/features/overlay/index.js b/react/features/overlay/index.js deleted file mode 100644 index c13d3e017..000000000 --- a/react/features/overlay/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// @flow - -export * from './actions'; -export * from './components'; -export * from './functions'; diff --git a/react/features/overlay/middleware.ts b/react/features/overlay/middleware.ts index 40bf323ae..98a63cd3a 100644 --- a/react/features/overlay/middleware.ts +++ b/react/features/overlay/middleware.ts @@ -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); } } ); diff --git a/react/features/overlay/overlays.native.ts b/react/features/overlay/overlays.native.ts deleted file mode 100644 index 8bd8e60fa..000000000 --- a/react/features/overlay/overlays.native.ts +++ /dev/null @@ -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} - */ -export function getOverlays(): Array { - return [ - PageReloadOverlay - ]; -} diff --git a/react/features/overlay/overlays.web.ts b/react/features/overlay/overlays.web.ts deleted file mode 100644 index 244d423a8..000000000 --- a/react/features/overlay/overlays.web.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - PageReloadOverlay, - SuspendedOverlay, - UserMediaPermissionsOverlay - - // @ts-ignore -} from './components/web'; - -/** - * Returns the list of available platform specific overlays. - * - * @returns {Array} - */ -export function getOverlays(): Array { - return [ - PageReloadOverlay, - SuspendedOverlay, - UserMediaPermissionsOverlay - ]; -} diff --git a/react/features/overlay/reducer.ts b/react/features/overlay/reducer.ts index 3af45bf28..affc1aa1f 100644 --- a/react/features/overlay/reducer.ts +++ b/react/features/overlay/reducer.ts @@ -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('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); -}