Merge pull request #21409 from Arthurk12/layout_media_only
feat(layout): new hidden layout type: `MEDIA_ONLY`
This commit is contained in:
commit
c79637f21b
@ -6,6 +6,7 @@ export const LAYOUT_TYPE = {
|
||||
CAMERAS_ONLY: 'camerasOnly',
|
||||
PRESENTATION_ONLY: 'presentationOnly',
|
||||
PARTICIPANTS_AND_CHAT_ONLY: 'participantsAndChatOnly',
|
||||
MEDIA_ONLY: 'mediaOnly',
|
||||
};
|
||||
|
||||
export const DEVICE_TYPE = {
|
||||
@ -18,6 +19,8 @@ export const DEVICE_TYPE = {
|
||||
|
||||
export const SMALL_VIEWPORT_BREAKPOINT = 640;
|
||||
|
||||
export const MEDIA_ONLY_LAYOUT_MARGIN = 10;
|
||||
|
||||
export const CAMERADOCK_POSITION = {
|
||||
CONTENT_TOP: 'contentTop',
|
||||
CONTENT_RIGHT: 'contentRight',
|
||||
@ -31,8 +34,22 @@ export const HIDDEN_LAYOUTS = [
|
||||
LAYOUT_TYPE.CAMERAS_ONLY,
|
||||
LAYOUT_TYPE.PRESENTATION_ONLY,
|
||||
LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY,
|
||||
LAYOUT_TYPE.MEDIA_ONLY,
|
||||
];
|
||||
|
||||
export const LAYOUT_ELEMENTS = {
|
||||
LAYOUT_TYPE: 'layoutType',
|
||||
PRESENTATION_STATE: 'presentationState',
|
||||
FOCUSED_CAMERA: 'focusedCamera',
|
||||
CAMERA_DOCK_SIZE: 'cameraDockSize',
|
||||
CAMERA_DOCK_POSITION: 'cameradockPosition',
|
||||
};
|
||||
|
||||
export const SYNC = {
|
||||
PROPAGATE_ELEMENTS: 'propagateElements',
|
||||
REPLICATE_ELEMENTS: 'replicateElements',
|
||||
};
|
||||
|
||||
export const ACTIONS = {
|
||||
SET_AUTO_ARRANGE_LAYOUT: 'setAutoArrangeLayout',
|
||||
SET_IS_RTL: 'setIsRTL',
|
||||
|
@ -14,6 +14,7 @@ import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
|
||||
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
|
||||
import { SETTINGS } from '/imports/ui/services/settings/enums';
|
||||
import { useIsPresentationEnabled } from '/imports/ui/services/features';
|
||||
import MediaOnlyLayout from './mediaOnlyLayout';
|
||||
|
||||
const LayoutEngine = () => {
|
||||
const bannerBarInput = layoutSelectInput((i) => i.bannerBar);
|
||||
@ -70,7 +71,6 @@ const LayoutEngine = () => {
|
||||
return cameraDockBounds;
|
||||
}
|
||||
|
||||
const navBarHeight = calculatesNavbarHeight();
|
||||
const hasPresentation = isPresentationEnabled && slidesLength !== 0;
|
||||
const isGeneralMediaOff = !hasPresentation
|
||||
&& !hasExternalVideo && !hasScreenShare
|
||||
@ -79,9 +79,9 @@ const LayoutEngine = () => {
|
||||
if (!isOpen || isGeneralMediaOff) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height - bannerAreaHeight();
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.top = navBarHeight + bannerAreaHeight();
|
||||
cameraDockBounds.top = mediaAreaBounds.top;
|
||||
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : 0;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
@ -296,7 +296,7 @@ const LayoutEngine = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesMediaAreaBounds = (sidebarNavWidth, sidebarContentWidth) => {
|
||||
const calculatesMediaAreaBounds = (sidebarNavWidth, sidebarContentWidth, margin = 0) => {
|
||||
const { height: actionBarHeight } = calculatesActionbarHeight();
|
||||
const navBarHeight = calculatesNavbarHeight();
|
||||
|
||||
@ -310,10 +310,10 @@ const LayoutEngine = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + bannerAreaHeight()),
|
||||
top: navBarHeight + bannerAreaHeight(),
|
||||
left,
|
||||
width: width - (2 * margin),
|
||||
height: windowHeight() - (navBarHeight + actionBarHeight + bannerAreaHeight() + (2 * margin)),
|
||||
top: navBarHeight + bannerAreaHeight() + margin,
|
||||
left: left + margin,
|
||||
};
|
||||
};
|
||||
|
||||
@ -358,6 +358,9 @@ const LayoutEngine = () => {
|
||||
case LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY:
|
||||
layout?.setAttribute('data-layout', LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY);
|
||||
return <ParticipantsAndChatOnlyLayout {...common} />;
|
||||
case LAYOUT_TYPE.MEDIA_ONLY:
|
||||
layout?.setAttribute('data-layout', LAYOUT_TYPE.MEDIA_ONLY);
|
||||
return <MediaOnlyLayout {...common} isPresentationEnabled={isPresentationEnabled} />;
|
||||
default:
|
||||
layout?.setAttribute('data-layout', LAYOUT_TYPE.CUSTOM_LAYOUT);
|
||||
return <CustomLayout {...common} isPresentationEnabled={isPresentationEnabled} />;
|
||||
|
@ -0,0 +1,525 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { throttle } from '/imports/utils/throttle';
|
||||
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, CAMERADOCK_POSITION, MEDIA_ONLY_LAYOUT_MARGIN } from '/imports/ui/components/layout/enums';
|
||||
import { defaultsDeep } from '/imports/utils/array-utils';
|
||||
import Session from '/imports/ui/services/storage/in-memory';
|
||||
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
|
||||
|
||||
const windowWidth = () => window.document.documentElement.clientWidth;
|
||||
const windowHeight = () => window.document.documentElement.clientHeight;
|
||||
|
||||
const MediaOnlyLayout = (props) => {
|
||||
const { 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 Settings = getSettingsSingletonInstance();
|
||||
const { isRTL } = Settings.application;
|
||||
const fullscreen = layoutSelect((i) => i.fullscreen);
|
||||
const fontSize = layoutSelect((i) => i.fontSize);
|
||||
const currentPanelType = layoutSelect((i) => i.currentPanelType);
|
||||
|
||||
const navbarInput = layoutSelectInput((i) => i.navBar);
|
||||
const presentationInput = layoutSelectInput((i) => i.presentation);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const actionbarInput = layoutSelectInput((i) => i.actionBar);
|
||||
const externalVideoInput = layoutSelectInput((i) => i.externalVideo);
|
||||
const genericMainContentInput = layoutSelectInput((i) => i.genericMainContent);
|
||||
const screenShareInput = layoutSelectInput((i) => i.screenShare);
|
||||
const sharedNotesInput = layoutSelectInput((i) => i.sharedNotes);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
const prevDeviceType = usePrevious(deviceType);
|
||||
const { isPresentationEnabled } = props;
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_BROWSER_SIZE,
|
||||
value: {
|
||||
width: window.document.documentElement.clientWidth,
|
||||
height: window.document.documentElement.clientHeight,
|
||||
},
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const calculatesSlideSize = (mediaAreaBounds) => {
|
||||
const { currentSlide } = presentationInput;
|
||||
|
||||
if (currentSlide.size.width === 0 && currentSlide.size.height === 0) {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let slideWidth;
|
||||
let slideHeight;
|
||||
|
||||
slideWidth = (currentSlide.size.width * mediaAreaBounds.height) / currentSlide.size.height;
|
||||
slideHeight = mediaAreaBounds.height;
|
||||
|
||||
if (slideWidth > mediaAreaBounds.width) {
|
||||
slideWidth = mediaAreaBounds.width;
|
||||
slideHeight = (currentSlide.size.height * mediaAreaBounds.width) / currentSlide.size.width;
|
||||
}
|
||||
|
||||
return {
|
||||
width: slideWidth,
|
||||
height: slideHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesScreenShareSize = (mediaAreaBounds) => {
|
||||
const { width = 0, height = 0 } = screenShareInput;
|
||||
|
||||
if (width === 0 && height === 0) return { width, height };
|
||||
|
||||
let screeShareWidth;
|
||||
let screeShareHeight;
|
||||
|
||||
screeShareWidth = (width * mediaAreaBounds.height) / height;
|
||||
screeShareHeight = mediaAreaBounds.height;
|
||||
|
||||
if (screeShareWidth > mediaAreaBounds.width) {
|
||||
screeShareWidth = mediaAreaBounds.width;
|
||||
screeShareHeight = (height * mediaAreaBounds.width) / width;
|
||||
}
|
||||
|
||||
return {
|
||||
width: screeShareWidth,
|
||||
height: screeShareHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const calculatesCameraDockBounds = (mediaAreaBounds, mediaBounds, sidebarSize) => {
|
||||
const { baseCameraDockBounds } = props;
|
||||
const baseBounds = baseCameraDockBounds(mediaAreaBounds, sidebarSize);
|
||||
|
||||
if (Object.keys(baseBounds).length > 0) {
|
||||
baseBounds.isCameraHorizontal = false;
|
||||
return baseBounds;
|
||||
}
|
||||
|
||||
const { presentationToolbarMinWidth } = DEFAULT_VALUES;
|
||||
|
||||
const cameraDockBounds = {};
|
||||
|
||||
cameraDockBounds.isCameraHorizontal = false;
|
||||
|
||||
const mediaBoundsWidth = mediaBounds.width > presentationToolbarMinWidth
|
||||
&& !isMobile
|
||||
? mediaBounds.width
|
||||
: presentationToolbarMinWidth;
|
||||
cameraDockBounds.top = mediaAreaBounds.top;
|
||||
cameraDockBounds.left = mediaAreaBounds.left;
|
||||
cameraDockBounds.right = isRTL ? sidebarSize : null;
|
||||
cameraDockBounds.zIndex = 1;
|
||||
|
||||
if (mediaBounds.width < mediaAreaBounds.width) {
|
||||
cameraDockBounds.width = mediaAreaBounds.width
|
||||
- mediaBoundsWidth - MEDIA_ONLY_LAYOUT_MARGIN;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width * 0.8;
|
||||
cameraDockBounds.height = mediaAreaBounds.height;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height;
|
||||
cameraDockBounds.isCameraHorizontal = true;
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_LEFT;
|
||||
} else {
|
||||
cameraDockBounds.width = mediaAreaBounds.width;
|
||||
cameraDockBounds.maxWidth = mediaAreaBounds.width;
|
||||
cameraDockBounds.height = mediaAreaBounds.height
|
||||
- mediaBounds.height - MEDIA_ONLY_LAYOUT_MARGIN;
|
||||
cameraDockBounds.maxHeight = mediaAreaBounds.height * 0.8;
|
||||
cameraDockBounds.position = CAMERADOCK_POSITION.CONTENT_TOP;
|
||||
}
|
||||
|
||||
cameraDockBounds.minWidth = cameraDockBounds.width;
|
||||
cameraDockBounds.minHeight = cameraDockBounds.height;
|
||||
|
||||
return cameraDockBounds;
|
||||
};
|
||||
|
||||
const calculatesMediaBounds = (mediaAreaBounds, slideSize, sidebarSize, screenShareSize) => {
|
||||
const { isOpen, slidesLength } = presentationInput;
|
||||
const { hasExternalVideo } = externalVideoInput;
|
||||
const { genericContentId } = genericMainContentInput;
|
||||
const { hasScreenShare } = screenShareInput;
|
||||
const { isPinned: isSharedNotesPinned } = sharedNotesInput;
|
||||
|
||||
const hasPresentation = isPresentationEnabled && slidesLength !== 0;
|
||||
const isGeneralMediaOff = !hasPresentation && !hasExternalVideo
|
||||
&& !hasScreenShare && !isSharedNotesPinned && !genericContentId;
|
||||
|
||||
const mediaBounds = {};
|
||||
const { element: fullscreenElement } = fullscreen;
|
||||
|
||||
if (!isOpen || isGeneralMediaOff) {
|
||||
mediaBounds.width = 0;
|
||||
mediaBounds.height = 0;
|
||||
mediaBounds.top = 0;
|
||||
mediaBounds.left = !isRTL ? 0 : null;
|
||||
mediaBounds.right = isRTL ? 0 : null;
|
||||
mediaBounds.zIndex = 0;
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
if (
|
||||
fullscreenElement === 'Presentation'
|
||||
|| fullscreenElement === 'Screenshare'
|
||||
|| fullscreenElement === 'ExternalVideo'
|
||||
|| fullscreenElement === 'GenericContent'
|
||||
) {
|
||||
mediaBounds.width = windowWidth();
|
||||
mediaBounds.height = windowHeight();
|
||||
mediaBounds.top = 0;
|
||||
mediaBounds.left = !isRTL ? 0 : null;
|
||||
mediaBounds.right = isRTL ? 0 : null;
|
||||
mediaBounds.zIndex = 99;
|
||||
return mediaBounds;
|
||||
}
|
||||
|
||||
const mediaContentSize = hasScreenShare ? screenShareSize : slideSize;
|
||||
|
||||
if (cameraDockInput.numCameras > 0 && !cameraDockInput.isDragging) {
|
||||
if (mediaContentSize.width !== 0 && mediaContentSize.height !== 0
|
||||
&& !hasExternalVideo && !genericContentId) {
|
||||
if (mediaContentSize.width < mediaAreaBounds.width && !isMobile) {
|
||||
if (mediaContentSize.width < mediaAreaBounds.width * 0.8) {
|
||||
mediaBounds.width = mediaContentSize.width;
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaBounds.width * 0.8;
|
||||
}
|
||||
mediaBounds.height = mediaAreaBounds.height;
|
||||
mediaBounds.top = mediaAreaBounds.top;
|
||||
const sizeValue = mediaAreaBounds.left + (mediaAreaBounds.width - mediaBounds.width);
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
} else {
|
||||
if (mediaContentSize.height < mediaAreaBounds.height * 0.8) {
|
||||
mediaBounds.height = mediaContentSize.height;
|
||||
} else {
|
||||
mediaBounds.height = mediaAreaBounds.height * 0.8;
|
||||
}
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.top = mediaAreaBounds.top + (mediaAreaBounds.height - mediaBounds.height);
|
||||
const sizeValue = mediaAreaBounds.left;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.height = mediaAreaBounds.height * 0.8;
|
||||
mediaBounds.top = mediaAreaBounds.top + (mediaAreaBounds.height - mediaBounds.height);
|
||||
const sizeValue = mediaAreaBounds.left;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
} else {
|
||||
mediaBounds.width = mediaAreaBounds.width;
|
||||
mediaBounds.height = mediaAreaBounds.height;
|
||||
mediaBounds.top = mediaAreaBounds.top;
|
||||
const sizeValue = mediaAreaBounds.left;
|
||||
mediaBounds.left = !isRTL ? sizeValue : null;
|
||||
mediaBounds.right = isRTL ? sidebarSize : null;
|
||||
}
|
||||
mediaBounds.zIndex = 1;
|
||||
|
||||
return mediaBounds;
|
||||
};
|
||||
|
||||
const calculatesLayout = () => {
|
||||
const {
|
||||
calculatesNavbarBounds,
|
||||
calculatesActionbarBounds,
|
||||
calculatesSidebarNavBounds,
|
||||
calculatesSidebarContentBounds,
|
||||
calculatesMediaAreaBounds,
|
||||
} = props;
|
||||
const { camerasMargin } = DEFAULT_VALUES;
|
||||
|
||||
const sidebarNavBounds = calculatesSidebarNavBounds();
|
||||
const sidebarContentBounds = calculatesSidebarContentBounds(0);
|
||||
const mediaAreaBounds = calculatesMediaAreaBounds(0, 0, MEDIA_ONLY_LAYOUT_MARGIN);
|
||||
const navbarBounds = calculatesNavbarBounds(mediaAreaBounds);
|
||||
const actionbarBounds = calculatesActionbarBounds(mediaAreaBounds);
|
||||
const slideSize = calculatesSlideSize(mediaAreaBounds);
|
||||
const screenShareSize = calculatesScreenShareSize(mediaAreaBounds);
|
||||
const sidebarSize = 0;
|
||||
const mediaBounds = calculatesMediaBounds(
|
||||
mediaAreaBounds,
|
||||
slideSize,
|
||||
sidebarSize,
|
||||
screenShareSize,
|
||||
);
|
||||
const cameraDockBounds = calculatesCameraDockBounds(mediaAreaBounds, mediaBounds, sidebarSize);
|
||||
const horizontalCameraDiff = cameraDockBounds.isCameraHorizontal
|
||||
? cameraDockBounds.width + camerasMargin * 2
|
||||
: 0;
|
||||
|
||||
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_SIDEBAR_NAVIGATION_OUTPUT,
|
||||
value: {
|
||||
display: false,
|
||||
minWidth: 0,
|
||||
width: 0,
|
||||
maxWidth: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
tabOrder: DEFAULT_VALUES.sidebarNavTabOrder,
|
||||
isResizable: false,
|
||||
zIndex: sidebarNavBounds.zIndex,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_NAVIGATION_RESIZABLE_EDGE,
|
||||
value: {
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
|
||||
value: {
|
||||
display: false,
|
||||
minWidth: 0,
|
||||
width: 0,
|
||||
maxWidth: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
currentPanelType,
|
||||
tabOrder: DEFAULT_VALUES.sidebarContentTabOrder,
|
||||
isResizable: false,
|
||||
zIndex: sidebarContentBounds.zIndex,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_RESIZABLE_EDGE,
|
||||
value: {
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
},
|
||||
});
|
||||
|
||||
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: cameraDockBounds.position,
|
||||
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,
|
||||
focusedId: input.cameraDock.focusedId,
|
||||
},
|
||||
});
|
||||
|
||||
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 + horizontalCameraDiff : null,
|
||||
tabOrder: DEFAULT_VALUES.presentationTabOrder,
|
||||
isResizable: false,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SCREEN_SHARE_OUTPUT,
|
||||
value: {
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
zIndex: mediaBounds.zIndex,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_EXTERNAL_VIDEO_OUTPUT,
|
||||
value: {
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_GENERIC_CONTENT_OUTPUT,
|
||||
value: {
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SHARED_NOTES_OUTPUT,
|
||||
value: {
|
||||
width: mediaBounds.width,
|
||||
height: mediaBounds.height,
|
||||
top: mediaBounds.top,
|
||||
left: mediaBounds.left,
|
||||
right: isRTL ? mediaBounds.right + horizontalCameraDiff : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const throttledCalculatesLayout = throttle(() => calculatesLayout(), 50, {
|
||||
trailing: true,
|
||||
leading: true,
|
||||
});
|
||||
|
||||
const init = () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_LAYOUT_INPUT,
|
||||
value: (prevInput) => {
|
||||
const {
|
||||
sidebarContent, presentation, cameraDock,
|
||||
externalVideo, genericMainContent, screenShare, sharedNotes,
|
||||
} = prevInput;
|
||||
const { sidebarContentPanel } = sidebarContent;
|
||||
return defaultsDeep(
|
||||
{
|
||||
sidebarNavigation: {
|
||||
isOpen: false,
|
||||
},
|
||||
sidebarContent: {
|
||||
isOpen: false,
|
||||
sidebarContentPanel,
|
||||
},
|
||||
SidebarContentHorizontalResizer: {
|
||||
isOpen: false,
|
||||
},
|
||||
presentation: {
|
||||
isOpen: presentation.isOpen,
|
||||
slidesLength: presentation.slidesLength,
|
||||
currentSlide: {
|
||||
...presentation.currentSlide,
|
||||
},
|
||||
},
|
||||
cameraDock: {
|
||||
numCameras: cameraDock.numCameras,
|
||||
},
|
||||
externalVideo: {
|
||||
hasExternalVideo: externalVideo.hasExternalVideo,
|
||||
},
|
||||
genericMainContent: {
|
||||
genericContentId: genericMainContent.genericContentId,
|
||||
},
|
||||
screenShare: {
|
||||
hasScreenShare: screenShare.hasScreenShare,
|
||||
width: screenShare.width,
|
||||
height: screenShare.height,
|
||||
},
|
||||
sharedNotes: {
|
||||
isPinned: sharedNotes.isPinned,
|
||||
},
|
||||
},
|
||||
INITIAL_INPUT_STATE,
|
||||
);
|
||||
},
|
||||
});
|
||||
Session.setItem('layoutReady', true);
|
||||
throttledCalculatesLayout();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceType === null) return () => null;
|
||||
|
||||
if (deviceType !== prevDeviceType) {
|
||||
// reset layout if deviceType changed
|
||||
// not all options is supported in all devices
|
||||
init();
|
||||
} else {
|
||||
throttledCalculatesLayout();
|
||||
}
|
||||
return () => {};
|
||||
}, [input, deviceType, isRTL, fontSize, fullscreen, isPresentationEnabled]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MediaOnlyLayout;
|
@ -3,8 +3,13 @@ import PropTypes from 'prop-types';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import { LAYOUT_TYPE, ACTIONS } from '../enums';
|
||||
import { isMobile } from '../utils';
|
||||
import {
|
||||
LAYOUT_TYPE,
|
||||
ACTIONS,
|
||||
SYNC,
|
||||
LAYOUT_ELEMENTS,
|
||||
} from '../enums';
|
||||
import { isMobile, LAYOUTS_SYNC } from '../utils';
|
||||
import { updateSettings } from '/imports/ui/components/settings/service';
|
||||
import Session from '/imports/ui/services/storage/in-memory';
|
||||
import usePreviousValue from '/imports/ui/hooks/usePreviousValue';
|
||||
@ -159,6 +164,7 @@ const PushLayoutEngine = (props) => {
|
||||
}, [hasMeetingLayout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedLayout) return () => {};
|
||||
const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
|
||||
const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
|
||||
const enforceLayoutDidChange = enforceLayout !== prevProps.enforceLayout;
|
||||
@ -166,13 +172,15 @@ const PushLayoutEngine = (props) => {
|
||||
? meetingLayoutDidChange || enforceLayoutDidChange
|
||||
: ((meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting)
|
||||
|| enforceLayoutDidChange;
|
||||
const layoutReplicateElements = LAYOUTS_SYNC[selectedLayout][SYNC.REPLICATE_ELEMENTS];
|
||||
const layoutPropagateElements = LAYOUTS_SYNC[selectedLayout][SYNC.PROPAGATE_ELEMENTS];
|
||||
const Settings = getSettingsSingletonInstance();
|
||||
|
||||
if (shouldSwitchLayout) {
|
||||
const replicateLayoutType = () => {
|
||||
let contextLayout = enforceLayout || meetingLayout;
|
||||
if (isMobile()) {
|
||||
if (contextLayout === 'custom') {
|
||||
contextLayout = 'smart';
|
||||
if (contextLayout === LAYOUT_TYPE.CUSTOM_LAYOUT) {
|
||||
contextLayout = LAYOUT_TYPE.SMART_LAYOUT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,18 +195,19 @@ const PushLayoutEngine = (props) => {
|
||||
selectedLayout: contextLayout,
|
||||
},
|
||||
}, null, setLocalSettings);
|
||||
}
|
||||
};
|
||||
|
||||
if (!enforceLayout && pushLayoutMeetingDidChange) {
|
||||
updateSettings({
|
||||
application: {
|
||||
...Settings.application,
|
||||
pushLayout: pushLayoutMeeting,
|
||||
},
|
||||
}, null, setLocalSettings);
|
||||
}
|
||||
const replicatePresentationState = () => {
|
||||
if (meetingPresentationIsOpen !== prevProps.meetingPresentationIsOpen
|
||||
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: meetingPresentationIsOpen,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (meetingLayout === 'custom' && selectedLayout === 'custom' && !isPresenter) {
|
||||
const replicateFocusedCamera = () => {
|
||||
if (meetingLayoutFocusedCamera !== prevProps.meetingLayoutFocusedCamera
|
||||
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
|
||||
layoutContextDispatch({
|
||||
@ -206,7 +215,9 @@ const PushLayoutEngine = (props) => {
|
||||
value: meetingLayoutFocusedCamera,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const replicateCameraDockPosition = () => {
|
||||
if (meetingLayoutCameraPosition !== prevProps.meetingLayoutCameraPosition
|
||||
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
|
||||
layoutContextDispatch({
|
||||
@ -214,7 +225,9 @@ const PushLayoutEngine = (props) => {
|
||||
value: meetingLayoutCameraPosition,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const replicateCameraDockSize = () => {
|
||||
if (!equalDouble(meetingLayoutVideoRate, prevProps.meetingLayoutVideoRate)
|
||||
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
|
||||
let w; let h;
|
||||
@ -243,16 +256,28 @@ const PushLayoutEngine = (props) => {
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (meetingPresentationIsOpen !== prevProps.meetingPresentationIsOpen
|
||||
|| meetingLayoutUpdatedAt !== prevProps.meetingLayoutUpdatedAt) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: meetingPresentationIsOpen,
|
||||
});
|
||||
// REPLICATE LAYOUT
|
||||
if (shouldSwitchLayout && layoutReplicateElements.includes(LAYOUT_ELEMENTS.LAYOUT_TYPE)) {
|
||||
replicateLayoutType();
|
||||
}
|
||||
if (!isPresenter) {
|
||||
if (layoutReplicateElements.includes(LAYOUT_ELEMENTS.PRESENTATION_STATE)) {
|
||||
replicatePresentationState();
|
||||
}
|
||||
if (layoutReplicateElements.includes(LAYOUT_ELEMENTS.FOCUSED_CAMERA)) {
|
||||
replicateFocusedCamera();
|
||||
}
|
||||
if (layoutReplicateElements.includes(LAYOUT_ELEMENTS.CAMERA_DOCK_POSITION)) {
|
||||
replicateCameraDockPosition();
|
||||
}
|
||||
if (layoutReplicateElements.includes(LAYOUT_ELEMENTS.CAMERA_DOCK_SIZE)) {
|
||||
replicateCameraDockSize();
|
||||
}
|
||||
}
|
||||
|
||||
// PROPAGATE LAYOUT
|
||||
const layoutChanged = presentationIsOpen !== prevProps.presentationIsOpen
|
||||
|| selectedLayout !== prevProps.selectedLayout
|
||||
|| cameraIsResizing !== prevProps.cameraIsResizing
|
||||
@ -270,8 +295,12 @@ const PushLayoutEngine = (props) => {
|
||||
}
|
||||
|
||||
// change layout sizes / states
|
||||
if ((pushLayout && layoutChanged) || pushLayout !== prevProps.pushLayout) {
|
||||
if (isPresenter) {
|
||||
if (isPresenter
|
||||
// since all meeting layout properties are pushed together in a
|
||||
// single call just check whether there is any element to be propagate
|
||||
&& layoutPropagateElements.length > 0
|
||||
) {
|
||||
if ((pushLayout && layoutChanged) || pushLayout !== prevProps.pushLayout) {
|
||||
setMeetingLayout();
|
||||
}
|
||||
}
|
||||
@ -279,6 +308,7 @@ const PushLayoutEngine = (props) => {
|
||||
if (selectedLayout !== prevProps.selectedLayout) {
|
||||
Session.setItem('isGridEnabled', selectedLayout === LAYOUT_TYPE.VIDEO_FOCUS);
|
||||
}
|
||||
return () => {};
|
||||
});
|
||||
|
||||
return null;
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { DEVICE_TYPE, LAYOUT_TYPE } from './enums';
|
||||
import {
|
||||
DEVICE_TYPE,
|
||||
LAYOUT_ELEMENTS,
|
||||
LAYOUT_TYPE,
|
||||
SYNC,
|
||||
} from './enums';
|
||||
|
||||
const phoneUpperBoundary = 600;
|
||||
const tabletPortraitUpperBoundary = 900;
|
||||
@ -66,4 +71,58 @@ const suportedLayouts = [
|
||||
],
|
||||
},
|
||||
];
|
||||
export { suportedLayouts };
|
||||
|
||||
const COMMON_ELEMENTS = {
|
||||
DEFAULT: [
|
||||
LAYOUT_ELEMENTS.LAYOUT_TYPE,
|
||||
LAYOUT_ELEMENTS.PRESENTATION_STATE,
|
||||
LAYOUT_ELEMENTS.FOCUSED_CAMERA,
|
||||
],
|
||||
DOCK: [
|
||||
LAYOUT_ELEMENTS.CAMERA_DOCK_POSITION,
|
||||
LAYOUT_ELEMENTS.CAMERA_DOCK_SIZE,
|
||||
],
|
||||
};
|
||||
|
||||
const LAYOUTS_SYNC = {
|
||||
[LAYOUT_TYPE.CUSTOM_LAYOUT]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: [...COMMON_ELEMENTS.DEFAULT, ...COMMON_ELEMENTS.DOCK],
|
||||
[SYNC.REPLICATE_ELEMENTS]: [...COMMON_ELEMENTS.DEFAULT, ...COMMON_ELEMENTS.DOCK],
|
||||
},
|
||||
[LAYOUT_TYPE.SMART_LAYOUT]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
[SYNC.REPLICATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
},
|
||||
[LAYOUT_TYPE.PRESENTATION_FOCUS]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
[SYNC.REPLICATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
},
|
||||
[LAYOUT_TYPE.VIDEO_FOCUS]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
[SYNC.REPLICATE_ELEMENTS]: COMMON_ELEMENTS.DEFAULT,
|
||||
},
|
||||
// Hidden layouts neither propagate nor replicate the layout type pushed.
|
||||
// These layouts are not available for selection in the UI and are set only via join parameters.
|
||||
// Propagating or replicating the layout type would be inappropriate, as
|
||||
// they are intended to maintain a specific view unaffected by layout type changes.
|
||||
[LAYOUT_TYPE.CAMERAS_ONLY]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: [],
|
||||
[SYNC.REPLICATE_ELEMENTS]: [LAYOUT_ELEMENTS.FOCUSED_CAMERA],
|
||||
},
|
||||
[LAYOUT_TYPE.PRESENTATION_ONLY]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: [],
|
||||
[SYNC.REPLICATE_ELEMENTS]: [],
|
||||
},
|
||||
[LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: [],
|
||||
[SYNC.REPLICATE_ELEMENTS]: [],
|
||||
},
|
||||
[LAYOUT_TYPE.MEDIA_ONLY]: {
|
||||
[SYNC.PROPAGATE_ELEMENTS]: [],
|
||||
[SYNC.REPLICATE_ELEMENTS]: [
|
||||
LAYOUT_ELEMENTS.FOCUSED_CAMERA,
|
||||
LAYOUT_ELEMENTS.PRESENTATION_STATE,
|
||||
],
|
||||
},
|
||||
};
|
||||
export { suportedLayouts, LAYOUTS_SYNC };
|
||||
|
@ -289,7 +289,8 @@ class NavBar extends Component {
|
||||
const { selectedLayout } = Settings.application;
|
||||
const shouldShowNavBarToggleButton = selectedLayout !== LAYOUT_TYPE.CAMERAS_ONLY
|
||||
&& selectedLayout !== LAYOUT_TYPE.PRESENTATION_ONLY
|
||||
&& selectedLayout !== LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY;
|
||||
&& selectedLayout !== LAYOUT_TYPE.PARTICIPANTS_AND_CHAT_ONLY
|
||||
&& selectedLayout !== LAYOUT_TYPE.MEDIA_ONLY;
|
||||
|
||||
const APP_CONFIG = window.meetingClientSettings?.public?.app;
|
||||
const enableTalkingIndicator = APP_CONFIG?.enableTalkingIndicator;
|
||||
|
@ -27,6 +27,8 @@ const SidebarContentContainer = () => {
|
||||
|
||||
const currentSlideId = presentationPage?.pageId;
|
||||
|
||||
if (sidebarContentOutput.display === false) return null;
|
||||
|
||||
return (
|
||||
<SidebarContent
|
||||
{...sidebarContentOutput}
|
||||
|
@ -21,6 +21,7 @@ import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedS
|
||||
import { useStorageKey } from '/imports/ui/services/storage/hooks';
|
||||
import useSettings from '../../services/settings/hooks/useSettings';
|
||||
import { SETTINGS } from '../../services/settings/enums';
|
||||
import { INITIAL_INPUT_STATE } from '../layout/initState';
|
||||
|
||||
interface WebcamComponentProps {
|
||||
cameraDock: Output['cameraDock'];
|
||||
@ -107,7 +108,7 @@ const WebcamComponent: React.FC<WebcamComponentProps> = ({
|
||||
const handleVideoFocus = (id: string) => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_FOCUSED_CAMERA_ID,
|
||||
value: focusedId !== id ? id : false,
|
||||
value: focusedId !== id ? id : INITIAL_INPUT_STATE.cameraDock.focusedId,
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user