diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 2238d77940..2d52b94a0b 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -11,12 +11,12 @@ import AudioManager from '/imports/ui/services/audio-manager'; import logger from '/imports/startup/client/logger'; import Users from '/imports/api/users'; import { Session } from 'meteor/session'; +import { FormattedMessage } from 'react-intl'; import IntlStartup from './intl'; import Meetings, { RecordMeetings } from '../../api/meetings'; import AppService from '/imports/ui/components/app/service'; import Breakouts from '/imports/api/breakouts'; import AudioService from '/imports/ui/components/audio/service'; -import { FormattedMessage } from 'react-intl'; import { notify } from '/imports/ui/services/notification'; const BREAKOUT_END_NOTIFY_DELAY = 50; diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 0e801cf28c..b6775edcc5 100755 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -21,6 +21,7 @@ import LockNotifier from '/imports/ui/components/lock-viewers/notify/container'; import PingPongContainer from '/imports/ui/components/ping-pong/container'; import MediaService from '/imports/ui/components/media/service'; import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-users-notify/container'; +import { withDraggableContext } from '../media/webcam-draggable-overlay/context'; import { styles } from './styles'; const MOBILE_MEDIA = 'only screen and (max-width: 40em)'; @@ -103,6 +104,7 @@ class App extends Component { this.handleWindowResize = throttle(this.handleWindowResize).bind(this); this.shouldAriaHide = this.shouldAriaHide.bind(this); + this.renderMedia = withDraggableContext(this.renderMedia.bind(this)); } componentDidMount() { diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx index 82118c3b01..edb1b405e9 100644 --- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx @@ -1,16 +1,15 @@ -import React, { Component, Fragment } from 'react'; +import React, { PureComponent, Fragment } from 'react'; import Draggable from 'react-draggable'; import cx from 'classnames'; import _ from 'lodash'; import PropTypes from 'prop-types'; import Resizable from 're-resizable'; import { isMobile, isIPad13 } from 'react-device-detect'; -import { withDraggableContext } from './context'; +import { withDraggableConsumer } from './context'; import VideoProviderContainer from '/imports/ui/components/video-provider/container'; import { styles } from '../styles.scss'; import Storage from '../../../services/storage/session'; -const { webcamsDefaultPlacement } = Meteor.settings.public.layout; const BROWSER_ISMOBILE = isMobile || isIPad13; const propTypes = { @@ -32,7 +31,7 @@ const defaultProps = { }; const dispatchResizeEvent = () => window.dispatchEvent(new Event('resize')); -class WebcamDraggable extends Component { +class WebcamDraggable extends PureComponent { constructor(props) { super(props); @@ -58,17 +57,34 @@ class WebcamDraggable extends Component { } componentDidUpdate(prevProps) { - const { swapLayout, webcamDraggableState } = this.props; - const { placement } = webcamDraggableState; + const { swapLayout, webcamDraggableState, webcamDraggableDispatch } = this.props; + const { + placement: statePlacement, + orientation, + lastPlacementLandscape, + lastPlacementPortrait, + } = webcamDraggableState; const { webcamDraggableState: prevWebcamDraggableState } = prevProps; - const { placement: prevPlacement } = prevWebcamDraggableState; + const { placement: prevPlacement, orientation: prevOrientation } = prevWebcamDraggableState; if (prevProps.swapLayout !== swapLayout) { setTimeout(() => this.forceUpdate(), 500); } - if (prevPlacement !== placement) { + if (prevPlacement !== statePlacement) { setTimeout(() => this.forceUpdate(), 200); setTimeout(() => window.dispatchEvent(new Event('resize')), 500); } + + if (prevOrientation !== orientation) { + const storagePlacement = Storage.getItem('webcamPlacement'); + if ((prevOrientation == null || prevOrientation === 'portrait') && orientation === 'landscape') { + if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'top') webcamDraggableDispatch({ type: 'setplacementToTop' }); + if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'bottom') webcamDraggableDispatch({ type: 'setplacementToBottom' }); + } + if ((prevOrientation == null || prevOrientation === 'landscape') && orientation === 'portrait') { + if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'left') webcamDraggableDispatch({ type: 'setplacementToLeft' }); + if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'right') webcamDraggableDispatch({ type: 'setplacementToRight' }); + } + } } componentWillUnmount() { @@ -227,12 +243,16 @@ class WebcamDraggable extends Component { if (targetClassname) { if (targetClassname.includes('Top')) { webcamDraggableDispatch({ type: 'setplacementToTop' }); + webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToTop' }); } else if (targetClassname.includes('Right')) { webcamDraggableDispatch({ type: 'setplacementToRight' }); + webcamDraggableDispatch({ type: 'setLastPlacementPortraitToRight' }); } else if (targetClassname.includes('Bottom')) { webcamDraggableDispatch({ type: 'setplacementToBottom' }); + webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToBottom' }); } else if (targetClassname.includes('Left')) { webcamDraggableDispatch({ type: 'setplacementToLeft' }); + webcamDraggableDispatch({ type: 'setLastPlacementPortraitToLeft' }); } } webcamDraggableDispatch({ type: 'dragEnd' }); @@ -261,12 +281,12 @@ class WebcamDraggable extends Component { videoListSize, optimalGrid, } = webcamDraggableState; - let placement = Storage.getItem('webcamPlacement'); + + const placement = Storage.getItem('webcamPlacement'); + const lastPosition = Storage.getItem('webcamLastPosition') || { x: 0, y: 0 }; + let position = lastPosition; - if (!placement) { - placement = webcamsDefaultPlacement; - } if (dragging) { position = webcamDraggableState.tempPosition; @@ -537,4 +557,4 @@ class WebcamDraggable extends Component { WebcamDraggable.propTypes = propTypes; WebcamDraggable.defaultProps = defaultProps; -export default withDraggableContext(WebcamDraggable); +export default withDraggableConsumer(WebcamDraggable); diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx index c17a3104e0..155886dcaf 100644 --- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/context.jsx @@ -1,10 +1,15 @@ import React, { createContext, useReducer, useEffect } from 'react'; import Storage from '../../../services/storage/session'; +const { webcamsDefaultPlacement } = Meteor.settings.public.layout; + export const WebcamDraggableContext = createContext(); const initialState = { - placement: 'top', + placement: webcamsDefaultPlacement, + lastPlacementLandscape: 'top', + lastPlacementPortrait: 'left', + orientation: null, mediaSize: { width: 0, height: 0, @@ -58,12 +63,48 @@ const reducer = (state, action) => { placement: 'left', }; } + case 'setLastPlacementPortraitToLeft': { + return { + ...state, + lastPlacementPortrait: 'left', + }; + } + case 'setLastPlacementPortraitToRight': { + return { + ...state, + lastPlacementPortrait: 'right', + }; + } + case 'setLastPlacementLandscapeToTop': { + return { + ...state, + lastPlacementLandscape: 'top', + }; + } + case 'setLastPlacementLandscapeToBottom': { + return { + ...state, + lastPlacementLandscape: 'bottom', + }; + } case 'setplacementToFloating': { return { ...state, placement: 'floating', }; } + case 'setOrientationToLandscape': { + return { + ...state, + orientation: 'landscape', + }; + } + case 'setOrientationToPortrait': { + return { + ...state, + orientation: 'portrait', + }; + } case 'setMediaSize': { return { ...state, @@ -165,13 +206,22 @@ const ContextConsumer = Component => props => ( const ContextProvider = (props) => { const [webcamDraggableState, webcamDraggableDispatch] = useReducer(reducer, initialState); - const { placement, lastPosition } = webcamDraggableState; + const { + placement, + lastPosition, + lastPlacementLandscape, + lastPlacementPortrait, + } = webcamDraggableState; const { children } = props; useEffect(() => { Storage.setItem('webcamPlacement', placement); + Storage.setItem('webcamLastPlacementLandscape', lastPlacementLandscape); + Storage.setItem('webcamlastPlacementPortrait', lastPlacementPortrait); Storage.setItem('webcamLastPosition', lastPosition); }, [ placement, + lastPlacementLandscape, + lastPlacementPortrait, lastPosition, ]); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index 0b6b1a6f68..d736d3b4ef 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -15,6 +15,7 @@ import PresentationCloseButton from './presentation-close-button/component'; import DownloadPresentationButton from './download-presentation-button/component'; import FullscreenService from '../fullscreen-button/service'; import FullscreenButtonContainer from '../fullscreen-button/container'; +import { withDraggableContext, withDraggableConsumer } from '../media/webcam-draggable-overlay/context'; const intlMessages = defineMessages({ presentationLabel: { @@ -81,10 +82,25 @@ class PresentationArea extends PureComponent { window.addEventListener('resize', this.onResize); this.getInitialPresentationSizes(); this.refPresentationContainer.addEventListener('fullscreenchange', this.onFullscreenChange); + + const { slidePosition, webcamDraggableDispatch } = this.props; + const { width: currWidth, height: currHeight } = slidePosition; + if (currWidth > currHeight || currWidth === currHeight) { + webcamDraggableDispatch({ type: 'setOrientationToLandscape' }); + } + if (currHeight > currWidth) { + webcamDraggableDispatch({ type: 'setOrientationToPortrait' }); + } } componentDidUpdate(prevProps) { - const { currentPresentation, notify, intl } = this.props; + const { + currentPresentation, + notify, + intl, + slidePosition, + webcamDraggableDispatch, + } = this.props; if (prevProps.currentPresentation.name !== currentPresentation.name) { notify( @@ -93,6 +109,18 @@ class PresentationArea extends PureComponent { 'presentation', ); } + + const { width: prevWidth, height: prevHeight } = prevProps.slidePosition; + const { width: currWidth, height: currHeight } = slidePosition; + + if (prevWidth !== currWidth || prevHeight !== currHeight) { + if (currWidth > currHeight || currWidth === currHeight) { + webcamDraggableDispatch({ type: 'setOrientationToLandscape' }); + } + if (currHeight > currWidth) { + webcamDraggableDispatch({ type: 'setOrientationToPortrait' }); + } + } } componentWillUnmount() { @@ -654,7 +682,7 @@ class PresentationArea extends PureComponent { } } -export default injectIntl(PresentationArea); +export default injectIntl(withDraggableConsumer(PresentationArea)); PresentationArea.propTypes = { intl: intlShape.isRequired,