From 60df6860d77af1fb7f6d173e20ea03967c91298d Mon Sep 17 00:00:00 2001 From: AK <144495202+AKnassa@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:50:14 -0400 Subject: [PATCH] feat(common): add FullscreenProvider + context module Introduce a single, app-root-owned source of truth for fullscreen state, mirroring the existing provider pattern (ToastProvider, FileDropProvider). The provider centralizes the fullscreenchange / win-visibility-changed / keydown listeners and exposes the same [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] tuple that consumers already destructure. Not yet wired up - both the legacy src/common/useFullscreen hook and the new module coexist. Subsequent commits mount the provider in App.js and switch consumers over. Made-with: Cursor --- src/common/Fullscreen/FullscreenContext.ts | 20 ++++ src/common/Fullscreen/FullscreenProvider.tsx | 105 +++++++++++++++++++ src/common/Fullscreen/index.ts | 7 ++ src/common/Fullscreen/useFullscreen.ts | 8 ++ 4 files changed, 140 insertions(+) create mode 100644 src/common/Fullscreen/FullscreenContext.ts create mode 100644 src/common/Fullscreen/FullscreenProvider.tsx create mode 100644 src/common/Fullscreen/index.ts create mode 100644 src/common/Fullscreen/useFullscreen.ts diff --git a/src/common/Fullscreen/FullscreenContext.ts b/src/common/Fullscreen/FullscreenContext.ts new file mode 100644 index 000000000..860eb044f --- /dev/null +++ b/src/common/Fullscreen/FullscreenContext.ts @@ -0,0 +1,20 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +import { createContext } from 'react'; + +export type FullscreenContextValue = readonly [ + boolean, + () => Promise | void, + () => void, + () => void, +]; + +const noop = () => { /* no-op */ }; + +const defaultValue: FullscreenContextValue = [false, noop, noop, noop]; + +const FullscreenContext = createContext(defaultValue); + +FullscreenContext.displayName = 'FullscreenContext'; + +export default FullscreenContext; diff --git a/src/common/Fullscreen/FullscreenProvider.tsx b/src/common/Fullscreen/FullscreenProvider.tsx new file mode 100644 index 000000000..b0b721d30 --- /dev/null +++ b/src/common/Fullscreen/FullscreenProvider.tsx @@ -0,0 +1,105 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import useShell, { type WindowVisibility } from '../useShell'; +import useSettings from '../useSettings'; +import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext'; + +type Props = { + children: React.ReactNode, +}; + +// Single source of truth for fullscreen state. Mounted once at the app root so +// the value survives route remounts (fixes desync where switching tabs while in +// fullscreen would leave the UI thinking we were still windowed). +const FullscreenProvider = ({ children }: Props) => { + const shell = useShell(); + const [settings] = useSettings(); + + const [fullscreen, setFullscreen] = useState(() => { + if (typeof document === 'undefined') return false; + return document.fullscreenElement === document.documentElement; + }); + + const requestFullscreen = useCallback(async () => { + if (shell.active) { + shell.send('win-set-visibility', { fullscreen: true }); + } else { + try { + await document.documentElement.requestFullscreen(); + } catch (err) { + console.error('Error enabling fullscreen', err); + } + } + }, [shell]); + + const exitFullscreen = useCallback(() => { + if (shell.active) { + shell.send('win-set-visibility', { fullscreen: false }); + } else { + if (document.fullscreenElement === document.documentElement) { + document.exitFullscreen(); + } + } + }, [shell]); + + const toggleFullscreen = useCallback(() => { + fullscreen ? exitFullscreen() : requestFullscreen(); + }, [fullscreen, exitFullscreen, requestFullscreen]); + + useEffect(() => { + const onWindowVisibilityChanged = (state: WindowVisibility) => { + setFullscreen(state.isFullscreen === true); + }; + + const onFullscreenChange = () => { + setFullscreen(document.fullscreenElement === document.documentElement); + }; + + const onKeyDown = (event: KeyboardEvent) => { + const activeElement = document.activeElement as HTMLElement; + + const inputFocused = + activeElement && + (activeElement.tagName === 'INPUT' || + activeElement.tagName === 'TEXTAREA' || + activeElement.tagName === 'SELECT' || + activeElement.isContentEditable); + + if (event.code === 'Escape' && settings.escExitFullscreen) { + exitFullscreen(); + } + + if (event.code === 'KeyF' && !inputFocused) { + toggleFullscreen(); + } + + if (event.code === 'F11' && shell.active) { + toggleFullscreen(); + } + }; + + shell.on('win-visibility-changed', onWindowVisibilityChanged); + document.addEventListener('keydown', onKeyDown); + document.addEventListener('fullscreenchange', onFullscreenChange); + + return () => { + shell.off('win-visibility-changed', onWindowVisibilityChanged); + document.removeEventListener('keydown', onKeyDown); + document.removeEventListener('fullscreenchange', onFullscreenChange); + }; + }, [shell, settings.escExitFullscreen, toggleFullscreen, exitFullscreen]); + + const value = useMemo( + () => [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen], + [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] + ); + + return ( + + {children} + + ); +}; + +export default FullscreenProvider; diff --git a/src/common/Fullscreen/index.ts b/src/common/Fullscreen/index.ts new file mode 100644 index 000000000..cc89f769b --- /dev/null +++ b/src/common/Fullscreen/index.ts @@ -0,0 +1,7 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +import FullscreenProvider from './FullscreenProvider'; +import useFullscreen from './useFullscreen'; + +export { FullscreenProvider, useFullscreen }; +export default useFullscreen; diff --git a/src/common/Fullscreen/useFullscreen.ts b/src/common/Fullscreen/useFullscreen.ts new file mode 100644 index 000000000..29b2f62e5 --- /dev/null +++ b/src/common/Fullscreen/useFullscreen.ts @@ -0,0 +1,8 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +import { useContext } from 'react'; +import FullscreenContext from './FullscreenContext'; + +const useFullscreen = () => useContext(FullscreenContext); + +export default useFullscreen;