From e2db60e662d9a7e3c8619249ed9a90309daa55b6 Mon Sep 17 00:00:00 2001
From: Vitor Mateus <vitormateusalmeida@gmail.com>
Date: Thu, 23 Apr 2020 11:07:44 -0300
Subject: [PATCH] WIP Resizable works fine

---
 .../imports/startup/client/base.jsx           |   5 +
 .../imports/ui/components/layout/context.jsx  |  68 ++-
 .../ui/components/layout/layout-manager.jsx   | 202 ++++++---
 .../imports/ui/components/media/component.jsx |  32 +-
 .../imports/ui/components/media/container.jsx |   5 +-
 .../webcam-draggable-overlay/component.jsx    | 400 ++++++++----------
 .../webcam-draggable-overlay/context.jsx      |  95 -----
 .../ui/components/nav-bar/component.jsx       |   2 -
 .../ui/components/presentation/component.jsx  |  84 ++--
 .../components/video-provider/component.jsx   |  69 +--
 .../video-provider/video-list/component.jsx   |   6 +
 11 files changed, 487 insertions(+), 481 deletions(-)

diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx
index 54995be0b6..62bcb2ddaf 100755
--- a/bigbluebutton-html5/imports/startup/client/base.jsx
+++ b/bigbluebutton-html5/imports/startup/client/base.jsx
@@ -21,6 +21,7 @@ import getFromUserSettings from '/imports/ui/services/users-settings';
 import LayoutManager from '/imports/ui/components/layout/layout-manager';
 import { withLayoutContext } from '/imports/ui/components/layout/context';
 import IntlStartup from '/imports/startup/client/intl';
+import VideoService from '/imports/ui/components/video-provider/service';
 
 const CHAT_CONFIG = Meteor.settings.public.chat;
 const CHAT_ENABLED = CHAT_CONFIG.enabled;
@@ -215,6 +216,7 @@ class Base extends Component {
       locale,
       layoutContextState,
       layoutContextDispatch,
+      usersVideo,
     } = this.props;
 
     const { updateLoadingState } = this;
