diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx
index f67e33d967..c4c7c4260c 100755
--- a/bigbluebutton-html5/imports/ui/components/app/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx
@@ -30,12 +30,11 @@ import PresentationAreaContainer from '../presentation/presentation-area/contain
import ScreenshareContainer from '../screenshare/container';
import ExternalVideoContainer from '../external-video-player/container';
import Styled from './styles';
-import { LAYOUT_TYPE, DEVICE_TYPE, ACTIONS, SMALL_VIEWPORT_BREAKPOINT } from '../layout/enums';
+import { DEVICE_TYPE, ACTIONS, SMALL_VIEWPORT_BREAKPOINT } from '../layout/enums';
import {
isMobile, isTablet, isTabletPortrait, isTabletLandscape, isDesktop,
} from '../layout/utils';
import LayoutEngine from '../layout/layout-manager/layoutEngine';
-import getFromUserSettings from '/imports/ui/services/users-settings';
import NavBarContainer from '../nav-bar/container';
import SidebarNavigationContainer from '../sidebar-navigation/container';
import SidebarContentContainer from '../sidebar-content/container';
@@ -46,24 +45,16 @@ import Settings from '/imports/ui/services/settings';
import { registerTitleView } from '/imports/utils/dom-utils';
import Notifications from '../notifications/container';
import GlobalStyles from '/imports/ui/stylesheets/styled-components/globalStyles';
-import MediaService from '/imports/ui/components/media/service';
import ActionsBarContainer from '../actions-bar/container';
-import { updateSettings } from '/imports/ui/components/settings/service';
+import PushLayoutEngine from '../layout/push-layout/pushLayoutEngine';
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 OVERRIDE_LOCALE = APP_CONFIG.defaultSettings.application.overrideLocale;
-const HIDE_PRESENTATION = Meteor.settings.public.layout.hidePresentation;
const LAYOUT_CONFIG = Meteor.settings.public.layout;
-const equalDouble = (n1, n2) => {
- const precision = 0.01;
-
- return Math.abs(n1 - n2) <= precision;
-};
-
const intlMessages = defineMessages({
userListLabel: {
id: 'app.userList.label',
@@ -159,18 +150,7 @@ class App extends Component {
intl,
validIOSVersion,
layoutContextDispatch,
- meetingLayout,
- settingsLayout,
- cameraWidth,
- cameraHeight,
- layoutPresOpen,
- layoutCamPosition,
- layoutFocusedCam,
- layoutRate,
- horizontalPosition,
isRTL,
- shouldShowScreenshare,
- shouldShowExternalVideo,
} = this.props;
const { browserName } = browserInfo;
const { osName } = deviceInfo;
@@ -193,57 +173,6 @@ class App extends Component {
value: parseInt(fontSize.slice(0, -2), 10),
});
- const userLayout = LAYOUT_TYPE[getFromUserSettings('bbb_change_layout', false)];
- Settings.application.selectedLayout = settingsLayout
- || userLayout
- || meetingLayout;
-
- let selectedLayout = Settings.application.selectedLayout;
- if (isMobile()) {
- selectedLayout = selectedLayout === 'custom' ? 'smart' : selectedLayout;
- Settings.application.selectedLayout = selectedLayout;
- }
- Settings.save();
-
- const initialPresentation = !getFromUserSettings('bbb_hide_presentation', HIDE_PRESENTATION || !layoutPresOpen) || shouldShowScreenshare || shouldShowExternalVideo;
- MediaService.setPresentationIsOpen(layoutContextDispatch, initialPresentation);
-
- if (selectedLayout === 'custom') {
- setTimeout(() => {
-
- layoutContextDispatch({
- type: ACTIONS.SET_FOCUSED_CAMERA_ID,
- value: layoutFocusedCam,
- });
-
- layoutContextDispatch({
- type: ACTIONS.SET_CAMERA_DOCK_POSITION,
- value: layoutCamPosition,
- });
-
- if (!equalDouble(layoutRate, 0)) {
- let w, h;
- if (horizontalPosition) {
- w = window.innerWidth * layoutRate;
- h = cameraHeight;
- } else {
- w = cameraWidth;
- h = window.innerHeight * layoutRate;
- }
-
- layoutContextDispatch({
- type: ACTIONS.SET_CAMERA_DOCK_SIZE,
- value: {
- width: w,
- height: h,
- browserWidth: window.innerWidth,
- browserHeight: window.innerHeight,
- }
- });
- }
- }, 0);
- }
-
const body = document.getElementsByTagName('body')[0];
if (browserName) {
@@ -286,146 +215,11 @@ class App extends Component {
intl,
mountModal,
deviceType,
- meetingLayout,
- meetingLayoutUpdatedAt,
- presentationIsOpen,
- focusedCamera,
- cameraPosition,
- presentationVideoRate,
- cameraWidth,
- cameraHeight,
- cameraIsResizing,
- isPresenter,
- isModerator,
- layoutPresOpen,
- layoutIsResizing,
- layoutCamPosition,
- layoutFocusedCam,
- layoutRate,
- horizontalPosition,
- selectedLayout, // layout name
- pushLayout, // is layout pushed
- pushLayoutMeeting,
- layoutContextDispatch,
mountRandomUserModal,
- setPushLayout,
- setMeetingLayout,
} = this.props;
this.renderDarkMode();
- const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
- const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
- const shouldSwitchLayout = isPresenter
- ? meetingLayoutDidChange
- : (meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting;
-
- if (shouldSwitchLayout) {
-
- let contextLayout = meetingLayout;
- if (isMobile()) {
- contextLayout = meetingLayout === 'custom' ? 'smart' : meetingLayout;
- }
-
- layoutContextDispatch({
- type: ACTIONS.SET_LAYOUT_TYPE,
- value: contextLayout,
- });
-
- updateSettings({
- application: {
- ...Settings.application,
- selectedLayout: contextLayout,
- },
- });
- }
-
- if (pushLayoutMeetingDidChange) {
- updateSettings({
- application: {
- ...Settings.application,
- pushLayout: pushLayoutMeeting,
- },
- });
- }
-
- if (meetingLayout === "custom" && !isPresenter) {
-
- if (layoutFocusedCam !== prevProps.layoutFocusedCam
- || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
-
- layoutContextDispatch({
- type: ACTIONS.SET_FOCUSED_CAMERA_ID,
- value: layoutFocusedCam,
- });
- }
-
- if (layoutCamPosition !== prevProps.layoutCamPosition
- || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
-
- layoutContextDispatch({
- type: ACTIONS.SET_CAMERA_DOCK_POSITION,
- value: layoutCamPosition,
- });
- }
-
- if (!equalDouble(layoutRate, prevProps.layoutRate)
- || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
-
- let w, h;
- if (horizontalPosition) {
- w = window.innerWidth * layoutRate;
- h = cameraHeight;
- } else {
- w = cameraWidth;
- h = window.innerHeight * layoutRate;
- }
-
- if (layoutIsResizing !== prevProps.layoutIsResizing) {
- layoutContextDispatch({
- type: ACTIONS.SET_CAMERA_DOCK_IS_RESIZING,
- value: layoutIsResizing,
- });
- }
-
- layoutContextDispatch({
- type: ACTIONS.SET_CAMERA_DOCK_SIZE,
- value: {
- width: w,
- height: h,
- browserWidth: window.innerWidth,
- browserHeight: window.innerHeight,
- }
- });
- }
-
- if (layoutPresOpen !== prevProps.layoutPresOpen
- || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
-
- layoutContextDispatch({
- type: ACTIONS.SET_PRESENTATION_IS_OPEN,
- value: layoutPresOpen,
- });
- }
- }
-
- const layoutChanged = presentationIsOpen !== prevProps.presentationIsOpen
- || selectedLayout !== prevProps.selectedLayout
- || cameraIsResizing !== prevProps.cameraIsResizing
- || cameraPosition !== prevProps.cameraPosition
- || focusedCamera !== prevProps.focusedCamera
- || !equalDouble(presentationVideoRate, prevProps.presentationVideoRate);
-
- if ((pushLayout && layoutChanged) // change layout sizes / states
- || (pushLayout !== prevProps.pushLayout) // push layout once after presenter toggles / special case where we set pushLayout to false in all viewers
- ) {
- if (isPresenter) {
- setMeetingLayout();
- } else if (isModerator) {
- setPushLayout();
- }
- }
-
if (mountRandomUserModal) mountModal();
if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) {
@@ -623,6 +417,68 @@ class App extends Component {
: DarkReader.disable();
}
+ mountPushLayoutEngine() {
+ const {
+ cameraWidth,
+ cameraHeight,
+ cameraIsResizing,
+ cameraPosition,
+ focusedCamera,
+ horizontalPosition,
+ isLayoutMeetingResizing,
+ isPresenter,
+ isModerator,
+ layoutContextDispatch,
+ meetingLayout,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
+ meetingPresentationIsOpen,
+ meetingLayoutUpdatedAt,
+ presentationIsOpen,
+ presentationVideoRate,
+ pushLayout,
+ pushLayoutMeeting,
+ selectedLayout,
+ setPushLayout,
+ setMeetingLayout,
+ shouldShowScreenshare,
+ shouldShowExternalVideo,
+ } = this.props;
+
+ return (
+
+ );
+ }
+
render() {
const {
customStyle,
@@ -641,6 +497,7 @@ class App extends Component {
<>
+ {this.mountPushLayoutEngine()}
{
const {
actionsbar,
- meetingLayout,
- meetingLayoutUpdatedAt,
selectedLayout,
pushLayout,
pushLayoutMeeting,
@@ -72,11 +70,13 @@ const AppContainer = (props) => {
isPresenter,
randomlySelectedUser,
isModalOpen,
- presentationIsOpen: layoutPresOpen,
- isResizing: layoutIsResizing,
- cameraPosition: layoutCamPosition,
- focusedCamera: layoutFocusedCam,
- presentationVideoRate: layoutRate,
+ meetingLayout,
+ meetingLayoutUpdatedAt,
+ meetingPresentationIsOpen,
+ isLayoutMeetingResizing,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
...otherProps
} = props;
@@ -87,7 +87,6 @@ const AppContainer = (props) => {
const cameraDock = layoutSelectOutput((i) => i.cameraDock);
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
const presentation = layoutSelectInput((i) => i.presentation);
- const layoutType = layoutSelect((i) => i.layoutType);
const deviceType = layoutSelect((i) => i.deviceType);
const layoutContextDispatch = layoutDispatch();
@@ -154,11 +153,11 @@ const AppContainer = (props) => {
cameraWidth: cameraDock.width,
cameraHeight: cameraDock.height,
cameraIsResizing: cameraDockInput.isResizing,
- layoutPresOpen,
- layoutIsResizing,
- layoutCamPosition,
- layoutFocusedCam,
- layoutRate,
+ meetingPresentationIsOpen,
+ isLayoutMeetingResizing,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
horizontalPosition,
deviceType,
layoutContextDispatch,
@@ -222,8 +221,17 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
randomlySelectedUser,
} = currentMeeting;
- const meetingLayout = LayoutMeetings.findOne({ meetingId: Auth.meetingID }) || {};
- const { layout, pushLayout: pushLayoutMeeting, layoutUpdatedAt, presentationIsOpen, isResizing, cameraPosition, focusedCamera, presentationVideoRate } = meetingLayout;
+ const meetingLayoutObj = LayoutMeetings.findOne({ meetingId: Auth.meetingID }) || {};
+ const {
+ layout: meetingLayout,
+ pushLayout: pushLayoutMeeting,
+ layoutUpdatedAt: meetingLayoutUpdatedAt,
+ presentationIsOpen: meetingPresentationIsOpen,
+ isResizing: isMeetingLayoutResizing,
+ cameraPosition: meetingLayoutCameraPosition,
+ focusedCamera: meetingLayoutFocusedCamera,
+ presentationVideoRate: meetingLayoutVideoRate,
+ } = meetingLayoutObj;
if (currentUser && !currentUser.approved) {
baseControls.updateLoadingState(intl.formatMessage(intlMessages.waitingApprovalMessage));
@@ -267,13 +275,13 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
currentUserId: currentUser?.userId,
isPresenter,
isModerator: currentUser?.role === ROLE_MODERATOR,
- meetingLayout: layout,
- meetingLayoutUpdatedAt: layoutUpdatedAt,
- presentationIsOpen,
- isResizing,
- cameraPosition,
- focusedCamera,
- presentationVideoRate,
+ meetingLayout,
+ meetingLayoutUpdatedAt,
+ meetingPresentationIsOpen,
+ isMeetingLayoutResizing,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
selectedLayout,
pushLayout,
pushLayoutMeeting,
diff --git a/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx b/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx
new file mode 100644
index 0000000000..ae95d1fb3c
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx
@@ -0,0 +1,263 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Meteor } from 'meteor/meteor';
+import getFromUserSettings from '/imports/ui/services/users-settings';
+import Settings from '/imports/ui/services/settings';
+import MediaService from '/imports/ui/components/media/service';
+import { LAYOUT_TYPE, ACTIONS } from '../enums';
+import { isMobile } from '../utils';
+import { updateSettings } from '/imports/ui/components/settings/service';
+
+const HIDE_PRESENTATION = Meteor.settings.public.layout.hidePresentation;
+
+const equalDouble = (n1, n2) => {
+ const precision = 0.01;
+
+ return Math.abs(n1 - n2) <= precision;
+};
+
+const propTypes = {
+ cameraWidth: PropTypes.number,
+ cameraHeight: PropTypes.number,
+ cameraIsResizing: PropTypes.bool,
+ cameraPosition: PropTypes.string,
+ focusedCamera: PropTypes.string,
+ horizontalPosition: PropTypes.string,
+ isLayoutMeetingResizing: PropTypes.bool,
+ isPresenter: PropTypes.bool,
+ isModerator: PropTypes.bool,
+ layoutContextDispatch: PropTypes.func,
+ meetingLayout: PropTypes.string,
+ meetingLayoutCameraPosition: PropTypes.string,
+ meetingLayoutFocusedCamera: PropTypes.string,
+ meetingLayoutVideoRate: PropTypes.number,
+ meetingPresentationIsOpen: PropTypes.bool,
+ meetingLayoutUpdatedAt: PropTypes.number,
+ presentationIsOpen: PropTypes.bool,
+ presentationVideoRate: PropTypes.number,
+ pushLayout: PropTypes.bool,
+ pushLayoutMeeting: PropTypes.bool,
+ selectedLayout: PropTypes.string,
+ setPushLayout: PropTypes.func,
+ setMeetingLayout: PropTypes.func,
+ shouldShowScreenshare: PropTypes.bool,
+ shouldShowExternalVideo: PropTypes.bool,
+};
+
+class PushLayoutEngine extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ componentDidMount() {
+ const {
+ cameraWidth,
+ cameraHeight,
+ horizontalPosition,
+ layoutContextDispatch,
+ meetingLayout,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
+ meetingPresentationIsOpen,
+ shouldShowScreenshare,
+ shouldShowExternalVideo,
+ } = this.props;
+
+ const userLayout = LAYOUT_TYPE[getFromUserSettings('bbb_change_layout', false)];
+ Settings.application.selectedLayout = userLayout || meetingLayout;
+
+ let selectedLayout = Settings.application.selectedLayout;
+ if (isMobile()) {
+ selectedLayout = selectedLayout === 'custom' ? 'smart' : selectedLayout;
+ Settings.application.selectedLayout = selectedLayout;
+ }
+ Settings.save();
+
+ const initialPresentation = !getFromUserSettings('bbb_hide_presentation', HIDE_PRESENTATION || !meetingPresentationIsOpen) || shouldShowScreenshare || shouldShowExternalVideo;
+ MediaService.setPresentationIsOpen(layoutContextDispatch, initialPresentation);
+
+ if (selectedLayout === 'custom') {
+ setTimeout(() => {
+ layoutContextDispatch({
+ type: ACTIONS.SET_FOCUSED_CAMERA_ID,
+ value: meetingLayoutFocusedCamera,
+ });
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_CAMERA_DOCK_POSITION,
+ value: meetingLayoutCameraPosition,
+ });
+
+ if (!equalDouble(meetingLayoutVideoRate, 0)) {
+ let w, h;
+ if (horizontalPosition) {
+ w = window.innerWidth * meetingLayoutVideoRate;
+ h = cameraHeight;
+ } else {
+ w = cameraWidth;
+ h = window.innerHeight * meetingLayoutVideoRate;
+ }
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_CAMERA_DOCK_SIZE,
+ value: {
+ width: w,
+ height: h,
+ browserWidth: window.innerWidth,
+ browserHeight: window.innerHeight,
+ }
+ });
+ }
+ }, 0);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const {
+ cameraWidth,
+ cameraHeight,
+ cameraIsResizing,
+ cameraPosition,
+ focusedCamera,
+ horizontalPosition,
+ isLayoutMeetingResizing,
+ isModerator,
+ isPresenter,
+ layoutContextDispatch,
+ meetingLayout,
+ meetingLayoutUpdatedAt,
+ meetingPresentationIsOpen,
+ meetingLayoutCameraPosition,
+ meetingLayoutFocusedCamera,
+ meetingLayoutVideoRate,
+ presentationIsOpen,
+ presentationVideoRate,
+ pushLayout,
+ pushLayoutMeeting,
+ selectedLayout,
+ setMeetingLayout,
+ setPushLayout,
+ } = this.props;
+
+ const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
+ const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
+ const shouldSwitchLayout = isPresenter
+ ? meetingLayoutDidChange
+ : (meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting;
+
+ if (shouldSwitchLayout) {
+
+ let contextLayout = meetingLayout;
+ if (isMobile()) {
+ contextLayout = meetingLayout === 'custom' ? 'smart' : meetingLayout;
+ }
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_LAYOUT_TYPE,
+ value: contextLayout,
+ });
+
+ updateSettings({
+ application: {
+ ...Settings.application,
+ selectedLayout: contextLayout,
+ },
+ });
+ }
+
+ if (pushLayoutMeetingDidChange) {
+ updateSettings({
+ application: {
+ ...Settings.application,
+ pushLayout: pushLayoutMeeting,
+ },
+ });
+ }
+
+ if (meetingLayout === "custom" && !isPresenter) {
+
+ if (meetingLayoutFocusedCamera !== prevProps.meetingLayoutFocusedCamera
+ || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_FOCUSED_CAMERA_ID,
+ value: meetingLayoutFocusedCamera,
+ });
+ }
+
+ if (meetingLayoutCameraPosition !== prevProps.meetingLayoutCameraPosition
+ || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_CAMERA_DOCK_POSITION,
+ value: meetingLayoutCameraPosition,
+ });
+ }
+
+ if (!equalDouble(meetingLayoutVideoRate, prevProps.meetingLayoutVideoRate)
+ || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
+
+ let w, h;
+ if (horizontalPosition) {
+ w = window.innerWidth * meetingLayoutVideoRate;
+ h = cameraHeight;
+ } else {
+ w = cameraWidth;
+ h = window.innerHeight * meetingLayoutVideoRate;
+ }
+
+ if (isLayoutMeetingResizing !== prevProps.isLayoutMeetingResizing) {
+ layoutContextDispatch({
+ type: ACTIONS.SET_CAMERA_DOCK_IS_RESIZING,
+ value: isLayoutMeetingResizing,
+ });
+ }
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_CAMERA_DOCK_SIZE,
+ value: {
+ width: w,
+ height: h,
+ browserWidth: window.innerWidth,
+ browserHeight: window.innerHeight,
+ }
+ });
+ }
+
+ if (meetingPresentationIsOpen !== prevProps.meetingPresentationIsOpen
+ || meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
+
+ layoutContextDispatch({
+ type: ACTIONS.SET_PRESENTATION_IS_OPEN,
+ value: meetingPresentationIsOpen,
+ });
+ }
+ }
+
+ const layoutChanged = presentationIsOpen !== prevProps.presentationIsOpen
+ || selectedLayout !== prevProps.selectedLayout
+ || cameraIsResizing !== prevProps.cameraIsResizing
+ || cameraPosition !== prevProps.cameraPosition
+ || focusedCamera !== prevProps.focusedCamera
+ || !equalDouble(presentationVideoRate, prevProps.presentationVideoRate);
+
+ if ((pushLayout && layoutChanged) // change layout sizes / states
+ || (pushLayout !== prevProps.pushLayout) // push layout once after presenter toggles / special case where we set pushLayout to false in all viewers
+ ) {
+ if (isPresenter) {
+ setMeetingLayout();
+ } else if (isModerator) {
+ setPushLayout();
+ }
+ }
+ }
+
+ render() {
+ return null;
+ }
+};
+
+PushLayoutEngine.propTypes = propTypes;
+
+export default PushLayoutEngine;