// @flow import _ from 'lodash'; import React from 'react'; import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout'; import { getConferenceNameForTitle } from '../../../base/conference'; import { connect, disconnect } from '../../../base/connection'; import { isMobileBrowser } from '../../../base/environment/utils'; import { translate } from '../../../base/i18n'; import { connect as reactReduxConnect } from '../../../base/redux'; import { setColorAlpha } from '../../../base/util'; import { Chat } from '../../../chat'; import { Filmstrip } from '../../../filmstrip'; import { CalleeInfoContainer } from '../../../invite'; import { LargeVideo } from '../../../large-video'; import { KnockingParticipantList, LobbyScreen } from '../../../lobby'; import { getIsLobbyVisible } from '../../../lobby/functions'; import { ParticipantsPane } from '../../../participants-pane/components/web'; import { getParticipantsPaneOpen } from '../../../participants-pane/functions'; import { Prejoin, isPrejoinPageVisible } from '../../../prejoin'; import { toggleToolboxVisible } from '../../../toolbox/actions.any'; import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web'; import { JitsiPortal, Toolbox } from '../../../toolbox/components/web'; import { LAYOUTS, getCurrentLayout } from '../../../video-layout'; import { maybeShowSuboptimalExperienceNotification } from '../../functions'; import { AbstractConference, abstractMapStateToProps } from '../AbstractConference'; import type { AbstractProps } from '../AbstractConference'; import ConferenceInfo from './ConferenceInfo'; import { default as Notice } from './Notice'; declare var APP: Object; declare var interfaceConfig: Object; /** * DOM events for when full screen mode has changed. Different browsers need * different vendor prefixes. * * @private * @type {Array} */ const FULL_SCREEN_EVENTS = [ 'webkitfullscreenchange', 'mozfullscreenchange', 'fullscreenchange' ]; /** * The CSS class to apply to the root element of the conference so CSS can * modify the app layout. * * @private * @type {Object} */ const LAYOUT_CLASSNAMES = { [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip', [LAYOUTS.TILE_VIEW]: 'tile-view', [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip' }; /** * The type of the React {@code Component} props of {@link Conference}. */ type Props = AbstractProps & { /** * The alpha(opacity) of the background. */ _backgroundAlpha: number, /** * If participants pane is visible or not. */ _isParticipantsPaneVisible: boolean, /** * The CSS class to apply to the root of {@link Conference} to modify the * application layout. */ _layoutClassName: string, /** * The config specified interval for triggering mouseMoved iframe api events. */ _mouseMoveCallbackInterval: number, /** *Whether or not the notifications should be displayed in the overflow drawer. */ _overflowDrawer: boolean, /** * Name for this conference room. */ _roomName: string, /** * If lobby page is visible or not. */ _showLobby: boolean, /** * If prejoin page is visible or not. */ _showPrejoin: boolean, dispatch: Function, t: Function } /** * The conference page of the Web application. */ class Conference extends AbstractConference { _onFullScreenChange: Function; _onMouseEnter: Function; _onMouseLeave: Function; _onMouseMove: Function; _onShowToolbar: Function; _onVidespaceTouchStart: Function; _originalOnMouseMove: Function; _originalOnShowToolbar: Function; _setBackground: Function; /** * Initializes a new Conference instance. * * @param {Object} props - The read-only properties with which the new * instance is to be initialized. */ constructor(props) { super(props); const { _mouseMoveCallbackInterval } = props; // Throttle and bind this component's mousemove handler to prevent it // from firing too often. this._originalOnShowToolbar = this._onShowToolbar; this._originalOnMouseMove = this._onMouseMove; this._onShowToolbar = _.throttle( () => this._originalOnShowToolbar(), 100, { leading: true, trailing: false }); this._onMouseMove = _.throttle( event => this._originalOnMouseMove(event), _mouseMoveCallbackInterval, { leading: true, trailing: false }); // Bind event handler so it is only bound once for every instance. this._onFullScreenChange = this._onFullScreenChange.bind(this); this._onVidespaceTouchStart = this._onVidespaceTouchStart.bind(this); this._setBackground = this._setBackground.bind(this); } /** * Start the connection and get the UI ready for the conference. * * @inheritdoc */ componentDidMount() { document.title = `${this.props._roomName} | ${interfaceConfig.APP_NAME}`; this._start(); } /** * Calls into legacy UI to update the application layout, if necessary. * * @inheritdoc * returns {void} */ componentDidUpdate(prevProps) { if (this.props._shouldDisplayTileView === prevProps._shouldDisplayTileView) { return; } // TODO: For now VideoLayout is being called as LargeVideo and Filmstrip // sizing logic is still handled outside of React. Once all components // are in react they should calculate size on their own as much as // possible and pass down sizings. VideoLayout.refreshLayout(); } /** * Disconnect from the conference when component will be * unmounted. * * @inheritdoc */ componentWillUnmount() { APP.UI.unbindEvents(); FULL_SCREEN_EVENTS.forEach(name => document.removeEventListener(name, this._onFullScreenChange)); APP.conference.isJoined() && this.props.dispatch(disconnect()); } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { _isParticipantsPaneVisible, _layoutClassName, _notificationsVisible, _overflowDrawer, _showLobby, _showPrejoin } = this.props; return (
{!_isParticipantsPaneVisible &&
}
{ _showPrejoin || _showLobby || } {_notificationsVisible && (_overflowDrawer ? {this.renderNotificationsContainer({ portal: true })} : this.renderNotificationsContainer()) } { _showPrejoin && } { _showLobby && }
); } /** * Sets custom background opacity based on config. It also applies the * opacity on parent element, as the parent element is not accessible directly, * only though it's child. * * @param {Object} element - The DOM element for which to apply opacity. * * @private * @returns {void} */ _setBackground(element) { if (!element) { return; } if (this.props._backgroundAlpha !== undefined) { const elemColor = element.style.background; const alphaElemColor = setColorAlpha(elemColor, this.props._backgroundAlpha); element.style.background = alphaElemColor; if (element.parentElement) { const parentColor = element.parentElement.style.background; const alphaParentColor = setColorAlpha(parentColor, this.props._backgroundAlpha); element.parentElement.style.background = alphaParentColor; } } } /** * Handler used for touch start on Video container. * * @private * @returns {void} */ _onVidespaceTouchStart() { this.props.dispatch(toggleToolboxVisible()); } /** * Updates the Redux state when full screen mode has been enabled or * disabled. * * @private * @returns {void} */ _onFullScreenChange() { this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen())); } /** * Triggers iframe API mouseEnter event. * * @param {MouseEvent} event - The mouse event. * @private * @returns {void} */ _onMouseEnter(event) { APP.API.notifyMouseEnter(event); } /** * Triggers iframe API mouseLeave event. * * @param {MouseEvent} event - The mouse event. * @private * @returns {void} */ _onMouseLeave(event) { APP.API.notifyMouseLeave(event); } /** * Triggers iframe API mouseMove event. * * @param {MouseEvent} event - The mouse event. * @private * @returns {void} */ _onMouseMove(event) { APP.API.notifyMouseMove(event); } /** * Displays the toolbar. * * @private * @returns {void} */ _onShowToolbar() { this.props.dispatch(showToolbox()); } /** * Until we don't rewrite UI using react components * we use UI.start from old app. Also method translates * component right after it has been mounted. * * @inheritdoc */ _start() { APP.UI.start(); APP.UI.registerListeners(); APP.UI.bindEvents(); FULL_SCREEN_EVENTS.forEach(name => document.addEventListener(name, this._onFullScreenChange)); const { dispatch, t } = this.props; dispatch(connect()); maybeShowSuboptimalExperienceNotification(dispatch, t); } } /** * Maps (parts of) the Redux state to the associated props for the * {@code Conference} component. * * @param {Object} state - The Redux state. * @private * @returns {Props} */ function _mapStateToProps(state) { const { backgroundAlpha, mouseMoveCallbackInterval } = state['features/base/config']; const { overflowDrawer } = state['features/toolbox']; return { ...abstractMapStateToProps(state), _backgroundAlpha: backgroundAlpha, _isParticipantsPaneVisible: getParticipantsPaneOpen(state), _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)], _mouseMoveCallbackInterval: mouseMoveCallbackInterval, _overflowDrawer: overflowDrawer, _roomName: getConferenceNameForTitle(state), _showLobby: getIsLobbyVisible(state), _showPrejoin: isPrejoinPageVisible(state) }; } export default reactReduxConnect(_mapStateToProps)(translate(Conference));