import { PropsWithChildren, SyntheticEvent, useCallback, useContext, useEffect, useMemo, useState, } from 'react' import CssBaseline from '@mui/material/CssBaseline' import { ThemeProvider, createTheme } from '@mui/material/styles' import Box from '@mui/material/Box' import { AlertColor } from '@mui/material/Alert' import { useWindowSize } from '@react-hook/window-size' import { ShellContext } from 'contexts/ShellContext' import { SettingsContext } from 'contexts/SettingsContext' import { AlertOptions } from 'models/shell' import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat' import { ErrorBoundary } from 'components/ErrorBoundary' import { PeerConnectionType } from 'services/PeerRoom/PeerRoom' import { Drawer } from './Drawer' import { UpgradeDialog } from './UpgradeDialog' import { ShellAppBar } from './ShellAppBar' import { NotificationArea } from './NotificationArea' import { RouteContent } from './RouteContent' import { PeerList } from './PeerList' import { QRCodeDialog } from './QRCodeDialog' import { RoomShareDialog } from './RoomShareDialog' import { useConnectionTest } from './useConnectionTest' export interface ShellProps extends PropsWithChildren { userPeerId: string appNeedsUpdate: boolean } export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => { const { getUserSettings, updateUserSettings } = useContext(SettingsContext) const { colorMode } = getUserSettings() const theme = useMemo( () => createTheme({ palette: { mode: colorMode, }, }), [colorMode] ) const [windowWidth] = useWindowSize() const defaultSidebarsOpen = windowWidth >= theme.breakpoints.values.lg const [isAlertShowing, setIsAlertShowing] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(defaultSidebarsOpen) const [isQRCodeDialogOpen, setIsQRCodeDialogOpen] = useState(false) const [isRoomShareDialogOpen, setIsRoomShareDialogOpen] = useState(false) const [alertSeverity, setAlertSeverity] = useState('info') const [showAppBar, setShowAppBar] = useState(true) const [showRoomControls, setShowRoomControls] = useState(true) const [isFullscreen, setIsFullscreen] = useState(false) const [title, setTitle] = useState('') const [alertText, setAlertText] = useState('') const [roomId, setRoomId] = useState(undefined) const [password, setPassword] = useState(undefined) const [isPeerListOpen, setIsPeerListOpen] = useState(defaultSidebarsOpen) const [peerList, setPeerList] = useState([]) // except self const [peerConnectionTypes, setPeerConnectionTypes] = useState< Record >({}) const [tabHasFocus, setTabHasFocus] = useState(true) const [audioState, setAudioState] = useState(AudioState.STOPPED) const [videoState, setVideoState] = useState(VideoState.STOPPED) const [screenState, setScreenState] = useState( ScreenShareState.NOT_SHARING ) const [customUsername, setCustomUsername] = useState( getUserSettings().customUsername ) const [peerAudios, setPeerAudios] = useState< Record >({}) const showAlert = useCallback< (message: string, options?: AlertOptions) => void >((message, options) => { setAlertText(message) setAlertSeverity(options?.severity ?? 'info') setIsAlertShowing(true) }, []) const { connectionTestResults } = useConnectionTest() const shellContextValue = useMemo( () => ({ tabHasFocus, showRoomControls, setShowRoomControls, setTitle, showAlert, isPeerListOpen, setIsQRCodeDialogOpen, roomId, setRoomId, password, setPassword, setIsPeerListOpen, peerList, setPeerList, peerConnectionTypes, setPeerConnectionTypes, audioState, setAudioState, videoState, setVideoState, screenState, setScreenState, peerAudios, setPeerAudios, customUsername, setCustomUsername, connectionTestResults, }), [ isPeerListOpen, setIsQRCodeDialogOpen, roomId, setRoomId, password, setPassword, peerList, peerConnectionTypes, tabHasFocus, showRoomControls, setShowRoomControls, setTitle, showAlert, audioState, setAudioState, videoState, setVideoState, screenState, setScreenState, peerAudios, setPeerAudios, customUsername, setCustomUsername, connectionTestResults, ] ) const handleAlertClose = ( _event?: SyntheticEvent | Event, reason?: string ) => { if (reason === 'clickaway') { return } setIsAlertShowing(false) } useEffect(() => { if (customUsername === getUserSettings().customUsername) return updateUserSettings({ customUsername }) }, [customUsername, getUserSettings, updateUserSettings]) useEffect(() => { document.title = title }, [title]) const enterFullscreen = async () => { const body: any = document.body try { if (body.requestFullscreen) { await body.requestFullscreen() } else if (body.webkitRequestFullscreen) { await body.webkitRequestFullscreen() } else if (body.mozRequestFullScreen) { await body.mozRequestFullScreen() } else if (body.msRequestFullscreen) { await body.msRequestFullscreen() } } catch (e) { // Silence harmless errors } } const exitFullscreen = async () => { const document: any = window.document try { if (document.exitFullscreen) { await document.exitFullscreen() } else if (document.webkitExitFullscreen) { await document.webkitExitFullscreen() } else if (document.mozCancelFullScreen) { await document.mozCancelFullScreen() } else if (document.msExitFullScreen) { await document.msExitFullScreen() } } catch (e) { // Silence harmless errors } } useEffect(() => { if (isFullscreen) { enterFullscreen() setShowRoomControls(false) setShowAppBar(false) } else { exitFullscreen() setShowAppBar(true) setShowRoomControls(true) } }, [isFullscreen, setShowRoomControls, setShowAppBar]) useEffect(() => { if (isFullscreen) setShowAppBar(showRoomControls) }, [isFullscreen, showRoomControls, setShowAppBar]) useEffect(() => { const handleFocus = () => { setTabHasFocus(true) } const handleBlur = () => { setTabHasFocus(false) } const handleFullscreen = () => { setIsFullscreen(!!document.fullscreenElement) } window.addEventListener('focus', handleFocus) window.addEventListener('blur', handleBlur) document.addEventListener('fullscreenchange', handleFullscreen) return () => { window.removeEventListener('focus', handleFocus) window.removeEventListener('blur', handleBlur) document.removeEventListener('fullscreenchange', handleFullscreen) } }, []) const handleDrawerOpen = () => { setIsDrawerOpen(true) } const handleDrawerClose = () => { setIsDrawerOpen(false) } const handlePeerListClick = () => { setIsPeerListOpen(!isPeerListOpen) } const copyToClipboard = async ( content: string, alert: string, severity: AlertColor = 'success' ) => { await navigator.clipboard.writeText(content) shellContextValue.showAlert(alert, { severity }) } const handleLinkButtonClick = async () => { if (roomId !== undefined && password !== undefined) { setIsRoomShareDialogOpen(true) } else { copyToClipboard(window.location.href, 'Current URL copied to clipboard') } } const handleQRCodeDialogClose = () => { setIsQRCodeDialogOpen(false) } const handleRoomShareDialogClose = () => { setIsRoomShareDialogOpen(false) } return ( setShowRoomControls(!showRoomControls)} setIsQRCodeDialogOpen={setIsQRCodeDialogOpen} showAppBar={showAppBar} isFullscreen={isFullscreen} setIsFullscreen={setIsFullscreen} /> {children} ) }