import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { throttle } from 'lodash'; import { defineMessages, injectIntl } from 'react-intl'; import Modal from 'react-modal'; import browser from 'browser-detect'; import PanelManager from '/imports/ui/components/panel-manager/component'; import PollingContainer from '/imports/ui/components/polling/container'; import logger from '/imports/startup/client/logger'; import ActivityCheckContainer from '/imports/ui/components/activity-check/container'; import UserInfoContainer from '/imports/ui/components/user-info/container'; import BreakoutRoomInvitation from '/imports/ui/components/breakout-room/invitation/container'; import ToastContainer from '../toast/container'; import ModalContainer from '../modal/container'; import NotificationsBarContainer from '../notifications-bar/container'; import AudioContainer from '../audio/container'; import ChatAlertContainer from '../chat/alert/container'; import BannerBarContainer from '/imports/ui/components/banner-bar/container'; import WaitingNotifierContainer from '/imports/ui/components/waiting-users/alert/container'; import LockNotifier from '/imports/ui/components/lock-viewers/notify/container'; import StatusNotifier from '/imports/ui/components/status-notifier/container'; import MediaService from '/imports/ui/components/media/service'; import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-users-notify/container'; import UploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container'; import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container'; import { withDraggableContext } from '../media/webcam-draggable-overlay/context'; import { styles } from './styles'; import { makeCall } from '/imports/ui/services/api'; import { NAVBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; const APP_CONFIG = Meteor.settings.public.app; const DESKTOP_FONT_SIZE = APP_CONFIG.desktopFontSize; const MOBILE_FONT_SIZE = APP_CONFIG.mobileFontSize; const ENABLE_NETWORK_MONITORING = Meteor.settings.public.networkMonitoring.enableNetworkMonitoring; const intlMessages = defineMessages({ userListLabel: { id: 'app.userList.label', description: 'Aria-label for Userlist Nav', }, chatLabel: { id: 'app.chat.label', description: 'Aria-label for Chat Section', }, mediaLabel: { id: 'app.media.label', description: 'Aria-label for Media Section', }, actionsBarLabel: { id: 'app.actionsBar.label', description: 'Aria-label for ActionsBar Section', }, iOSWarning: { id: 'app.iOSWarning.label', description: 'message indicating to upgrade ios version', }, clearedEmoji: { id: 'app.toast.clearedEmoji.label', description: 'message for cleared emoji status', }, setEmoji: { id: 'app.toast.setEmoji.label', description: 'message when a user emoji has been set', }, raisedHand: { id: 'app.toast.setEmoji.raiseHand', description: 'toast message for raised hand notification', }, loweredHand: { id: 'app.toast.setEmoji.lowerHand', description: 'toast message for lowered hand notification', }, meetingMuteOn: { id: 'app.toast.meetingMuteOn.label', description: 'message used when meeting has been muted', }, meetingMuteOff: { id: 'app.toast.meetingMuteOff.label', description: 'message used when meeting has been unmuted', }, pollPublishedLabel: { id: 'app.whiteboard.annotations.poll', description: 'message displayed when a poll is published', }, }); const propTypes = { navbar: PropTypes.element, sidebar: PropTypes.element, media: PropTypes.element, actionsbar: PropTypes.element, captions: PropTypes.element, locale: PropTypes.string, intl: PropTypes.object.isRequired, }; const defaultProps = { navbar: null, sidebar: null, media: null, actionsbar: null, captions: null, locale: 'en', }; const LAYERED_BREAKPOINT = 640; const isLayeredView = window.matchMedia(`(max-width: ${LAYERED_BREAKPOINT}px)`); class App extends Component { constructor() { super(); this.state = { enableResize: !window.matchMedia(MOBILE_MEDIA).matches, }; this.handleWindowResize = throttle(this.handleWindowResize).bind(this); this.shouldAriaHide = this.shouldAriaHide.bind(this); this.renderMedia = withDraggableContext(this.renderMedia.bind(this)); } componentDidMount() { const { locale, notify, intl, validIOSVersion, startBandwidthMonitoring, handleNetworkConnection, } = this.props; const BROWSER_RESULTS = browser(); const isMobileBrowser = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android'); MediaService.setSwapLayout(); Modal.setAppElement('#app'); document.getElementsByTagName('html')[0].lang = locale; document.getElementsByTagName('html')[0].style.fontSize = isMobileBrowser ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE; const body = document.getElementsByTagName('body')[0]; if (BROWSER_RESULTS && BROWSER_RESULTS.name) { body.classList.add(`browser-${BROWSER_RESULTS.name}`); } if (BROWSER_RESULTS && BROWSER_RESULTS.os) { body.classList.add(`os-${BROWSER_RESULTS.os.split(' ').shift().toLowerCase()}`); } if (!validIOSVersion()) { notify( intl.formatMessage(intlMessages.iOSWarning), 'error', 'warning', ); } this.handleWindowResize(); window.addEventListener('resize', this.handleWindowResize, false); window.ondragover = function (e) { e.preventDefault(); }; window.ondrop = function (e) { e.preventDefault(); }; if (ENABLE_NETWORK_MONITORING) { if (navigator.connection) { handleNetworkConnection(); navigator.connection.addEventListener('change', handleNetworkConnection); } startBandwidthMonitoring(); } if (isMobileBrowser) makeCall('setMobileUser'); logger.info({ logCode: 'app_component_componentdidmount' }, 'Client loaded successfully'); } componentDidUpdate(prevProps) { const { meetingMuted, notify, currentUserEmoji, intl, hasPublishedPoll, randomlySelectedUser, currentUserId, mountModal, } = this.props; if (randomlySelectedUser === currentUserId) mountModal(); if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) { const formattedEmojiStatus = intl.formatMessage({ id: `app.actionsBar.emojiMenu.${currentUserEmoji.status}Label` }) || currentUserEmoji.status; const raisedHand = currentUserEmoji.status === 'raiseHand'; let statusLabel = ''; if (currentUserEmoji.status === 'none') { statusLabel = prevProps.currentUserEmoji.status === 'raiseHand' ? intl.formatMessage(intlMessages.loweredHand) : intl.formatMessage(intlMessages.clearedEmoji); } else { statusLabel = raisedHand ? intl.formatMessage(intlMessages.raisedHand) : intl.formatMessage(intlMessages.setEmoji, ({ 0: formattedEmojiStatus })); } notify( statusLabel, 'info', currentUserEmoji.status === 'none' ? 'clear_status' : 'user', ); } if (!prevProps.meetingMuted && meetingMuted) { notify( intl.formatMessage(intlMessages.meetingMuteOn), 'info', 'mute', ); } if (prevProps.meetingMuted && !meetingMuted) { notify( intl.formatMessage(intlMessages.meetingMuteOff), 'info', 'unmute', ); } if (!prevProps.hasPublishedPoll && hasPublishedPoll) { notify( intl.formatMessage(intlMessages.pollPublishedLabel), 'info', 'polling', ); } } componentWillUnmount() { const { handleNetworkConnection } = this.props; window.removeEventListener('resize', this.handleWindowResize, false); if (navigator.connection) { navigator.connection.addEventListener('change', handleNetworkConnection, false); } } handleWindowResize() { const { enableResize } = this.state; const shouldEnableResize = !window.matchMedia(MOBILE_MEDIA).matches; if (enableResize === shouldEnableResize) return; this.setState({ enableResize: shouldEnableResize }); } shouldAriaHide() { const { openPanel, isPhone } = this.props; return openPanel !== '' && (isPhone || isLayeredView.matches); } renderPanel() { const { enableResize } = this.state; const { openPanel, isRTL } = this.props; return ( ); } renderNavBar() { const { navbar } = this.props; if (!navbar) return null; return (
{navbar}
); } renderSidebar() { const { sidebar } = this.props; if (!sidebar) return null; return ( ); } renderCaptions() { const { captions } = this.props; if (!captions) return null; return (
{captions}
); } renderMedia() { const { media, intl, } = this.props; if (!media) return null; return (
{media} {this.renderCaptions()}
); } renderActionsBar() { const { actionsbar, intl, } = this.props; if (!actionsbar) return null; return (
{actionsbar}
); } renderActivityCheck() { const { User } = this.props; const { inactivityCheck, responseDelay } = User; return (inactivityCheck ? ( ) : null); } renderUserInformation() { const { UserInfo, User } = this.props; return (UserInfo.length > 0 ? ( ) : null); } render() { const { customStyle, customStyleUrl, openPanel, layoutContextState, } = this.props; return (
{this.renderActivityCheck()} {this.renderUserInformation()}
{this.renderNavBar()} {this.renderMedia()} {this.renderActionsBar()}
{this.renderPanel()} {this.renderSidebar()}
{!layoutContextState.presentationIsFullscreen && !layoutContextState.screenShareIsFullscreen && } {customStyleUrl ? : null} {customStyle ? : null}
); } } App.propTypes = propTypes; App.defaultProps = defaultProps; export default injectIntl(App);