2024-08-29 22:47:50 +08:00
|
|
|
import React, { useEffect } from 'react';
|
2022-09-03 02:18:17 +08:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import getFromUserSettings from '/imports/ui/services/users-settings';
|
2024-05-29 21:26:11 +08:00
|
|
|
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
|
2022-09-03 02:18:17 +08:00
|
|
|
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';
|
2024-06-06 21:50:03 +08:00
|
|
|
import Session from '/imports/ui/services/storage/in-memory';
|
2024-08-29 22:47:50 +08:00
|
|
|
import usePreviousValue from '/imports/ui/hooks/usePreviousValue';
|
|
|
|
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
|
|
|
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
|
|
|
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';
|
|
|
|
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
|
|
|
|
import { SETTINGS } from '/imports/ui/services/settings/enums';
|
|
|
|
import {
|
|
|
|
layoutDispatch,
|
|
|
|
layoutSelectInput,
|
|
|
|
layoutSelectOutput,
|
|
|
|
} from '../context';
|
|
|
|
import { calculatePresentationVideoRate } from './service';
|
|
|
|
import { useMeetingLayoutUpdater, usePushLayoutUpdater } from './hooks';
|
2022-09-03 02:18:17 +08:00
|
|
|
|
|
|
|
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,
|
2022-09-15 23:01:31 +08:00
|
|
|
horizontalPosition: PropTypes.bool,
|
2022-10-10 21:32:19 +08:00
|
|
|
isMeetingLayoutResizing: PropTypes.bool,
|
2022-09-03 02:18:17 +08:00
|
|
|
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,
|
|
|
|
setMeetingLayout: PropTypes.func,
|
2023-03-04 05:59:47 +08:00
|
|
|
setPushLayout: PropTypes.func,
|
2022-09-03 02:18:17 +08:00
|
|
|
shouldShowScreenshare: PropTypes.bool,
|
|
|
|
shouldShowExternalVideo: PropTypes.bool,
|
2023-11-23 01:21:07 +08:00
|
|
|
enforceLayout: PropTypes.string,
|
2024-01-30 21:03:11 +08:00
|
|
|
setLocalSettings: PropTypes.func.isRequired,
|
2022-09-03 02:18:17 +08:00
|
|
|
};
|
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
const PushLayoutEngine = (props) => {
|
2024-08-30 02:08:24 +08:00
|
|
|
const prevProps = usePreviousValue(props) || {};
|
2024-08-29 22:47:50 +08:00
|
|
|
|
|
|
|
const {
|
|
|
|
cameraWidth,
|
|
|
|
cameraHeight,
|
|
|
|
horizontalPosition,
|
|
|
|
meetingLayout,
|
|
|
|
meetingLayoutCameraPosition,
|
|
|
|
meetingLayoutFocusedCamera,
|
|
|
|
meetingLayoutVideoRate,
|
|
|
|
meetingPresentationIsOpen,
|
|
|
|
shouldShowScreenshare,
|
|
|
|
shouldShowExternalVideo,
|
|
|
|
enforceLayout,
|
|
|
|
setLocalSettings,
|
|
|
|
pushLayoutMeeting,
|
|
|
|
cameraIsResizing,
|
|
|
|
cameraPosition,
|
|
|
|
focusedCamera,
|
|
|
|
isMeetingLayoutResizing,
|
|
|
|
isModerator,
|
|
|
|
isPresenter,
|
|
|
|
layoutContextDispatch,
|
|
|
|
meetingLayoutUpdatedAt,
|
|
|
|
presentationIsOpen,
|
|
|
|
presentationVideoRate,
|
|
|
|
pushLayout,
|
|
|
|
selectedLayout,
|
|
|
|
setMeetingLayout,
|
|
|
|
setPushLayout,
|
|
|
|
} = props;
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-29 21:26:11 +08:00
|
|
|
const Settings = getSettingsSingletonInstance();
|
|
|
|
|
2024-03-19 04:42:03 +08:00
|
|
|
const changeLayout = LAYOUT_TYPE[getFromUserSettings('bbb_change_layout', null)];
|
|
|
|
const defaultLayout = LAYOUT_TYPE[getFromUserSettings('bbb_default_layout', null)];
|
2024-05-10 21:35:25 +08:00
|
|
|
const enforcedLayout = LAYOUT_TYPE[enforceLayout] || null;
|
2022-09-03 02:18:17 +08:00
|
|
|
|
2024-05-10 21:35:25 +08:00
|
|
|
Settings.application.selectedLayout = enforcedLayout
|
2024-03-19 04:42:03 +08:00
|
|
|
|| changeLayout
|
|
|
|
|| defaultLayout
|
|
|
|
|| meetingLayout;
|
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
let { selectedLayout: actualLayout } = Settings.application;
|
2022-09-03 02:18:17 +08:00
|
|
|
if (isMobile()) {
|
2024-08-29 22:47:50 +08:00
|
|
|
actualLayout = actualLayout === 'custom' ? 'smart' : actualLayout;
|
|
|
|
Settings.application.actualLayout = actualLayout;
|
2022-09-03 02:18:17 +08:00
|
|
|
}
|
2024-08-29 22:47:50 +08:00
|
|
|
Session.setItem('isGridEnabled', actualLayout === LAYOUT_TYPE.VIDEO_FOCUS);
|
2023-07-26 00:20:45 +08:00
|
|
|
|
2024-01-30 21:03:11 +08:00
|
|
|
Settings.save(setLocalSettings);
|
2022-09-03 02:18:17 +08:00
|
|
|
|
2024-05-29 21:26:11 +08:00
|
|
|
const HIDE_PRESENTATION = window.meetingClientSettings.public.layout.hidePresentationOnJoin;
|
|
|
|
|
2024-02-15 22:58:30 +08:00
|
|
|
const shouldOpenPresentation = shouldShowScreenshare || shouldShowExternalVideo;
|
2024-08-29 22:47:50 +08:00
|
|
|
let presentationLastState = !getFromUserSettings('bbb_hide_presentation_on_join', HIDE_PRESENTATION);
|
|
|
|
presentationLastState = pushLayoutMeeting ? meetingPresentationIsOpen : presentationLastState;
|
|
|
|
presentationLastState = shouldOpenPresentation || presentationLastState;
|
|
|
|
MediaService.setPresentationIsOpen(layoutContextDispatch, presentationLastState);
|
|
|
|
Session.setItem('presentationLastState', presentationLastState);
|
2022-09-03 02:18:17 +08:00
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
if (actualLayout === 'custom') {
|
2022-09-03 02:18:17 +08:00
|
|
|
setTimeout(() => {
|
|
|
|
layoutContextDispatch({
|
|
|
|
type: ACTIONS.SET_FOCUSED_CAMERA_ID,
|
|
|
|
value: meetingLayoutFocusedCamera,
|
|
|
|
});
|
|
|
|
|
|
|
|
layoutContextDispatch({
|
|
|
|
type: ACTIONS.SET_CAMERA_DOCK_POSITION,
|
2024-04-02 02:48:15 +08:00
|
|
|
value: meetingLayoutCameraPosition || 'contentTop',
|
2022-09-03 02:18:17 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!equalDouble(meetingLayoutVideoRate, 0)) {
|
2024-08-28 20:59:22 +08:00
|
|
|
let w; let h;
|
2022-09-03 02:18:17 +08:00
|
|
|
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,
|
2024-08-28 20:59:22 +08:00
|
|
|
},
|
2022-09-03 02:18:17 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}, 0);
|
|
|
|
}
|
2024-08-29 22:47:50 +08:00
|
|
|
}, []);
|
2022-09-03 02:18:17 +08:00
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
useEffect(() => {
|
2022-09-03 02:18:17 +08:00
|
|
|
const meetingLayoutDidChange = meetingLayout !== prevProps.meetingLayout;
|
|
|
|
const pushLayoutMeetingDidChange = pushLayoutMeeting !== prevProps.pushLayoutMeeting;
|
2023-11-23 01:21:07 +08:00
|
|
|
const enforceLayoutDidChange = enforceLayout !== prevProps.enforceLayout;
|
2022-09-03 02:18:17 +08:00
|
|
|
const shouldSwitchLayout = isPresenter
|
2023-11-23 01:21:07 +08:00
|
|
|
? meetingLayoutDidChange || enforceLayoutDidChange
|
2024-08-28 20:59:22 +08:00
|
|
|
: ((meetingLayoutDidChange || pushLayoutMeetingDidChange) && pushLayoutMeeting)
|
|
|
|
|| enforceLayoutDidChange;
|
2024-05-29 21:26:11 +08:00
|
|
|
const Settings = getSettingsSingletonInstance();
|
2022-09-03 02:18:17 +08:00
|
|
|
|
|
|
|
if (shouldSwitchLayout) {
|
2023-11-23 01:21:07 +08:00
|
|
|
let contextLayout = enforceLayout || meetingLayout;
|
2022-09-03 02:18:17 +08:00
|
|
|
if (isMobile()) {
|
2023-11-23 01:21:07 +08:00
|
|
|
if (contextLayout === 'custom') {
|
|
|
|
contextLayout = 'smart';
|
|
|
|
}
|
2022-09-03 02:18:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
layoutContextDispatch({
|
|
|
|
type: ACTIONS.SET_LAYOUT_TYPE,
|
|
|
|
value: contextLayout,
|
|
|
|
});
|
|
|
|
|
|
|
|
updateSettings({
|
|
|
|
application: {
|
|
|
|
...Settings.application,
|
|
|
|
selectedLayout: contextLayout,
|
|
|
|
},
|
2024-01-30 21:03:11 +08:00
|
|
|
}, null, setLocalSettings);
|
2022-09-03 02:18:17 +08:00
|
|
|
}
|
|
|
|
|
2023-11-23 01:21:07 +08:00
|
|
|
if (!enforceLayout && pushLayoutMeetingDidChange) {
|
2022-09-03 02:18:17 +08:00
|
|
|
updateSettings({
|
|
|
|
application: {
|
|
|
|
...Settings.application,
|
|
|
|
pushLayout: pushLayoutMeeting,
|
|
|
|
},
|
2024-01-30 21:03:11 +08:00
|
|
|
}, null, setLocalSettings);
|
2022-09-03 02:18:17 +08:00
|
|
|
}
|
|
|
|
|
2024-08-28 20:59:22 +08:00
|
|
|
if (meetingLayout === 'custom' && selectedLayout === 'custom' && !isPresenter) {
|
2022-09-03 02:18:17 +08:00
|
|
|
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) {
|
2024-08-28 20:59:22 +08:00
|
|
|
let w; let h;
|
2022-09-03 02:18:17 +08:00
|
|
|
if (horizontalPosition) {
|
|
|
|
w = window.innerWidth * meetingLayoutVideoRate;
|
|
|
|
h = cameraHeight;
|
|
|
|
} else {
|
|
|
|
w = cameraWidth;
|
|
|
|
h = window.innerHeight * meetingLayoutVideoRate;
|
|
|
|
}
|
|
|
|
|
2022-10-10 21:32:19 +08:00
|
|
|
if (isMeetingLayoutResizing !== prevProps.isMeetingLayoutResizing) {
|
2022-09-03 02:18:17 +08:00
|
|
|
layoutContextDispatch({
|
|
|
|
type: ACTIONS.SET_CAMERA_DOCK_IS_RESIZING,
|
2022-10-10 21:32:19 +08:00
|
|
|
value: isMeetingLayoutResizing,
|
2022-09-03 02:18:17 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
layoutContextDispatch({
|
|
|
|
type: ACTIONS.SET_CAMERA_DOCK_SIZE,
|
|
|
|
value: {
|
|
|
|
width: w,
|
|
|
|
height: h,
|
|
|
|
browserWidth: window.innerWidth,
|
|
|
|
browserHeight: window.innerHeight,
|
2024-08-28 20:59:22 +08:00
|
|
|
},
|
2022-09-03 02:18:17 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2023-11-23 01:21:07 +08:00
|
|
|
|| enforceLayout !== prevProps.enforceLayout
|
2022-09-03 02:18:17 +08:00
|
|
|
|| !equalDouble(presentationVideoRate, prevProps.presentationVideoRate);
|
|
|
|
|
2024-08-28 20:59:22 +08:00
|
|
|
// push layout once after presenter toggles
|
|
|
|
// special case where we set pushLayout to false in all viewers
|
|
|
|
if (pushLayout !== prevProps.pushLayout) {
|
2023-03-07 05:08:25 +08:00
|
|
|
if (isModerator) {
|
2023-03-04 05:59:47 +08:00
|
|
|
setPushLayout(pushLayout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-28 20:59:22 +08:00
|
|
|
// change layout sizes / states
|
|
|
|
if ((pushLayout && layoutChanged) || pushLayout !== prevProps.pushLayout) {
|
2022-09-03 02:18:17 +08:00
|
|
|
if (isPresenter) {
|
|
|
|
setMeetingLayout();
|
|
|
|
}
|
|
|
|
}
|
2023-07-26 00:20:45 +08:00
|
|
|
|
|
|
|
if (selectedLayout !== prevProps.selectedLayout) {
|
2024-06-06 21:50:03 +08:00
|
|
|
Session.setItem('isGridEnabled', selectedLayout === LAYOUT_TYPE.VIDEO_FOCUS);
|
2023-07-26 00:20:45 +08:00
|
|
|
}
|
2024-08-29 22:47:50 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
2022-09-03 02:18:17 +08:00
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
const PushLayoutEngineContainer = (props) => {
|
|
|
|
const cameraDockOutput = layoutSelectOutput((i) => i.cameraDock);
|
|
|
|
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
|
|
|
const presentationInput = layoutSelectInput((i) => i.presentation);
|
|
|
|
const layoutContextDispatch = layoutDispatch();
|
|
|
|
|
|
|
|
const applicationSettings = useSettings(SETTINGS.APPLICATION);
|
|
|
|
const {
|
|
|
|
selectedLayout,
|
|
|
|
pushLayout,
|
|
|
|
} = applicationSettings;
|
|
|
|
|
|
|
|
const {
|
|
|
|
width: cameraWidth,
|
|
|
|
height: cameraHeight,
|
|
|
|
position: cameraPosition,
|
|
|
|
focusedId: focusedCamera,
|
|
|
|
} = cameraDockOutput;
|
|
|
|
|
|
|
|
const {
|
|
|
|
isResizing: cameraIsResizing,
|
|
|
|
} = cameraDockInput;
|
|
|
|
|
|
|
|
const horizontalPosition = cameraPosition === 'contentLeft' || cameraPosition === 'contentRight';
|
|
|
|
|
|
|
|
const {
|
|
|
|
data: currentMeeting,
|
|
|
|
} = useMeeting((m) => ({
|
|
|
|
layout: m.layout,
|
|
|
|
}));
|
|
|
|
const meetingLayout = LAYOUT_TYPE[currentMeeting?.layout.currentLayoutType];
|
|
|
|
const meetingLayoutUpdatedAt = new Date(currentMeeting?.layout.updatedAt).getTime();
|
|
|
|
const {
|
|
|
|
propagateLayout: pushLayoutMeeting,
|
|
|
|
cameraDockIsResizing: isMeetingLayoutResizing,
|
|
|
|
cameraDockPlacement: meetingLayoutCameraPosition,
|
|
|
|
cameraDockAspectRatio: meetingLayoutVideoRate,
|
|
|
|
cameraWithFocus: meetingLayoutFocusedCamera,
|
2024-08-30 02:08:24 +08:00
|
|
|
presentationMinimized: meetingPresentationMinimized,
|
2024-08-29 22:47:50 +08:00
|
|
|
} = (currentMeeting?.layout || {});
|
|
|
|
|
|
|
|
const { isOpen: presentationIsOpen } = presentationInput;
|
|
|
|
|
|
|
|
const { data: currentUserData } = useCurrentUser((user) => ({
|
|
|
|
enforceLayout: user.enforceLayout,
|
|
|
|
isModerator: user.isModerator,
|
|
|
|
presenter: user.presenter,
|
|
|
|
}));
|
|
|
|
const isModerator = currentUserData?.isModerator;
|
|
|
|
const isPresenter = currentUserData?.presenter;
|
|
|
|
|
|
|
|
const presentationVideoRate = calculatePresentationVideoRate(cameraDockOutput);
|
|
|
|
|
|
|
|
const setLocalSettings = useUserChangedLocalSettings();
|
|
|
|
const setPushLayout = usePushLayoutUpdater(pushLayout);
|
|
|
|
const setMeetingLayout = useMeetingLayoutUpdater(
|
|
|
|
cameraDockOutput,
|
|
|
|
cameraDockInput,
|
|
|
|
presentationInput,
|
|
|
|
applicationSettings,
|
|
|
|
);
|
|
|
|
|
|
|
|
const validateEnforceLayout = (currUser) => {
|
|
|
|
const layoutTypes = Object.keys(LAYOUT_TYPE);
|
|
|
|
const enforceLayout = currUser?.enforceLayout;
|
|
|
|
return enforceLayout && layoutTypes.includes(enforceLayout) ? enforceLayout : null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const enforceLayout = validateEnforceLayout(currentUserData);
|
2024-08-30 02:08:24 +08:00
|
|
|
const meetingPresentationIsOpen = !meetingPresentationMinimized;
|
2024-08-29 22:47:50 +08:00
|
|
|
|
2024-09-10 20:44:49 +08:00
|
|
|
if (!meetingLayout) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
return (
|
|
|
|
<PushLayoutEngine
|
|
|
|
{...{
|
|
|
|
cameraWidth,
|
|
|
|
cameraHeight,
|
|
|
|
horizontalPosition,
|
|
|
|
meetingLayout,
|
|
|
|
meetingLayoutCameraPosition,
|
|
|
|
meetingLayoutFocusedCamera,
|
|
|
|
meetingLayoutVideoRate,
|
|
|
|
meetingPresentationIsOpen,
|
|
|
|
enforceLayout,
|
|
|
|
setLocalSettings,
|
|
|
|
pushLayoutMeeting,
|
|
|
|
cameraIsResizing,
|
|
|
|
cameraPosition,
|
|
|
|
focusedCamera,
|
|
|
|
isMeetingLayoutResizing,
|
|
|
|
isModerator,
|
|
|
|
isPresenter,
|
|
|
|
layoutContextDispatch,
|
|
|
|
meetingLayoutUpdatedAt,
|
|
|
|
presentationIsOpen,
|
|
|
|
presentationVideoRate,
|
|
|
|
pushLayout,
|
|
|
|
selectedLayout,
|
|
|
|
setMeetingLayout,
|
|
|
|
setPushLayout,
|
|
|
|
...props,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
2022-09-03 02:18:17 +08:00
|
|
|
|
|
|
|
PushLayoutEngine.propTypes = propTypes;
|
|
|
|
|
2024-08-29 22:47:50 +08:00
|
|
|
export default PushLayoutEngineContainer;
|