bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/layout/observer.tsx
Arthur B. Grossi 4aab1b21d8
feat(layout): add userdata bbb_hide_controls (#21410)
* feat(layout): add `hideTopRow` to nav bar context

Adds the `hideTopRow` property to the navbar in the layout context, allowing
the top row of the navigation bar to be hidden. Only the row with the
talking indicators and timer indicator will remain visible when this
option is used.

* feat(layout): add userdata `bbb_hide_controls`

Introduces the userdata join parameter `bbb_hide_controls`, which hides
the action bar and the top row of the navigation bar (including the close
sidebar button, room title, connectivity indicator, and leave meeting button)
while keeping the row with the talking indicator and timer indicator visible.

* fix(layout): has actions bar boolean expression
2024-10-23 09:06:44 -03:00

275 lines
9.2 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { layoutDispatch, layoutSelect, layoutSelectInput } from './context';
import {
ACTIONS, DEVICE_TYPE, LAYOUT_TYPE, PANELS,
} from './enums';
import {
isMobile, isTablet, isTabletPortrait, isTabletLandscape, isDesktop,
} from './utils';
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
import { Input, Layout } from './layoutTypes';
import { throttle } from '/imports/utils/throttle';
import { SETTINGS } from '/imports/ui/services/settings/enums';
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
import getFromUserSettings from '/imports/ui/services/users-settings';
import useMeeting from '/imports/ui/core/hooks/useMeeting';
import MediaService from '/imports/ui/components/media/service';
import { useVideoStreams, useVideoStreamsCount } from '/imports/ui/components/video-provider/hooks';
import { useIsChatEnabled, useIsPresentationEnabled, useIsScreenSharingEnabled } from '/imports/ui/services/features';
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';
import Session from '/imports/ui/services/storage/in-memory';
import deviceInfo from '/imports/utils/deviceInfo';
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
const LayoutObserver: React.FC = () => {
const layoutType = useRef<string | null>(null);
const checkedUserSettings = useRef(false);
const layoutContextDispatch = layoutDispatch();
const deviceType = layoutSelect((i: Layout) => i.deviceType);
const cameraDockInput = layoutSelectInput((i: Input) => i.cameraDock);
const sidebarContentInput = layoutSelectInput((i: Input) => i.sidebarContent);
const presentationInput = layoutSelectInput((i: Input) => i.presentation);
const sharedNotesInput = layoutSelectInput((i: Input) => i.sharedNotes);
const sidebarContent = layoutSelectInput((i: Input) => i.sidebarContent);
const { sidebarContentPanel } = sidebarContent;
const [, setEnableResize] = useState(!window.matchMedia(MOBILE_MEDIA).matches);
const { selectedLayout } = useSettings(SETTINGS.APPLICATION) as { selectedLayout: string };
const {
data: currentMeeting,
} = useMeeting((m) => ({
layout: m.layout,
componentsFlags: m.componentsFlags,
}));
const isThereWebcam = useVideoStreamsCount() > 0;
const { streams: videoStream } = useVideoStreams();
const isScreenSharingEnabled = useIsScreenSharingEnabled();
const isPresentationEnabled = useIsPresentationEnabled();
const isChatEnabled = useIsChatEnabled();
const setLocalSettings = useUserChangedLocalSettings();
const { numCameras } = cameraDockInput;
const { isOpen: sidebarContentIsOpen } = sidebarContentInput;
const { isOpen: presentationIsOpen } = presentationInput;
const { currentLayoutType } = currentMeeting?.layout || {};
const meetingLayout = currentLayoutType && LAYOUT_TYPE[currentLayoutType as keyof typeof LAYOUT_TYPE];
const isSharingVideo = currentMeeting?.componentsFlags?.hasExternalVideo;
const setDeviceType = () => {
let newDeviceType = null;
if (isMobile()) newDeviceType = DEVICE_TYPE.MOBILE;
if (isTablet()) newDeviceType = DEVICE_TYPE.TABLET;
if (isTabletPortrait()) newDeviceType = DEVICE_TYPE.TABLET_PORTRAIT;
if (isTabletLandscape()) newDeviceType = DEVICE_TYPE.TABLET_LANDSCAPE;
if (isDesktop()) newDeviceType = DEVICE_TYPE.DESKTOP;
if (newDeviceType !== deviceType) {
layoutContextDispatch({
type: ACTIONS.SET_DEVICE_TYPE,
value: newDeviceType,
});
}
};
const throttledDeviceType = throttle(
() => setDeviceType(),
50, { trailing: true, leading: true },
);
useEffect(() => {
const Settings = getSettingsSingletonInstance();
layoutContextDispatch({
type: ACTIONS.SET_IS_RTL,
value: document.documentElement.getAttribute('dir') === 'rtl',
});
const APP_CONFIG = window.meetingClientSettings.public.app;
const DESKTOP_FONT_SIZE = APP_CONFIG.desktopFontSize;
const MOBILE_FONT_SIZE = APP_CONFIG.mobileFontSize;
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
document.getElementsByTagName('html')[0].style.fontSize = fontSize;
layoutContextDispatch({
type: ACTIONS.SET_FONT_SIZE,
value: parseInt(fontSize.slice(0, -2), 10),
});
layoutContextDispatch({
type: ACTIONS.SET_HAS_ACTIONBAR,
value: !(getFromUserSettings('bbb_hide_actions_bar', false)
|| getFromUserSettings('bbb_hide_controls', false)),
});
layoutContextDispatch({
type: ACTIONS.SET_HIDE_NAVBAR_TOP_ROW,
value: getFromUserSettings('bbb_hide_controls', false),
});
layoutContextDispatch({
type: ACTIONS.SET_HAS_NAVBAR,
value: !getFromUserSettings('bbb_hide_nav_bar', false),
});
layoutContextDispatch({
type: ACTIONS.SET_HIDE_NOTIFICATION_TOASTS,
value: getFromUserSettings('bbb_hide_notifications', false),
});
window.addEventListener('localeChanged', () => {
layoutContextDispatch({
type: ACTIONS.SET_IS_RTL,
value: Settings.application.isRTL,
});
});
const handleWindowResize = throttle(() => {
setEnableResize((enableResize) => {
const shouldEnableResize = !window.matchMedia(MOBILE_MEDIA).matches;
if (enableResize === shouldEnableResize) return enableResize;
return shouldEnableResize;
});
throttledDeviceType();
});
handleWindowResize();
window.addEventListener('resize', handleWindowResize, false);
return () => {
window.removeEventListener('resize', handleWindowResize, false);
};
}, []);
useEffect(() => {
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_group_id;
if (
selectedLayout?.toLowerCase?.()?.includes?.('focus')
&& !sidebarContentIsOpen
&& deviceType !== DEVICE_TYPE.MOBILE
&& numCameras > 0
&& presentationIsOpen
) {
setTimeout(() => {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: true,
});
layoutContextDispatch({
type: ACTIONS.SET_ID_CHAT_OPEN,
value: PUBLIC_CHAT_ID,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.CHAT,
});
}, 0);
}
}, [selectedLayout]);
useEffect(() => {
throttledDeviceType();
}, [deviceType]);
useEffect(() => {
if (
layoutContextDispatch
&& (typeof meetingLayout !== 'undefined')
&& (layoutType.current !== meetingLayout)
&& sharedNotesInput?.isPinned
) {
layoutType.current = meetingLayout;
layoutContextDispatch({
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
value: true,
});
}
}, [meetingLayout, layoutContextDispatch, layoutType]);
useEffect(() => {
layoutContextDispatch({
type: ACTIONS.SET_LAYOUT_TYPE,
value: selectedLayout,
});
}, [selectedLayout]);
useEffect(() => {
MediaService.buildLayoutWhenPresentationAreaIsDisabled(
layoutContextDispatch,
isSharingVideo,
sharedNotesInput?.isPinned,
isThereWebcam,
isScreenSharingEnabled,
isPresentationEnabled,
);
});
useEffect(() => {
layoutContextDispatch({
type: ACTIONS.SET_NUM_CAMERAS,
value: videoStream.length,
});
}, [videoStream.length]);
useEffect(() => {
if (Session.equals('layoutReady', true) && (sidebarContentPanel === PANELS.NONE)) {
if (!checkedUserSettings.current) {
const Settings = getSettingsSingletonInstance();
Settings.save(setLocalSettings);
if (getFromUserSettings('bbb_show_participants_on_login', window.meetingClientSettings.public.layout.showParticipantsOnLogin) && !deviceInfo.isPhone) {
if (isChatEnabled && getFromUserSettings('bbb_show_public_chat_on_login', !window.meetingClientSettings.public.chat.startClosed)) {
const PUBLIC_CHAT_ID = window.meetingClientSettings.public.chat.public_group_id;
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_IS_OPEN,
value: true,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: true,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.CHAT,
});
layoutContextDispatch({
type: ACTIONS.SET_ID_CHAT_OPEN,
value: PUBLIC_CHAT_ID,
});
} else {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_IS_OPEN,
value: true,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
}
} else {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_IS_OPEN,
value: false,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
}
checkedUserSettings.current = true;
}
}
});
return null;
};
export default LayoutObserver;