bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/layout/layout-manager/presentationFocusLayout.jsx
prlanzarin 6f13961018 fix(layout): set external-video/screen share initial states on all layouts
Only smart layout takes screen sharing/external video states in account
when populating its initial state. The others don't, and that causes
some weird issues when switching back-and-forth between layout types due
to their input states becoming inconsistent - ie having an active screen
sharing and transitioning from Smart -> Custom would mark it as false
(due to its absence from the initial state) and pollute its state for
subsequent layouts.

This commit guarantees those features are taken into account when
populating initial input states for Focus On*/Custom layouts.
2022-08-05 09:52:36 -03:00

468 lines
15 KiB
JavaScript

import { useEffect, useRef } from 'react';
import _ from 'lodash';
import { layoutDispatch, layoutSelect, layoutSelectInput } from '/imports/ui/components/layout/context';
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
import { INITIAL_INPUT_STATE } from '/imports/ui/components/layout/initState';
import {
ACTIONS,
PANELS,
CAMERADOCK_POSITION,
} from '/imports/ui/components/layout/enums';
const windowWidth = () => window.document.documentElement.clientWidth;
const windowHeight = () => window.document.documentElement.clientHeight;
const min = (value1, value2) => (value1 <= value2 ? value1 : value2);
const max = (value1, value2) => (value1 >= value2 ? value1 : value2);
const PresentationFocusLayout = (props) => {
const { bannerAreaHeight, isMobile } = props;
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const input = layoutSelect((i) => i.input);
const deviceType = layoutSelect((i) => i.deviceType);
const isRTL = layoutSelect((i) => i.isRTL);
const fullscreen = layoutSelect((i) => i.fullscreen);
const fontSize = layoutSelect((i) => i.fontSize);
const currentPanelType = layoutSelect((i) => i.currentPanelType);
const presentationInput = layoutSelectInput((i) => i.presentation);
const sidebarNavigationInput = layoutSelectInput((i) => i.sidebarNavigation);
const sidebarContentInput = layoutSelectInput((i) => i.sidebarContent);
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
const actionbarInput = layoutSelectInput((i) => i.actionBar);
const navbarInput = layoutSelectInput((i) => i.navBar);
const layoutContextDispatch = layoutDispatch();
const prevDeviceType = usePrevious(deviceType);
const throttledCalculatesLayout = _.throttle(() => calculatesLayout(),
50, { trailing: true, leading: true });
useEffect(() => {
window.addEventListener('resize', () => {
layoutContextDispatch({
type: ACTIONS.SET_BROWSER_SIZE,
value: {
width: window.document.documentElement.clientWidth,
height: window.document.documentElement.clientHeight,
},
});
});
}, []);
useEffect(() => {
if (deviceType === null) return;
if (deviceType !== prevDeviceType) {
// reset layout if deviceType changed
// not all options is supported in all devices
init();
} else {
throttledCalculatesLayout();
}
}, [input, deviceType, isRTL, fontSize, fullscreen]);
const init = () => {
if (isMobile) {
layoutContextDispatch({
type: ACTIONS.SET_LAYOUT_INPUT,
value: _.defaultsDeep({
sidebarNavigation: {
isOpen: false,
sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel,
},
sidebarContent: {
isOpen: false,
sidebarContentPanel: sidebarContentInput.sidebarContentPanel,
},
SidebarContentHorizontalResizer: {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
},
},
cameraDock: {
numCameras: cameraDockInput.numCameras,
},
externalVideo: {
hasExternalVideo: input.externalVideo.hasExternalVideo,
},
screenShare: {
hasScreenShare: input.screenShare.hasScreenShare,
},
}, INITIAL_INPUT_STATE),
});
} else {
const { sidebarContentPanel } = sidebarContentInput;
layoutContextDispatch({
type: ACTIONS.SET_LAYOUT_INPUT,
value: _.defaultsDeep({
sidebarNavigation: {
isOpen: input.sidebarNavigation.isOpen || sidebarContentPanel !== PANELS.NONE || false,
},
sidebarContent: {
isOpen: sidebarContentPanel !== PANELS.NONE,
sidebarContentPanel,
},
SidebarContentHorizontalResizer: {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
},
},
cameraDock: {
numCameras: cameraDockInput.numCameras,
},
}, INITIAL_INPUT_STATE),
});
}
throttledCalculatesLayout();
};
const calculatesSidebarContentHeight = () => {
const { isOpen } = presentationInput;
const {
navBarHeight,
sidebarContentMinHeight,
} = DEFAULT_VALUES;
let height = 0;
let minHeight = 0;
let maxHeight = 0;
if (sidebarContentInput.isOpen) {
if (isMobile) {
height = windowHeight() - navBarHeight - bannerAreaHeight();
minHeight = height;
maxHeight = height;
} else if (cameraDockInput.numCameras > 0 && isOpen) {
if (sidebarContentInput.height === 0) {
height = (windowHeight() * 0.75) - bannerAreaHeight();
} else {
height = min(max(sidebarContentInput.height, sidebarContentMinHeight),
windowHeight());
}
minHeight = windowHeight() * 0.25 - bannerAreaHeight();
maxHeight = windowHeight() * 0.75 - bannerAreaHeight();
} else {
height = windowHeight() - bannerAreaHeight();
minHeight = height;
maxHeight = height;
}
}
return {
height,
minHeight,
maxHeight,
};
};
const calculatesCameraDockBounds = (
mediaBounds,
mediaAreaBounds,
sidebarNavWidth,
sidebarContentWidth,
sidebarContentHeight,
) => {
const { baseCameraDockBounds } = props;
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
// do not proceed if using values from LayoutEngine
if (Object.keys(baseBounds).length > 0) {
return baseBounds;
}
const { cameraDockMinHeight } = DEFAULT_VALUES;
const cameraDockBounds = {};
let cameraDockHeight = 0;
if (isMobile) {
cameraDockBounds.top = mediaAreaBounds.top + mediaBounds.height;
cameraDockBounds.left = 0;
cameraDockBounds.right = 0;
cameraDockBounds.minWidth = mediaAreaBounds.width;
cameraDockBounds.width = mediaAreaBounds.width;
cameraDockBounds.maxWidth = mediaAreaBounds.width;
cameraDockBounds.minHeight = cameraDockMinHeight;
cameraDockBounds.height = mediaAreaBounds.height - mediaBounds.height;
cameraDockBounds.maxHeight = mediaAreaBounds.height - mediaBounds.height;
} else {
if (cameraDockInput.height === 0) {
cameraDockHeight = min(
max((windowHeight() - sidebarContentHeight), cameraDockMinHeight),
(windowHeight() - cameraDockMinHeight),
);
} else {
cameraDockHeight = min(
max(cameraDockInput.height, cameraDockMinHeight),
(windowHeight() - cameraDockMinHeight),
);
}
cameraDockBounds.top = windowHeight() - cameraDockHeight;
cameraDockBounds.left = !isRTL ? sidebarNavWidth : 0;
cameraDockBounds.right = isRTL ? sidebarNavWidth : 0;
cameraDockBounds.minWidth = sidebarContentWidth;
cameraDockBounds.width = sidebarContentWidth;
cameraDockBounds.maxWidth = sidebarContentWidth;
cameraDockBounds.minHeight = cameraDockMinHeight;
cameraDockBounds.height = cameraDockHeight;
cameraDockBounds.maxHeight = windowHeight() - sidebarContentHeight;
cameraDockBounds.zIndex = 1;
}
return cameraDockBounds;
};
const calculatesMediaBounds = (mediaAreaBounds, sidebarSize) => {
const mediaBounds = {};
const { element: fullscreenElement } = fullscreen;
if (fullscreenElement === 'Presentation' || fullscreenElement === 'Screenshare' || fullscreenElement === 'ExternalVideo') {
mediaBounds.width = windowWidth();
mediaBounds.height = windowHeight();
mediaBounds.top = 0;
mediaBounds.left = !isRTL ? 0 : null;
mediaBounds.right = isRTL ? 0 : null;
mediaBounds.zIndex = 99;
return mediaBounds;
}
if (isMobile && cameraDockInput.numCameras > 0) {
mediaBounds.height = mediaAreaBounds.height * 0.7;
} else {
mediaBounds.height = mediaAreaBounds.height;
}
mediaBounds.width = mediaAreaBounds.width;
mediaBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
mediaBounds.left = !isRTL ? mediaAreaBounds.left : null;
mediaBounds.right = isRTL ? sidebarSize : null;
mediaBounds.zIndex = 1;
return mediaBounds;
};
const calculatesLayout = () => {
const {
calculatesNavbarBounds,
calculatesActionbarBounds,
calculatesSidebarNavWidth,
calculatesSidebarNavHeight,
calculatesSidebarNavBounds,
calculatesSidebarContentWidth,
calculatesSidebarContentBounds,
calculatesMediaAreaBounds,
isTablet,
} = props;
const { captionsMargin } = DEFAULT_VALUES;
const sidebarNavWidth = calculatesSidebarNavWidth();
const sidebarNavHeight = calculatesSidebarNavHeight();
const sidebarContentWidth = calculatesSidebarContentWidth();
const sidebarNavBounds = calculatesSidebarNavBounds();
const sidebarContentBounds = calculatesSidebarContentBounds(sidebarNavWidth.width);
const mediaAreaBounds = calculatesMediaAreaBounds(
sidebarNavWidth.width, sidebarContentWidth.width,
);
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
const sidebarSize = sidebarContentWidth.width + sidebarNavWidth.width;
const mediaBounds = calculatesMediaBounds(mediaAreaBounds, sidebarSize);
const sidebarContentHeight = calculatesSidebarContentHeight();
const cameraDockBounds = calculatesCameraDockBounds(
mediaBounds,
mediaAreaBounds,
sidebarNavWidth.width,
sidebarContentWidth.width,
sidebarContentHeight.height,
);
const { isOpen } = presentationInput;
layoutContextDispatch({
type: ACTIONS.SET_NAVBAR_OUTPUT,
value: {
display: navbarInput.hasNavBar,
width: navbarBounds.width,
height: navbarBounds.height,
top: navbarBounds.top,
left: navbarBounds.left,
tabOrder: DEFAULT_VALUES.navBarTabOrder,
zIndex: navbarBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
value: {
display: actionbarInput.hasActionBar,
width: actionbarBounds.width,
height: actionbarBounds.height,
innerHeight: actionbarBounds.innerHeight,
top: actionbarBounds.top,
left: actionbarBounds.left,
padding: actionbarBounds.padding,
tabOrder: DEFAULT_VALUES.actionBarTabOrder,
zIndex: actionbarBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_CAPTIONS_OUTPUT,
value: {
left: !isRTL ? (sidebarSize + captionsMargin) : null,
right: isRTL ? (sidebarSize + captionsMargin) : null,
maxWidth: mediaAreaBounds.width - (captionsMargin * 2),
},
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
value: {
display: sidebarNavigationInput.isOpen,
minWidth: sidebarNavWidth.minWidth,
width: sidebarNavWidth.width,
maxWidth: sidebarNavWidth.maxWidth,
height: sidebarNavHeight,
top: sidebarNavBounds.top,
left: sidebarNavBounds.left,
right: sidebarNavBounds.right,
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
isResizable: !isMobile && !isTablet,
zIndex: sidebarNavBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_RESIZABLE_EDGE,
value: {
top: false,
right: !isRTL,
bottom: false,
left: isRTL,
},
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
value: {
display: sidebarContentInput.isOpen,
minWidth: sidebarContentWidth.minWidth,
width: sidebarContentWidth.width,
maxWidth: sidebarContentWidth.maxWidth,
minHeight: sidebarContentHeight.minHeight,
height: sidebarContentHeight.height,
maxHeight: sidebarContentHeight.maxHeight,
top: sidebarContentBounds.top,
left: sidebarContentBounds.left,
right: sidebarContentBounds.right,
currentPanelType,
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
isResizable: !isMobile && !isTablet,
zIndex: sidebarContentBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_RESIZABLE_EDGE,
value: {
top: false,
right: !isRTL,
bottom: cameraDockInput.numCameras > 0,
left: isRTL,
},
});
layoutContextDispatch({
type: ACTIONS.SET_MEDIA_AREA_SIZE,
value: {
width: mediaAreaBounds.width,
height: mediaAreaBounds.height,
},
});
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
value: {
display: cameraDockInput.numCameras > 0,
position: CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM,
minWidth: cameraDockBounds.minWidth,
width: cameraDockBounds.width,
maxWidth: cameraDockBounds.maxWidth,
minHeight: cameraDockBounds.minHeight,
height: cameraDockBounds.height,
maxHeight: cameraDockBounds.maxHeight,
top: cameraDockBounds.top,
left: cameraDockBounds.left,
right: cameraDockBounds.right,
tabOrder: 4,
isDraggable: false,
resizableEdge: {
top: false,
right: false,
bottom: false,
left: false,
},
zIndex: cameraDockBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_PRESENTATION_OUTPUT,
value: {
display: presentationInput.isOpen,
width: mediaBounds.width,
height: mediaBounds.height,
top: mediaBounds.top,
left: mediaBounds.left,
right: isRTL ? mediaBounds.right : null,
tabOrder: DEFAULT_VALUES.presentationTabOrder,
isResizable: false,
zIndex: mediaBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_SCREEN_SHARE_OUTPUT,
value: {
width: mediaBounds.width,
height: isOpen ? mediaBounds.height : 0,
top: mediaBounds.top,
left: mediaBounds.left,
right: mediaBounds.right,
zIndex: mediaBounds.zIndex,
},
});
layoutContextDispatch({
type: ACTIONS.SET_EXTERNAL_VIDEO_OUTPUT,
value: {
width: isOpen ? mediaBounds.width : 0,
height: isOpen ? mediaBounds.height : 0,
top: mediaBounds.top,
left: mediaBounds.left,
right: mediaBounds.right,
},
});
};
return null;
};
export default PresentationFocusLayout;