@@ -226,6 +228,7 @@ class Base extends Component {
         <LayoutManager
           layoutContextState={layoutContextState}
           layoutContextDispatch={layoutContextDispatch}
+          usersVideo={usersVideo}
         />
         {
           (!meetingExisted && !meetingExist && Auth.loggedIn)
@@ -389,6 +392,7 @@ const BaseContainer = withTracker(() => {
   }
 
   const codeError = Session.get('codeError');
+  const usersVideo = VideoService.getVideoStreams();
 
   return {
     approved,
@@ -405,6 +409,7 @@ const BaseContainer = withTracker(() => {
     subscriptionsReady: Session.get('subscriptionsReady'),
     loggedIn,
     codeError,
+    usersVideo,
   };
 })(withLayoutContext(Base));
 
diff --git a/bigbluebutton-html5/imports/ui/components/layout/context.jsx b/bigbluebutton-html5/imports/ui/components/layout/context.jsx
index 56eef52c34..fe07caca4a 100644
--- a/bigbluebutton-html5/imports/ui/components/layout/context.jsx
+++ b/bigbluebutton-html5/imports/ui/components/layout/context.jsx
@@ -1,12 +1,22 @@
-import React, { createContext, useReducer } from 'react';
+import React, { createContext, useReducer, useEffect } from 'react';
+import Storage from '/imports/ui/services/storage/session';
+
+const { webcamsDefaultPlacement } = Meteor.settings.public.layout;
 
 export const LayoutContext = createContext();
 
 const initialState = {
+  autoArrangeLayout: true,
   windowSize: {
     width: 0,
     height: 0,
   },
+  mediaBounds: {
+    width: 0,
+    height: 0,
+    top: 0,
+    left: 0,
+  },
   userListSize: {
     width: 0,
   },
@@ -17,15 +27,27 @@ const initialState = {
     width: 0,
     height: 0,
   },
+  percentWebcamsAreaUserSets: {
+    width: 0,
+    height: 0,
+  },
+  webcamsPlacement: webcamsDefaultPlacement || 'top',
   presentationAreaSize: {
     width: 0,
     height: 0,
   },
   presentationIsFullscreen: null,
+  presentationOrientation: null,
 };
 
 const reducer = (state, action) => {
   switch (action.type) {
+    case 'setAutoArrangeLayout': {
+      return {
+        ...state,
+        autoArrangeLayout: action.value,
+      };
+    }
     case 'setWindowSize': {
       return {
         ...state,
@@ -35,6 +57,17 @@ const reducer = (state, action) => {
         },
       };
     }
+    case 'setMediaBounds': {
+      return {
+        ...state,
+        mediaBounds: {
+          width: action.value.width,
+          height: action.value.height,
+          top: action.value.top,
+          left: action.value.left,
+        },
+      };
+    }
     case 'setUserListSize': {
       return {
         ...state,
@@ -51,6 +84,13 @@ const reducer = (state, action) => {
         },
       };
     }
+    case 'setWebcamsPlacement': {
+      // webcamsPlacement: ('top' | 'right' | 'bottom' | 'left') string
+      return {
+        ...state,
+        webcamsPlacement: action.value,
+      };
+    }
     case 'setWebcamsAreaSize': {
       return {
         ...state,
@@ -60,6 +100,15 @@ const reducer = (state, action) => {
         },
       };
     }
+    case 'setPercentWebcamsAreaUserSets': {
+      return {
+        ...state,
+        percentWebcamsAreaUserSets: {
+          width: action.value.width,
+          height: action.value.height,
+        },
+      };
+    }
     case 'setPresentationAreaSize': {
       return {
         ...state,
@@ -70,11 +119,19 @@ const reducer = (state, action) => {
       };
     }
     case 'setPresentationFullscreen': {
+      // presentationIsFullscreen: (true | false) boolean
       return {
         ...state,
         presentationIsFullscreen: action.value,
       };
     }
+    case 'setPresentationOrientation': {
+      // presentationOrientation: ('portrait' | 'landscape') string
+      return {
+        ...state,
+        presentationOrientation: action.value,
+      };
+    }
     default: {
       throw new Error('Unexpected action');
     }
@@ -83,8 +140,17 @@ const reducer = (state, action) => {
 
 const ContextProvider = (props) => {
   const [layoutContextState, layoutContextDispatch] = useReducer(reducer, initialState);
+  const { webcamsPlacement, autoArrangeLayout } = layoutContextState;
   const { children } = props;
 
+  useEffect(() => {
+    Storage.setItem('webcamsPlacement', webcamsPlacement);
+    Storage.setItem('autoArrangeLayout', autoArrangeLayout);
+  }, [
+    webcamsPlacement,
+    autoArrangeLayout,
+  ]);
+
   return (
     <LayoutContext.Provider value={{
       layoutContextState,
diff --git a/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx
index 36c625b374..0094791e8c 100644
--- a/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx
+++ b/bigbluebutton-html5/imports/ui/components/layout/layout-manager.jsx
@@ -16,6 +16,16 @@ const CHAT_MAX_WIDTH = 335;
 const NAVBAR_HEIGHT = 85;
 const ACTIONSBAR_HEIGHT = 42;
 
+const WEBCAMSAREA_MIN_PERCENT = 0.2;
+const WEBCAMSAREA_MAX_PERCENT = 0.8;
+
+// TODO function calPercentScreenOfUserSets
+// Salvar porcentagem de uso do media setado pelo usuário
+// Auto arange default true
+// Não pegar só proporção e sim calcular área disponível
+// Primeiro calculo pega orientação e medidas originais da apresentação
+// Segundo calculo olha para os valores e verifica se tem overflow e recalcula
+
 const storageLayoutData = () => Storage.getItem('layoutData');
 
 class LayoutManager extends Component {
@@ -23,7 +33,7 @@ class LayoutManager extends Component {
     super(props);
 
     this.setLayoutSizes = this.setLayoutSizes.bind(this);
-    this.calculaLayout = this.calculaLayout.bind(this);
+    this.calculatesLayout = this.calculatesLayout.bind(this);
   }
 
   componentDidMount() {
@@ -42,57 +52,31 @@ class LayoutManager extends Component {
     });
   }
 
-  setLayoutSizes(userlistChanged = false, chatChanged = false) {
-    const { layoutContextDispatch } = this.props;
-
-    let userListWidth;
-    let chatWidth;
-    let webcamsAreaWidth;
-    let presentationAreaWidth;
-    let webcamsAreaHeight;
-    let storageWindowWidth;
-    let presentationAreaHeight;
+  componentDidUpdate(prevProps) {
+    const { usersVideo, layoutContextState } = this.props;
+    const { usersVideo: prevUsersVideo, layoutContextState: prevLayoutContextState } = prevProps;
+    const { webcamsPlacement, webcamsAreaSize } = layoutContextState;
+    const {
+      webcamsPlacement: prevWebcamsPlacement,
+      webcamsAreaSize: prevWebcamsAreaSize,
+    } = prevLayoutContextState;
 
-    const layoutSizes = this.calculaLayout(userlistChanged, chatChanged);
-    // Get storage data and set in size variables
-    const storageLData = storageLayoutData();
-    if (storageLData) {
-      storageWindowWidth = storageLData.windowSize.width;
-      userListWidth = storageLData.userListSize.width;
-      chatWidth = storageLData.chatSize.width;
-      webcamsAreaWidth = storageLData.webcamsAreaSize.width;
-      webcamsAreaHeight = storageLData.webcamsAreaSize.height;
-      presentationAreaWidth = storageLData.presentationAreaSize.width;
-      presentationAreaHeight = storageLData.presentationAreaSize.height;
+    if (usersVideo.length !== prevUsersVideo.length
+      || webcamsPlacement !== prevWebcamsPlacement) {
+      this.setLayoutSizes();
     }
 
-    // If storage data does not exist or window size is changed or layout is changed
-    // (userlist or chat list size changed)
-    // Get size from calc function
-    if (!userListWidth
-      || windowWidth() !== storageWindowWidth
-      || userlistChanged
-      || chatChanged) {
-      userListWidth = layoutSizes.userListSize.width;
-    }
-    if (!chatWidth
-      || windowWidth() !== storageWindowWidth
-      || chatChanged
-      || userlistChanged) {
-      chatWidth = layoutSizes.chatSize.width;
-    }
-    if (!storageLayoutData() || windowWidth() !== storageWindowWidth) {
-      webcamsAreaWidth = layoutSizes.webcamsAreaSize.width;
-      webcamsAreaHeight = layoutSizes.webcamsAreaSize.height;
-    }
-    if (!storageLayoutData()
-      || windowWidth() !== storageWindowWidth
-      || layoutSizes.presentationAreaSize.width !== storageLayoutData().presentationAreaSize.width
-      || userlistChanged
-      || chatChanged) {
-      presentationAreaWidth = layoutSizes.presentationAreaSize.width;
-      presentationAreaHeight = layoutSizes.presentationAreaSize.height;
+    if (webcamsAreaSize.width !== prevWebcamsAreaSize.width
+      || webcamsAreaSize.height !== prevWebcamsAreaSize.height) {
+      this.setLayoutSizes(true, true, webcamsAreaSize);
     }
+  }
+
+  setLayoutSizes(userlistChanged = false, chatChanged = false, webcamsAreaSize = null) {
+    const { layoutContextDispatch } = this.props;
+
+    const layoutSizes = this
+      .calculatesLayout(userlistChanged, chatChanged, webcamsAreaSize);
 
     layoutContextDispatch(
       {
@@ -103,11 +87,22 @@ class LayoutManager extends Component {
         },
       },
     );
+    layoutContextDispatch(
+      {
+        type: 'setMediaBounds',
+        value: {
+          width: layoutSizes.mediaBounds.width,
+          height: layoutSizes.mediaBounds.height,
+          top: layoutSizes.mediaBounds.top,
+          left: layoutSizes.mediaBounds.left,
+        },
+      },
+    );
     layoutContextDispatch(
       {
         type: 'setUserListSize',
         value: {
-          width: userListWidth,
+          width: layoutSizes.userListSize.width,
         },
       },
     );
@@ -115,7 +110,7 @@ class LayoutManager extends Component {
       {
         type: 'setChatSize',
         value: {
-          width: chatWidth,
+          width: layoutSizes.chatSize.width,
         },
       },
     );
@@ -123,8 +118,8 @@ class LayoutManager extends Component {
       {
         type: 'setWebcamsAreaSize',
         value: {
-          width: webcamsAreaWidth,
-          height: webcamsAreaHeight,
+          width: layoutSizes.webcamsAreaSize.width,
+          height: layoutSizes.webcamsAreaSize.height,
         },
       },
     );
@@ -132,8 +127,8 @@ class LayoutManager extends Component {
       {
         type: 'setPresentationAreaSize',
         value: {
-          width: presentationAreaWidth,
-          height: presentationAreaHeight,
+          width: layoutSizes.presentationAreaSize.width,
+          height: layoutSizes.presentationAreaSize.height,
         },
       },
     );
@@ -143,35 +138,49 @@ class LayoutManager extends Component {
         width: windowWidth(),
         height: windowHeight(),
       },
+      mediaBounds: {
+        width: layoutSizes.mediaBounds.width,
+        height: layoutSizes.mediaBounds.height,
+        top: layoutSizes.mediaBounds.top,
+        left: layoutSizes.mediaBounds.left,
+      },
       userListSize: {
-        width: userListWidth,
+        width: layoutSizes.userListSize.width,
       },
       chatSize: {
-        width: chatWidth,
+        width: layoutSizes.chatSize.width,
       },
       webcamsAreaSize: {
-        width: webcamsAreaWidth,
-        height: webcamsAreaHeight,
+        width: layoutSizes.webcamsAreaSize.width,
+        height: layoutSizes.webcamsAreaSize.height,
       },
       presentationAreaSize: {
-        width: presentationAreaWidth,
-        height: presentationAreaHeight,
+        width: layoutSizes.presentationAreaSize.width,
+        height: layoutSizes.presentationAreaSize.height,
       },
     };
 
     Storage.setItem('layoutData', newLayoutData);
   }
 
-  calculaLayout(userlistChanged = false, chatChanged = false) {
-    const { layoutContextState } = this.props;
+  calculatesLayout(userlistChanged = false, chatChanged = false) {
     const {
+      layoutContextState,
+      usersVideo,
+    } = this.props;
+    const {
+      webcamsAreaSize,
       userListSize: userListSizeContext,
       chatSize: chatSizeContext,
       presentationIsFullscreen,
+      presentationOrientation,
+      webcamsPlacement,
     } = layoutContextState;
     const openPanel = Session.get('openPanel');
 
     const storageLData = storageLayoutData();
+    const autoArrangeLayout = Storage.getItem('autoArrangeLayout');
+
     let storageUserListWidth;
     let storageChatWidth;
     if (storageLData) {
@@ -220,15 +229,75 @@ class LayoutManager extends Component {
       };
     }
 
+    const fullAreaHeight = windowHeight() - (NAVBAR_HEIGHT + ACTIONSBAR_HEIGHT);
+    const fullAreaWidth = windowWidth() - (newUserListSize.width + newChatSize.width);
+
+
+    const newMediaBounds = {
+      width: fullAreaWidth,
+      height: fullAreaHeight,
+      top: NAVBAR_HEIGHT,
+      left: newUserListSize.width + newChatSize.width,
+    };
+    console.log('webcamsAreaSize', webcamsAreaSize);
+    console.log('autoArrangeLayout', autoArrangeLayout);
+    
+
+    let newWebcamAreaHeight;
+    let newWebcamAreaWidth;
+    let newPresentationAreaHeight;
+    let newPresentationAreaWidth;
+    if (usersVideo.length > 0) {
+      if (presentationOrientation === 'portrait'
+        || webcamsPlacement === 'left'
+        || webcamsPlacement === 'right'
+      ) {
+        newWebcamAreaWidth = min(
+          max(
+            webcamsAreaSize && autoArrangeLayout === false
+              ? webcamsAreaSize.width
+              : fullAreaWidth * WEBCAMSAREA_MIN_PERCENT,
+            fullAreaWidth * WEBCAMSAREA_MIN_PERCENT,
+          ),
+          fullAreaWidth * WEBCAMSAREA_MAX_PERCENT,
+        );
+        newWebcamAreaHeight = fullAreaHeight;
+
+        newPresentationAreaWidth = fullAreaWidth - newWebcamAreaWidth - 30; // 30 is margins
+        newPresentationAreaHeight = fullAreaHeight - 20; // 20 is margins
+      } else {
+        newWebcamAreaWidth = fullAreaWidth;
+        newWebcamAreaHeight = min(
+          max(
+            webcamsAreaSize && autoArrangeLayout === false
+              ? webcamsAreaSize.height
+              : fullAreaHeight * WEBCAMSAREA_MIN_PERCENT,
+            fullAreaHeight * WEBCAMSAREA_MIN_PERCENT,
+          ),
+          fullAreaHeight * WEBCAMSAREA_MAX_PERCENT,
+        );
+
+        newPresentationAreaWidth = fullAreaWidth;
+        newPresentationAreaHeight = fullAreaHeight - newWebcamAreaHeight - 30; // 30 is margins
+      }
+    } else {
+      newWebcamAreaWidth = 0;
+      newWebcamAreaHeight = 0;
+
+      newPresentationAreaWidth = fullAreaWidth;
+      newPresentationAreaHeight = fullAreaHeight - 20; // 20 is margins
+    }
+
     const newWebcamsAreaSize = {
-      width: windowWidth() - (newUserListSize.width + newChatSize.width),
+      width: newWebcamAreaWidth,
+      height: newWebcamAreaHeight,
     };
 
     let newPresentationAreaSize;
     if (!presentationIsFullscreen) {
       newPresentationAreaSize = {
-        width: windowWidth() - (newUserListSize.width + newChatSize.width),
-        height: windowHeight() - (NAVBAR_HEIGHT + ACTIONSBAR_HEIGHT),
+        width: newPresentationAreaWidth,
+        height: newPresentationAreaHeight,
       };
     } else {
       newPresentationAreaSize = {
@@ -238,6 +307,7 @@ class LayoutManager extends Component {
     }
 
     return {
+      mediaBounds: newMediaBounds,
       userListSize: newUserListSize,
       chatSize: newChatSize,
       webcamsAreaSize: newWebcamsAreaSize,
@@ -258,4 +328,6 @@ export {
   CHAT_MAX_WIDTH,
   NAVBAR_HEIGHT,
   ACTIONSBAR_HEIGHT,
+  WEBCAMSAREA_MIN_PERCENT,
+  WEBCAMSAREA_MAX_PERCENT,
 };
diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx
index 36c17f52fe..9f7c18c054 100644
--- a/bigbluebutton-html5/imports/ui/components/media/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx
@@ -16,7 +16,7 @@ const propTypes = {
   swapLayout: PropTypes.bool,
   disableVideo: PropTypes.bool,
   audioModalIsOpen: PropTypes.bool,
-  webcamDraggableState: PropTypes.instanceOf(Object).isRequired,
+  layoutContextState: PropTypes.instanceOf(Object).isRequired,
 };
 
 const defaultProps = {
@@ -43,12 +43,12 @@ export default class Media extends Component {
       children,
       audioModalIsOpen,
       usersVideo,
-      webcamDraggableState,
+      layoutContextState,
     } = this.props;
 
-    const { placement } = webcamDraggableState;
-    const placementStorage = Storage.getItem('webcamPlacement');
-    const webcamPlacement = placement || placementStorage;
+    const { webcamsPlacement: placement } = layoutContextState;
+    const placementStorage = Storage.getItem('webcamsPlacement');
+    const webcamsPlacement = placement || placementStorage;
 
     const contentClassName = cx({
       [styles.content]: true,
@@ -57,13 +57,13 @@ export default class Media extends Component {
     const overlayClassName = cx({
       [styles.overlay]: true,
       [styles.hideOverlay]: hideOverlay,
-      [styles.floatingOverlay]: (webcamPlacement === 'floating'),
+      [styles.floatingOverlay]: (webcamsPlacement === 'floating'),
     });
 
     const containerClassName = cx({
       [styles.container]: true,
-      [styles.containerV]: webcamPlacement === 'top' || webcamPlacement === 'bottom' || webcamPlacement === 'floating',
-      [styles.containerH]: webcamPlacement === 'left' || webcamPlacement === 'right',
+      [styles.containerV]: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' || webcamsPlacement === 'floating',
+      [styles.containerH]: webcamsPlacement === 'left' || webcamsPlacement === 'right',
     });
 
     return (
@@ -77,24 +77,24 @@ export default class Media extends Component {
           style={{
             maxHeight: usersVideo.length > 0
             && (
-              webcamPlacement !== 'left'
-              || webcamPlacement !== 'right'
+              webcamsPlacement !== 'left'
+              || webcamsPlacement !== 'right'
             )
             && (
-              webcamPlacement === 'top'
-              || webcamPlacement === 'bottom'
+              webcamsPlacement === 'top'
+              || webcamsPlacement === 'bottom'
             )
               ? '80%'
               : '100%',
             minHeight: BROWSER_ISMOBILE && window.innerWidth > window.innerHeight ? '50%' : '20%',
             maxWidth: usersVideo.length > 0
             && (
-              webcamPlacement !== 'top'
-              || webcamPlacement !== 'bottom'
+              webcamsPlacement !== 'top'
+              || webcamsPlacement !== 'bottom'
             )
             && (
-              webcamPlacement === 'left'
-              || webcamPlacement === 'right'
+              webcamsPlacement === 'left'
+              || webcamsPlacement === 'right'
             )
               ? '80%'
               : '100%',
diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx
index 4efa85853f..bd0091cd16 100755
--- a/bigbluebutton-html5/imports/ui/components/media/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx
@@ -16,6 +16,7 @@ import DefaultContent from '../presentation/default-content/component';
 import ExternalVideoContainer from '../external-video-player/container';
 import Storage from '../../services/storage/session';
 import { withDraggableConsumer } from './webcam-draggable-overlay/context';
+import { withLayoutConsumer } from '/imports/ui/components/layout/context';
 
 const LAYOUT_CONFIG = Meteor.settings.public.layout;
 const KURENTO_CONFIG = Meteor.settings.public.kurento;
@@ -103,7 +104,7 @@ class MediaContainer extends Component {
   }
 }
 
-export default withDraggableConsumer(withModalMounter(withTracker(() => {
+export default withLayoutConsumer(withModalMounter(withTracker(() => {
   const { dataSaving } = Settings;
   const { viewParticipantsWebcams, viewScreenshare } = dataSaving;
   const hidePresentation = getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation);
@@ -150,7 +151,7 @@ export default withDraggableConsumer(withModalMounter(withTracker(() => {
     );
   }
 
-  data.webcamPlacement = Storage.getItem('webcamPlacement');
+  data.webcamsPlacement = Storage.getItem('webcamsPlacement');
 
   MediaContainer.propTypes = propTypes;
   return data;
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 40f82c4ee6..e5a9fe8f63 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,7 +1,6 @@
 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';
@@ -9,6 +8,8 @@ import { withDraggableConsumer } from './context';
 import VideoProviderContainer from '/imports/ui/components/video-provider/container';
 import { styles } from '../styles.scss';
 import Storage from '../../../services/storage/session';
+import { withLayoutConsumer } from '/imports/ui/components/layout/context';
+import { WEBCAMSAREA_MIN_PERCENT } from '/imports/ui/components/layout/layout-manager';
 
 const BROWSER_ISMOBILE = isMobile || isIPad13;
 
@@ -20,7 +21,6 @@ const propTypes = {
   webcamDraggableState: PropTypes.objectOf(Object).isRequired,
   webcamDraggableDispatch: PropTypes.func.isRequired,
   refMediaContainer: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
-  usersVideoLenght: PropTypes.number.isRequired,
 };
 
 const defaultProps = {
@@ -30,70 +30,110 @@ const defaultProps = {
   audioModalIsOpen: false,
   refMediaContainer: null,
 };
-const dispatchResizeEvent = () => window.dispatchEvent(new Event('resize'));
 
 class WebcamDraggable extends PureComponent {
   constructor(props) {
     super(props);
 
+    const { layoutContextState } = props;
+    const { webcamsPlacement, mediaBounds } = layoutContextState;
+    this.state = {
+      webcamsAreaResizable: {
+        width: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' ? mediaBounds.width : mediaBounds.width * WEBCAMSAREA_MIN_PERCENT,
+        height: webcamsPlacement === 'left' || webcamsPlacement === 'right' ? mediaBounds.height : mediaBounds.height * WEBCAMSAREA_MIN_PERCENT,
+      },
+      resizing: false,
+    };
+
     this.handleWebcamDragStart = this.handleWebcamDragStart.bind(this);
     this.handleWebcamDragStop = this.handleWebcamDragStop.bind(this);
     this.onFullscreenChange = this.onFullscreenChange.bind(this);
-    this.debouncedOnResize = _.debounce(this.onWindowResize.bind(this), 500);
     this.onResizeStop = this.onResizeStop.bind(this);
     this.onResizeStart = this.onResizeStart.bind(this);
-    this.setPlacementPercent = this.setPlacementPercent.bind(this);
-    this.recalculateAreaSize = this.recalculateAreaSize.bind(this);
-
-    this.state = {
-      resizing: false,
-      placementPercent: 0,
-    };
   }
 
   componentDidMount() {
-    dispatchResizeEvent();
-    window.addEventListener('resize', this.debouncedOnResize);
+    const { webcamDraggableState } = this.props;
+    const {
+      lastPlacementLandscape,
+      lastPlacementPortrait,
+    } = webcamDraggableState;
+    const { layoutContextState, layoutContextDispatch } = this.props;
+    const { presentationOrientation } = layoutContextState;
     document.addEventListener('fullscreenchange', this.onFullscreenChange);
     window.addEventListener('orientationchange', () => setTimeout(this.recalculateAreaSize, 500));
+
+    if (presentationOrientation === 'landscape') {
+      layoutContextDispatch({
+        type: 'setWebcamsPlacement',
+        value: !lastPlacementLandscape ? 'top' : lastPlacementLandscape,
+      });
+    }
+    if (presentationOrientation === 'portrait') {
+      layoutContextDispatch({
+        type: 'setWebcamsPlacement',
+        value: !lastPlacementPortrait ? 'left' : lastPlacementPortrait,
+      });
+    }
   }
 
   componentDidUpdate(prevProps) {
+    // const { swapLayout } = this.props;
+
+    // Webcam Draggable Context
+    const { webcamDraggableState } = this.props;
     const {
-      swapLayout,
-      webcamDraggableState,
-      webcamDraggableDispatch,
-      usersVideoLenght,
-    } = this.props;
-    const {
-      placement: statePlacement,
-      orientation,
       lastPlacementLandscape,
       lastPlacementPortrait,
     } = webcamDraggableState;
-    const { webcamDraggableState: prevWebcamDraggableState } = prevProps;
-    const { placement: prevPlacement, orientation: prevOrientation } = prevWebcamDraggableState;
-    if (prevProps.swapLayout !== swapLayout) {
-      setTimeout(() => this.forceUpdate(), 500);
-    }
-    if (prevPlacement !== statePlacement) {
-      setTimeout(() => this.forceUpdate(), 200);
-      setTimeout(() => window.dispatchEvent(new Event('resize')), 500);
-    }
 
-    if (prevProps.usersVideoLenght !== usersVideoLenght) {
-      dispatchResizeEvent();
+    // Layout Context
+    const { layoutContextState, layoutContextDispatch } = this.props;
+    const { layoutContextState: prevLayoutContextState } = prevProps;
+    const {
+      webcamsAreaSize,
+      presentationOrientation,
+    } = layoutContextState;
+    const {
+      webcamsAreaSize: prevWebcamsAreaSize,
+      presentationOrientation: prevPresentationOrientation,
+    } = prevLayoutContextState;
+
+
+    if (webcamsAreaSize.width !== prevWebcamsAreaSize.width
+      || webcamsAreaSize.height !== prevWebcamsAreaSize.height) {
+      this.setWebcamsAreaResizable(webcamsAreaSize.width, webcamsAreaSize.height);
     }
 
-    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 (prevPresentationOrientation !== presentationOrientation) {
+      const storagePlacement = Storage.getItem('webcamsPlacement');
+      if ((prevPresentationOrientation == null || prevPresentationOrientation === 'portrait') && presentationOrientation === 'landscape') {
+        if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'top') {
+          layoutContextDispatch({
+            type: 'setWebcamsPlacement',
+            value: 'top',
+          });
+        }
+        if (storagePlacement !== lastPlacementLandscape && lastPlacementLandscape === 'bottom') {
+          layoutContextDispatch({
+            type: 'setWebcamsPlacement',
+            value: 'bottom',
+          });
+        }
       }
-      if ((prevOrientation == null || prevOrientation === 'landscape') && orientation === 'portrait') {
-        if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'left') webcamDraggableDispatch({ type: 'setplacementToLeft' });
-        if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'right') webcamDraggableDispatch({ type: 'setplacementToRight' });
+      if ((prevPresentationOrientation == null || prevPresentationOrientation === 'landscape') && presentationOrientation === 'portrait') {
+        if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'left') {
+          layoutContextDispatch({
+            type: 'setWebcamsPlacement',
+            value: 'left',
+          });
+        }
+        if (storagePlacement !== lastPlacementPortrait && lastPlacementPortrait === 'right') {
+          layoutContextDispatch({
+            type: 'setWebcamsPlacement',
+            value: 'right',
+          });
+        }
       }
     }
   }
@@ -101,7 +141,6 @@ class WebcamDraggable extends PureComponent {
   componentWillUnmount() {
     window.removeEventListener('resize', this.debouncedOnResize);
     document.removeEventListener('fullscreenchange', this.onFullscreenChange);
-    dispatchResizeEvent();
   }
 
   onFullscreenChange() {
@@ -112,93 +151,48 @@ class WebcamDraggable extends PureComponent {
     this.setState({ resizing: true });
   }
 
-  onWindowResize() {
-    const { webcamDraggableState, webcamDraggableDispatch } = this.props;
-    const { mediaSize } = webcamDraggableState;
-    const { width: stateWidth, height: stateHeight } = mediaSize;
-    const { width, height } = this.getMediaBounds();
-
-    if (stateWidth !== width || stateHeight !== height) {
-      webcamDraggableDispatch(
-        {
-          type: 'setMediaSize',
-          value: {
-            width,
-            height,
-          },
-        },
-      );
-      setTimeout(() => window.dispatchEvent(new Event('resize')), 300);
-    }
-  }
+  onResizeStop(resizableWidth, resizableHeight) {
+    const { webcamsAreaResizable } = this.state;
+    const { layoutContextState, layoutContextDispatch } = this.props;
+    const { webcamsPlacement, webcamsAreaSize } = layoutContextState;
 
-  onResize() {
-    this.setPlacementPercent();
-  }
+    layoutContextDispatch(
+      {
+        type: 'setAutoArrangeLayout',
+        value: false,
+      },
+    );
+
+    const newWebcamsAreaResizable = {
+      width: Math.trunc(webcamsAreaResizable.width) + resizableWidth,
+      height: Math.trunc(webcamsAreaResizable.height) + resizableHeight,
+    };
 
-  onResizeStop() {
-    const { webcamDraggableState, webcamDraggableDispatch } = this.props;
-    const { optimalGrid } = webcamDraggableState;
-    if (optimalGrid) {
-      webcamDraggableDispatch(
-        {
-          type: 'setVideoListSize',
-          value: {
-            width: optimalGrid.width,
-            height: optimalGrid.height,
-          },
+    layoutContextDispatch(
+      {
+        type: 'setWebcamsAreaSize',
+        value: {
+          width: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' ? webcamsAreaSize.width : newWebcamsAreaResizable.width,
+          height: webcamsPlacement === 'left' || webcamsPlacement === 'right' ? webcamsAreaSize.height : newWebcamsAreaResizable.height,
         },
-      );
-    }
-    this.setPlacementPercent();
-    window.dispatchEvent(new Event('resize'));
-    setTimeout(() => this.setState({ resizing: false }), 500);
-  }
+      },
+    );
 
-  setPlacementPercent() {
-    const { webcamDraggableState } = this.props;
-    const { optimalGrid, placement } = webcamDraggableState;
-    if (placement === 'top' || placement === 'bottom') {
-      const mediaSelection = document.querySelector('section[class^=media]');
-      const mediaHeight = mediaSelection ? mediaSelection.offsetHeight : 0;
-      this.setState({ placementPercent: (optimalGrid.height * 100) / mediaHeight });
-    }
-    if (placement === 'left' || placement === 'right') {
-      const mediaSelection = document.querySelector('section[class^=media]');
-      const mediaWidth = mediaSelection ? mediaSelection.offsetWidth : 0;
-      this.setState({ placementPercent: (optimalGrid.width * 100) / mediaWidth });
-    }
-  }
+    this.setState({
+      webcamsAreaResizable: {
+        width: webcamsPlacement === 'top' || webcamsPlacement === 'bottom' ? webcamsAreaSize.width : newWebcamsAreaResizable.width,
+        height: webcamsPlacement === 'left' || webcamsPlacement === 'right' ? webcamsAreaSize.height : newWebcamsAreaResizable.height,
+      },
+    });
 
-  getMediaBounds() {
-    const { refMediaContainer, webcamDraggableState, webcamDraggableDispatch } = this.props;
-    const { mediaSize: mediaState } = webcamDraggableState;
-    const { current: mediaContainer } = refMediaContainer;
-    if (mediaContainer) {
-      const mediaContainerRect = mediaContainer.getBoundingClientRect();
-      const {
-        top, left, width: newWidth, height: newHeight,
-      } = mediaContainerRect;
-      if ((mediaState.width === 0 || mediaState.height === 0) && (newWidth > 0 && newHeight > 0)) {
-        webcamDraggableDispatch(
-          {
-            type: 'setMediaSize',
-            value: {
-              newWidth,
-              newHeight,
-            },
-          },
-        );
-      }
+    setTimeout(() => this.setState({ resizing: false }), 500);
+    window.dispatchEvent(new Event('webcamAreaResize'));
+  }
 
-      return {
-        top,
-        left,
-        width: newWidth,
-        height: newHeight,
-      };
-    }
-    return false;
+  setWebcamsAreaResizable(width, height) {
+    this.setState({
+      webcamsAreaResizable: { width, height },
+    });
   }
 
   getWebcamsListBounds() {
@@ -210,22 +204,20 @@ class WebcamDraggable extends PureComponent {
         top, left, width, height,
       } = videoListRefRect;
       return {
-        top, // 10 = margin
-        left, // 10 = margin
-        width, // 20 = margin
-        height, // 20 = margin
+        top,
+        left,
+        width,
+        height,
       };
     }
     return false;
   }
 
-  recalculateAreaSize() {
-    this.onResizeStart();
-    this.onResizeStop();
-  }
-
   calculatePosition() {
-    const { top: mediaTop, left: mediaLeft } = this.getMediaBounds();
+    const { layoutContextState } = this.props;
+    const { mediaBounds } = layoutContextState;
+
+    const { top: mediaTop, left: mediaLeft } = mediaBounds;
     const { top: webcamsListTop, left: webcamsListLeft } = this.getWebcamsListBounds();
     const x = webcamsListLeft - mediaLeft;
     const y = webcamsListTop - mediaTop;
@@ -251,52 +243,57 @@ class WebcamDraggable extends PureComponent {
   }
 
   handleWebcamDragStop(e) {
-    const { webcamDraggableDispatch } = this.props;
+    const { webcamDraggableDispatch, layoutContextDispatch } = this.props;
     const targetClassname = JSON.stringify(e.target.className);
 
     if (targetClassname) {
       if (targetClassname.includes('Top')) {
-        webcamDraggableDispatch({ type: 'setplacementToTop' });
-        webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToTop' });
+        layoutContextDispatch({
+          type: 'setWebcamsPlacement',
+          value: 'top',
+        });
       } else if (targetClassname.includes('Right')) {
-        webcamDraggableDispatch({ type: 'setplacementToRight' });
-        webcamDraggableDispatch({ type: 'setLastPlacementPortraitToRight' });
+        layoutContextDispatch({
+          type: 'setWebcamsPlacement',
+          value: 'right',
+        });
       } else if (targetClassname.includes('Bottom')) {
-        webcamDraggableDispatch({ type: 'setplacementToBottom' });
-        webcamDraggableDispatch({ type: 'setLastPlacementLandscapeToBottom' });
+        layoutContextDispatch({
+          type: 'setWebcamsPlacement',
+          value: 'bottom',
+        });
       } else if (targetClassname.includes('Left')) {
-        webcamDraggableDispatch({ type: 'setplacementToLeft' });
-        webcamDraggableDispatch({ type: 'setLastPlacementPortraitToLeft' });
+        layoutContextDispatch({
+          type: 'setWebcamsPlacement',
+          value: 'left',
+        });
       }
     }
     webcamDraggableDispatch({ type: 'dragEnd' });
-    window.dispatchEvent(new Event('resize'));
-    setTimeout(this.recalculateAreaSize, 500);
+    window.dispatchEvent(new Event('webcamAreaResize'));
   }
 
   render() {
     const {
+      layoutContextState,
       webcamDraggableState,
       swapLayout,
       hideOverlay,
       disableVideo,
       audioModalIsOpen,
-      refMediaContainer,
     } = this.props;
 
-    const {
-      resizing,
-      placementPercent,
-    } = this.state;
+    const { resizing, webcamsAreaResizable } = this.state;
+
+    const { mediaBounds } = layoutContextState;
 
     const {
       dragging,
       isCameraFullscreen,
-      videoListSize,
       optimalGrid,
     } = webcamDraggableState;
 
-    const placement = Storage.getItem('webcamPlacement');
+    const webcamsPlacement = Storage.getItem('webcamsPlacement');
 
     const lastPosition = Storage.getItem('webcamLastPosition') || { x: 0, y: 0 };
 
@@ -323,7 +320,7 @@ class WebcamDraggable extends PureComponent {
     const {
       width: mediaWidth,
       height: mediaHeight,
-    } = this.getMediaBounds();
+    } = mediaBounds;
 
     const {
       width: webcamsWidth,
@@ -346,57 +343,16 @@ class WebcamDraggable extends PureComponent {
       [styles.fullHeight]: swapLayout,
     });
 
-    const { current: mediaContainer } = refMediaContainer;
-    let layout = 'vertical';
-    if (mediaContainer) {
-      const classNameMediaContainer = mediaContainer.className;
-      if (classNameMediaContainer.includes('containerH')) {
-        layout = 'horizontal';
-      } else {
-        layout = 'vertical';
-      }
-    }
-
     const overlayClassName = cx({
       [styles.overlay]: true,
       [styles.hideOverlay]: hideOverlay,
       [styles.floatingOverlay]: dragging,
       [styles.autoWidth]: dragging,
-      [styles.fullWidth]: (
-        (
-          placement === 'top'
-          || placement === 'bottom'
-        )
-        || swapLayout
-      )
-        && !dragging,
-      [styles.fullHeight]: (
-        (
-          placement === 'left'
-          && placement === 'right'
-        )
-        || swapLayout
-      )
-        && !dragging,
-      [styles.overlayToTop]: placement === 'top' && !dragging,
-      [styles.overlayToRight]: placement === 'right' && !dragging,
-      [styles.overlayToBottom]: placement === 'bottom' && !dragging,
-      [styles.overlayToLeft]: placement === 'left' && !dragging,
+      [styles.overlayToTop]: webcamsPlacement === 'top' && !dragging,
+      [styles.overlayToRight]: webcamsPlacement === 'right' && !dragging,
+      [styles.overlayToBottom]: webcamsPlacement === 'bottom' && !dragging,
+      [styles.overlayToLeft]: webcamsPlacement === 'left' && !dragging,
       [styles.dragging]: dragging,
-      [styles.hide]: (
-        (
-          placement === 'left'
-          || placement === 'right'
-        )
-        && layout === 'vertical'
-      )
-        || (
-          (
-            placement === 'top'
-            || placement === 'bottom'
-          )
-          && layout === 'horizontal'
-        ),
     });
 
     const dropZoneTopClassName = cx({
@@ -443,35 +399,6 @@ class WebcamDraggable extends PureComponent {
       [styles.dropZoneBgRight]: true,
     });
 
-    const mediaSelection = document.querySelector('section[class^=media]');
-    const mHeight = mediaSelection ? mediaSelection.offsetHeight : 0;
-    const mWidth = mediaSelection ? mediaSelection.offsetWidth : 0;
-
-    let resizeWidth;
-    let resizeHeight;
-    if (resizing && (placement === 'top' || placement === 'bottom') && !dragging) {
-      resizeWidth = '100%';
-      resizeHeight = videoListSize.height;
-    }
-    if (!resizing && (placement === 'top' || placement === 'bottom') && !dragging) {
-      resizeWidth = '100%';
-      resizeHeight = mHeight * (placementPercent / 100);
-    }
-
-    if (resizing && (placement === 'left' || placement === 'right') && !dragging) {
-      resizeWidth = videoListSize.width;
-      resizeHeight = '100%';
-    }
-    if (!resizing && (placement === 'left' || placement === 'right') && !dragging) {
-      resizeWidth = mWidth * (placementPercent / 100);
-      resizeHeight = '100%';
-    }
-
-    if (dragging) {
-      resizeHeight = optimalGrid.height;
-      resizeWidth = optimalGrid.width;
-    }
-
     return (
       <Fragment>
         <div
@@ -499,26 +426,29 @@ class WebcamDraggable extends PureComponent {
           onStart={this.handleWebcamDragStart}
           onStop={this.handleWebcamDragStop}
           onMouseDown={e => e.preventDefault()}
-          disabled={swapLayout || isCameraFullscreen || BROWSER_ISMOBILE}
+          disabled={swapLayout || isCameraFullscreen || BROWSER_ISMOBILE || resizing}
           position={position}
         >
           <Resizable
+            minWidth={mediaBounds.width * WEBCAMSAREA_MIN_PERCENT}
+            minHeight={mediaBounds.height * WEBCAMSAREA_MIN_PERCENT}
             size={
               {
-                height: resizeHeight,
-                width: resizeWidth,
+                height: dragging ? optimalGrid.height : webcamsAreaResizable.height,
+                width: dragging ? optimalGrid.width : webcamsAreaResizable.width,
               }
             }
             lockAspectRatio
             handleWrapperClass="resizeWrapper"
             onResizeStart={this.onResizeStart}
-            onResize={dispatchResizeEvent}
-            onResizeStop={this.onResizeStop}
+            onResizeStop={(e, direction, ref, d) => {
+              this.onResizeStop(d.width, d.height);
+            }}
             enable={{
-              top: (placement === 'bottom') && !swapLayout,
-              bottom: (placement === 'top') && !swapLayout,
-              left: (placement === 'right') && !swapLayout,
-              right: (placement === 'left') && !swapLayout,
+              top: (webcamsPlacement === 'bottom') && !swapLayout,
+              bottom: (webcamsPlacement === 'top') && !swapLayout,
+              left: (webcamsPlacement === 'right') && !swapLayout,
+              right: (webcamsPlacement === 'left') && !swapLayout,
               topLeft: false,
               topRight: false,
               bottomLeft: false,
@@ -572,4 +502,4 @@ class WebcamDraggable extends PureComponent {
 WebcamDraggable.propTypes = propTypes;
 WebcamDraggable.defaultProps = defaultProps;
 
-export default withDraggableConsumer(WebcamDraggable);
+export default withDraggableConsumer(withLayoutConsumer(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 34b0e8935b..f86b406f77 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,19 +1,9 @@
 import React, { createContext, useReducer, useEffect } from 'react';
 import Storage from '/imports/ui/services/storage/session';
 
-const { webcamsDefaultPlacement } = Meteor.settings.public.layout;
-
 export const WebcamDraggableContext = createContext();
 
 const initialState = {
-  placement: webcamsDefaultPlacement || 'top',
-  lastPlacementLandscape: 'top',
-  lastPlacementPortrait: 'left',
-  orientation: null,
-  mediaSize: {
-    width: 0,
-    height: 0,
-  },
   videoListSize: {
     width: 0,
     height: 0,
@@ -39,81 +29,6 @@ const initialState = {
 
 const reducer = (state, action) => {
   switch (action.type) {
-    case 'setplacementToTop': {
-      return {
-        ...state,
-        placement: 'top',
-      };
-    }
-    case 'setplacementToRight': {
-      return {
-        ...state,
-        placement: 'right',
-      };
-    }
-    case 'setplacementToBottom': {
-      return {
-        ...state,
-        placement: 'bottom',
-      };
-    }
-    case 'setplacementToLeft': {
-      return {
-        ...state,
-        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,
-        mediaSize: {
-          width: action.value.width,
-          height: action.value.height,
-        },
-      };
-    }
     case 'setVideoListSize': {
       return {
         ...state,
@@ -147,15 +62,6 @@ const reducer = (state, action) => {
         },
       };
     }
-    case 'setLastPosition': {
-      return {
-        ...state,
-        lastPosition: {
-          x: action.value.x,
-          y: action.value.y,
-        },
-      };
-    }
     case 'setVideoRef': {
       return {
         ...state,
@@ -214,7 +120,6 @@ const ContextProvider = (props) => {
   } = webcamDraggableState;
   const { children } = props;
   useEffect(() => {
-    Storage.setItem('webcamPlacement', placement);
     Storage.setItem('webcamLastPlacementLandscape', lastPlacementLandscape);
     Storage.setItem('webcamlastPlacementPortrait', lastPlacementPortrait);
     Storage.setItem('webcamLastPosition', lastPosition);
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index fa019c0176..1c316076af 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -12,8 +12,6 @@ import Button from '../button/component';
 import RecordingIndicator from './recording-indicator/container';
 import TalkingIndicatorContainer from '/imports/ui/components/nav-bar/talking-indicator/container';
 import SettingsDropdownContainer from './settings-dropdown/container';
-import { NAVBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager';
-
 
 const intlMessages = defineMessages({
   toggleUserListLabel: {
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
index 996bccb883..e24d55afd0 100755
--- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
@@ -84,18 +84,26 @@ class PresentationArea extends PureComponent {
   }
 
   componentDidMount() {
-    // adding an event listener to scale the whiteboard on 'resize' events sent by chat/userlist etc
-    // window.addEventListener('resize', this.onResize);
     this.getInitialPresentationSizes();
     this.refPresentationContainer.addEventListener('fullscreenchange', this.onFullscreenChange);
+    window.addEventListener('resize', this.onResize, false);
+    window.addEventListener('webcamAreaResize', this.onResize, false);
+    window.addEventListener('userListResizeChanged', this.onResize, false);
+    window.addEventListener('chatResizeChanged', this.onResize, false);
 
-    const { slidePosition, webcamDraggableDispatch } = this.props;
+    const { slidePosition, layoutContextDispatch } = this.props;
     const { width: currWidth, height: currHeight } = slidePosition;
     if (currWidth > currHeight || currWidth === currHeight) {
-      webcamDraggableDispatch({ type: 'setOrientationToLandscape' });
+      layoutContextDispatch({
+        type: 'setPresentationOrientation',
+        value: 'landscape',
+      });
     }
     if (currHeight > currWidth) {
-      webcamDraggableDispatch({ type: 'setOrientationToPortrait' });
+      layoutContextDispatch({
+        type: 'setPresentationOrientation',
+        value: 'portrait',
+      });
     }
   }
 
@@ -103,32 +111,40 @@ class PresentationArea extends PureComponent {
     const {
       currentPresentation,
       slidePosition,
-      webcamDraggableDispatch,
       layoutSwapped,
       currentSlide,
       publishedPoll,
       isViewer,
       toggleSwapLayout,
       restoreOnUpdate,
+      layoutContextDispatch,
       layoutContextState,
     } = this.props;
+
+    const { presentationAreaWidth, presentationAreaHeight } = this.state;
     const { presentationAreaSize } = layoutContextState;
     const { layoutContextState: prevLayoutContextState } = prevProps;
     const { presentationAreaSize: prevPresentationAreaSize } = prevLayoutContextState;
 
-    if (presentationAreaSize !== prevPresentationAreaSize) {
-      this.handleResize();
-    }
+    if (presentationAreaSize !== prevPresentationAreaSize
+      || presentationAreaSize.width !== presentationAreaWidth
+      || presentationAreaSize.height !== presentationAreaHeight) this.handleResize();
 
     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' });
+        layoutContextDispatch({
+          type: 'setPresentationOrientation',
+          value: 'landscape',
+        });
       }
       if (currHeight > currWidth) {
-        webcamDraggableDispatch({ type: 'setOrientationToPortrait' });
+        layoutContextDispatch({
+          type: 'setPresentationOrientation',
+          value: 'portrait',
+        });
       }
     }
 
@@ -157,7 +173,10 @@ class PresentationArea extends PureComponent {
   }
 
   componentWillUnmount() {
-    // window.removeEventListener('resize', this.onResize);
+    window.removeEventListener('resize', this.onResize, false);
+    window.removeEventListener('webcamAreaResize', this.onResize, false);
+    window.removeEventListener('userListResizeChanged', this.onResize, false);
+    window.removeEventListener('chatResizeChanged', this.onResize, false);
     this.refPresentationContainer.removeEventListener('fullscreenchange', this.onFullscreenChange);
   }
 
@@ -168,7 +187,6 @@ class PresentationArea extends PureComponent {
     if (isFullscreen !== newIsFullscreen) {
       this.setState({ isFullscreen: newIsFullscreen });
       layoutContextDispatch({ type: 'setPresentationFullscreen', value: newIsFullscreen });
-      // window.dispatchEvent(new Event('resize'));
     }
   }
 
@@ -189,29 +207,31 @@ class PresentationArea extends PureComponent {
   }
 
   getPresentationSizesAvailable() {
-    const { userIsPresenter, multiUser, layoutContextState } = this.props;
-    const { refPresentationArea, refWhiteboardArea } = this;
+    // const { userIsPresenter, multiUser } = this.props;
+    const { layoutContextState } = this.props;
+    // const { refPresentationArea, refWhiteboardArea } = this;
     const { presentationAreaSize } = layoutContextState;
 
     const presentationSizes = {};
 
-    if (refPresentationArea && refWhiteboardArea) {
-      // By default presentation sizes are equal to the sizes of the refPresentationArea
-      // direct parent of the svg wrapper
-      let { clientWidth, clientHeight } = refPresentationArea;
-
-      // if a user is a presenter - this means there is a whiteboard toolbar on the right
-      // and we have to get the width/height of the refWhiteboardArea
-      // (inner hidden div with absolute position)
-      if (userIsPresenter || multiUser) {
-        ({ clientWidth, clientHeight } = refWhiteboardArea);
-      }
-
-      presentationSizes.presentationAreaHeight = presentationAreaSize.height - this.getToolbarHeight();
-      presentationSizes.presentationAreaWidth = presentationAreaSize.width;
-      // presentationSizes.presentationAreaHeight = clientHeight - this.getToolbarHeight();
-      // presentationSizes.presentationAreaWidth = clientWidth;
-    }
+    // if (refPresentationArea && refWhiteboardArea) {
+    // By default presentation sizes are equal to the sizes of the refPresentationArea
+    // direct parent of the svg wrapper
+    // let { clientWidth, clientHeight } = refPresentationArea;
+
+    // if a user is a presenter - this means there is a whiteboard toolbar on the right
+    // and we have to get the width/height of the refWhiteboardArea
+    // (inner hidden div with absolute position)
+    // if (userIsPresenter || multiUser) {
+    //   ({ clientWidth, clientHeight } = refWhiteboardArea);
+    // }
+
+    presentationSizes.presentationAreaHeight = presentationAreaSize
+      .height - this.getToolbarHeight();
+    presentationSizes.presentationAreaWidth = presentationAreaSize.width;
+    // presentationSizes.presentationAreaHeight = clientHeight - this.getToolbarHeight();
+    // presentationSizes.presentationAreaWidth = clientWidth;
+    // }
     return presentationSizes;
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
index 910f89894a..8ebb5fee31 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
@@ -77,6 +77,10 @@ const propTypes = {
 };
 
 class VideoProvider extends Component {
+  static onBeforeUnload() {
+    VideoService.onBeforeUnload();
+  }
+
   constructor(props) {
     super(props);
 
@@ -101,8 +105,6 @@ class VideoProvider extends Component {
     this.onWsClose = this.onWsClose.bind(this);
     this.onWsMessage = this.onWsMessage.bind(this);
 
-    this.onBeforeUnload = this.onBeforeUnload.bind(this);
-
     this.updateStreams = this.updateStreams.bind(this);
   }
 
@@ -115,7 +117,7 @@ class VideoProvider extends Component {
 
     this.ws.onmessage = this.onWsMessage;
 
-    window.addEventListener('beforeunload', this.onBeforeUnload);
+    window.addEventListener('beforeunload', VideoProvider.onBeforeUnload);
   }
 
   componentDidUpdate(prevProps) {
@@ -134,7 +136,7 @@ class VideoProvider extends Component {
     window.removeEventListener('online', this.openWs);
     window.removeEventListener('offline', this.onWsClose);
 
-    window.removeEventListener('beforeunload', this.onBeforeUnload);
+    window.removeEventListener('beforeunload', VideoProvider.onBeforeUnload);
 
     VideoService.exitVideo();
 
@@ -205,17 +207,41 @@ class VideoProvider extends Component {
     this.setState({ socketOpen: true });
   }
 
-  onBeforeUnload() {
-    VideoService.onBeforeUnload();
+  setReconnectionTimeout(cameraId, isLocal) {
+    const peer = this.webRtcPeers[cameraId];
+    const peerHasStarted = peer && peer.started === true;
+    const shouldSetReconnectionTimeout = !this.restartTimeout[cameraId] && !peerHasStarted;
+
+    if (shouldSetReconnectionTimeout) {
+      const newReconnectTimer = this.restartTimer[cameraId] || CAMERA_SHARE_FAILED_WAIT_TIME;
+      this.restartTimer[cameraId] = newReconnectTimer;
+
+      logger.info({
+        logCode: 'video_provider_setup_reconnect',
+        extraInfo: {
+          cameraId,
+          reconnectTimer: newReconnectTimer,
+        },
+      }, `Camera has a new reconnect timer of ${newReconnectTimer} ms for ${cameraId}`);
+
+      this.restartTimeout[cameraId] = setTimeout(
+        this._getWebRTCStartTimeout(cameraId, isLocal),
+        this.restartTimer[cameraId],
+      );
+    }
   }
 
   updateStreams(streams) {
     const streamsCameraIds = streams.map(s => s.cameraId);
     const streamsConnected = Object.keys(this.webRtcPeers);
 
-    const streamsToConnect = streamsCameraIds.filter(cameraId => !streamsConnected.includes(cameraId));
+    const streamsToConnect = streamsCameraIds.filter(
+      cameraId => !streamsConnected.includes(cameraId),
+    );
 
-    const streamsToDisconnect = streamsConnected.filter(cameraId => !streamsCameraIds.includes(cameraId));
+    const streamsToDisconnect = streamsConnected.filter(
+      cameraId => !streamsCameraIds.includes(cameraId),
+    );
 
     streamsToConnect.forEach((cameraId) => {
       const isLocal = VideoService.isLocalStream(cameraId);
@@ -505,7 +531,8 @@ class VideoProvider extends Component {
       const peer = this.webRtcPeers[cameraId];
       if (peer && peer.peerConnection) {
         const conn = peer.peerConnection;
-        conn.oniceconnectionstatechange = this._getOnIceConnectionStateChangeCallback(cameraId, isLocal);
+        conn.oniceconnectionstatechange = this
+          ._getOnIceConnectionStateChangeCallback(cameraId, isLocal);
         VideoService.monitor(conn);
       }
     }
@@ -595,30 +622,6 @@ class VideoProvider extends Component {
     }, `Camera peer creation failed for ${cameraId} due to ${error.message}`);
   }
 
-  setReconnectionTimeout(cameraId, isLocal) {
-    const peer = this.webRtcPeers[cameraId];
-    const peerHasStarted = peer && peer.started === true;
-    const shouldSetReconnectionTimeout = !this.restartTimeout[cameraId] && !peerHasStarted;
-
-    if (shouldSetReconnectionTimeout) {
-      const newReconnectTimer = this.restartTimer[cameraId] || CAMERA_SHARE_FAILED_WAIT_TIME;
-      this.restartTimer[cameraId] = newReconnectTimer;
-
-      logger.info({
-        logCode: 'video_provider_setup_reconnect',
-        extraInfo: {
-          cameraId,
-          reconnectTimer: newReconnectTimer,
-        },
-      }, `Camera has a new reconnect timer of ${newReconnectTimer} ms for ${cameraId}`);
-
-      this.restartTimeout[cameraId] = setTimeout(
-        this._getWebRTCStartTimeout(cameraId, isLocal),
-        this.restartTimer[cameraId],
-      );
-    }
-  }
-
   _getOnIceCandidateCallback(cameraId, isLocal) {
     return (candidate) => {
       const peer = this.webRtcPeers[cameraId];
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx
index bfe4b551bc..844c74b0bc 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx
@@ -102,11 +102,17 @@ class VideoList extends Component {
 
     this.handleCanvasResize();
     window.addEventListener('resize', this.handleCanvasResize, false);
+    window.addEventListener('webcamAreaResize', this.handleCanvasResize, false);
+    window.addEventListener('userListResizeChanged', this.handleCanvasResize, false);
+    window.addEventListener('chatResizeChanged', this.handleCanvasResize, false);
     window.addEventListener('videoPlayFailed', this.handlePlayElementFailed);
   }
 
   componentWillUnmount() {
     window.removeEventListener('resize', this.handleCanvasResize, false);
+    window.removeEventListener('webcamAreaResize', this.handleCanvasResize, false);
+    window.removeEventListener('userListResizeChanged', this.handleCanvasResize, false);
+    window.removeEventListener('chatResizeChanged', this.handleCanvasResize, false);
     window.removeEventListener('videoPlayFailed', this.handlePlayElementFailed);
   }
 
-- 
GitLab