Fix fullscreen issues

This commit is contained in:
Dustin Brett
2023-08-07 15:24:23 -07:00
parent 205baae434
commit 643e705b5c
8 changed files with 187 additions and 148 deletions

View File

@@ -7,7 +7,6 @@ import {
ZoomOut,
} from "components/apps/Photos/PhotoIcons";
import StyledPhotos from "components/apps/Photos/StyledPhotos";
import useFullscreen from "components/apps/Photos/useFullscreen";
import usePanZoom, { panZoomConfig } from "components/apps/Photos/usePanZoom";
import type { ComponentProcessProps } from "components/system/Apps/RenderComponent";
import useFileDrop from "components/system/Files/FileManager/useFileDrop";
@@ -33,6 +32,7 @@ import {
imgDataToBuffer,
label,
} from "utils/functions";
import { useViewport } from "contexts/viewport";
const { maxScale, minScale } = panZoomConfig;
@@ -94,7 +94,7 @@ const Photos: FC<ComponentProcessProps> = ({ id }) => {
imageRef.current,
imageContainerRef.current
);
const { fullscreen, toggleFullscreen } = useFullscreen(containerRef.current);
const { fullscreenElement, toggleFullscreen } = useViewport();
const loadPhoto = useCallback(async (): Promise<void> => {
let fileContents: Buffer | string = await readFile(url);
const ext = getExtension(url);
@@ -225,10 +225,14 @@ const Photos: FC<ComponentProcessProps> = ({ id }) => {
<nav className="bottom">
<Button
disabled={!url}
onClick={() => toggleFullscreen("show")}
onClick={() => toggleFullscreen(containerRef.current, "show")}
{...label("Full-screen")}
>
{fullscreen ? <ExitFullscreen /> : <Fullscreen />}
{fullscreenElement === containerRef.current ? (
<ExitFullscreen />
) : (
<Fullscreen />
)}
</Button>
</nav>
</StyledPhotos>

View File

@@ -1,86 +0,0 @@
import { useEffect, useState } from "react";
type Fullscreen = {
fullscreen: boolean;
toggleFullscreen: (navigationUI?: FullscreenNavigationUI) => void;
};
type FullscreenDocument = Document & {
mozCancelFullScreen: () => Promise<void>;
mozFullScreenElement: HTMLElement;
mozFullScreenEnabled: boolean;
webkitExitFullscreen: () => Promise<void>;
webkitFullscreenElement: HTMLElement;
webkitFullscreenEnabled: boolean;
};
type FullscreenElement = HTMLElement & {
mozRequestFullScreen?: (options?: FullscreenOptions) => Promise<void>;
webkitRequestFullscreen?: (options?: FullscreenOptions) => Promise<void>;
};
const isFullscreen = (): boolean => {
const { fullscreenElement, mozFullScreenElement, webkitFullscreenElement } =
document as FullscreenDocument;
return Boolean(
fullscreenElement || mozFullScreenElement || webkitFullscreenElement
);
};
const useFullscreen = (element?: HTMLElement | null): Fullscreen => {
const [fullscreen, setFullscreen] = useState(false);
const toggleFullscreen = async (
navigationUI?: FullscreenNavigationUI
): Promise<void> => {
if (fullscreen) {
const fullscreenDocument = document as FullscreenDocument;
try {
if (fullscreenDocument.exitFullscreen) {
await fullscreenDocument.exitFullscreen();
} else if (fullscreenDocument.mozCancelFullScreen) {
await fullscreenDocument.mozCancelFullScreen();
} else if (fullscreenDocument.webkitExitFullscreen) {
await fullscreenDocument.webkitExitFullscreen();
}
} catch {
// Ignore failure while exiting fullscreen
}
} else {
const fullScreenElement = (element ||
document.documentElement) as FullscreenElement;
const fullscreenOptions: FullscreenOptions = {
navigationUI: navigationUI || "hide",
};
try {
if (fullScreenElement.requestFullscreen) {
await fullScreenElement.requestFullscreen(fullscreenOptions);
} else if (fullScreenElement.mozRequestFullScreen) {
await fullScreenElement.mozRequestFullScreen(fullscreenOptions);
} else if (fullScreenElement.webkitRequestFullscreen) {
await fullScreenElement.webkitRequestFullscreen(fullscreenOptions);
}
} catch {
// Ignore failure while entering fullscreen
}
}
};
useEffect(() => {
const monitorFullscreenState = (): void => setFullscreen(isFullscreen());
document.addEventListener("fullscreenchange", monitorFullscreenState);
return () =>
document.removeEventListener("fullscreenchange", monitorFullscreenState);
}, []);
return {
fullscreen,
toggleFullscreen,
};
};
export default useFullscreen;

View File

@@ -1,5 +1,4 @@
import { useMemo } from "react";
import useFullscreen from "components/apps/Photos/useFullscreen";
import { useMenu } from "contexts/menu";
import type {
ContextMenuCapture,
@@ -9,12 +8,13 @@ import { useProcesses } from "contexts/process";
import { useProcessesRef } from "hooks/useProcessesRef";
import { MENU_SEPERATOR } from "utils/constants";
import { toggleShowDesktop } from "utils/functions";
import { useViewport } from "contexts/viewport";
const useTaskbarContextMenu = (onStartButton = false): ContextMenuCapture => {
const { contextMenu } = useMenu();
const { minimize, open } = useProcesses();
const processesRef = useProcessesRef();
const { fullscreen, toggleFullscreen } = useFullscreen();
const { fullscreenElement, toggleFullscreen } = useViewport();
return useMemo(
() =>
@@ -54,7 +54,10 @@ const useTaskbarContextMenu = (onStartButton = false): ContextMenuCapture => {
menuItems.unshift(
{
action: () => toggleFullscreen(),
label: fullscreen ? "Exit full screen" : "Enter full screen",
label:
fullscreenElement === document.documentElement
? "Exit full screen"
: "Enter full screen",
},
MENU_SEPERATOR
);
@@ -64,7 +67,7 @@ const useTaskbarContextMenu = (onStartButton = false): ContextMenuCapture => {
}),
[
contextMenu,
fullscreen,
fullscreenElement,
minimize,
onStartButton,
open,

View File

@@ -0,0 +1,6 @@
import useViewportContextState from "contexts/viewport/useViewportContextState";
import contextFactory from "contexts/contextFactory";
const { Provider, useContext } = contextFactory(useViewportContextState);
export { Provider as ViewportProvider, useContext as useViewport };

View File

@@ -0,0 +1,26 @@
export type ViewportContextState = {
fullscreenElement: Element | null;
toggleFullscreen: (
element?: HTMLElement | null,
navigationUI?: FullscreenNavigationUI
) => void;
};
export type FullscreenDocument = Document & {
mozCancelFullScreen: () => Promise<void>;
mozFullScreenElement: Element | null;
webkitExitFullscreen: () => Promise<void>;
webkitFullscreenElement: Element | null;
};
export type FullscreenElement = HTMLElement & {
mozRequestFullScreen?: (options?: FullscreenOptions) => Promise<void>;
webkitRequestFullscreen?: (options?: FullscreenOptions) => Promise<void>;
};
export type NavigatorWithKeyboard = Navigator & {
keyboard?: {
lock?: (keys?: string[]) => Promise<void>;
unlock?: () => void;
};
};

View File

@@ -0,0 +1,106 @@
import { useEffect, useState } from "react";
import type {
ViewportContextState,
FullscreenDocument,
NavigatorWithKeyboard,
FullscreenElement,
} from "contexts/viewport/types";
import { isFirefox } from "utils/functions";
const FULLSCREEN_LOCKED_KEYS = ["MetaLeft", "MetaRight", "Escape"];
const enterFullscreen = async (
element: FullscreenElement,
options: FullscreenOptions
): Promise<void> => {
try {
if (element.requestFullscreen) {
await element.requestFullscreen(options);
} else if (element.mozRequestFullScreen) {
await element.mozRequestFullScreen(options);
} else if (element.webkitRequestFullscreen) {
await element.webkitRequestFullscreen(options);
}
} catch {
// Ignore failure while entering fullscreen
}
};
const exitFullscreen = async (): Promise<void> => {
const fullscreenDocument = document as FullscreenDocument;
try {
if (fullscreenDocument.exitFullscreen) {
await fullscreenDocument.exitFullscreen();
} else if (fullscreenDocument.mozCancelFullScreen) {
await fullscreenDocument.mozCancelFullScreen();
} else if (fullscreenDocument.webkitExitFullscreen) {
await fullscreenDocument.webkitExitFullscreen();
}
} catch {
// Ignore failure while exiting fullscreen
}
};
const toggleKeyboardLock = async (
fullscreenElement: Element | null
): Promise<void> => {
try {
if (fullscreenElement) {
await (navigator as NavigatorWithKeyboard)?.keyboard?.lock?.(
FULLSCREEN_LOCKED_KEYS
);
} else {
(navigator as NavigatorWithKeyboard)?.keyboard?.unlock?.();
}
} catch {
// Ignore failure to lock keys
}
};
const useViewportContextState = (): ViewportContextState => {
const [fullscreenElement, setFullscreenElement] = useState<Element | null>(
// eslint-disable-next-line unicorn/no-null
null
);
const toggleFullscreen = async (
element?: HTMLElement | null,
navigationUI?: FullscreenNavigationUI
): Promise<void> => {
if (fullscreenElement && (!element || element === fullscreenElement)) {
await exitFullscreen();
} else {
if (fullscreenElement && isFirefox()) await exitFullscreen();
await enterFullscreen(element || document.documentElement, {
navigationUI: navigationUI || "hide",
});
}
};
useEffect(() => {
const onFullscreenChange = (): void => {
const { mozFullScreenElement, webkitFullscreenElement } =
document as FullscreenDocument;
const currentFullscreenElement =
document.fullscreenElement ||
mozFullScreenElement ||
webkitFullscreenElement;
toggleKeyboardLock(currentFullscreenElement).then(() =>
setFullscreenElement(currentFullscreenElement)
);
};
document.addEventListener("fullscreenchange", onFullscreenChange, {
passive: true,
});
return () =>
document.removeEventListener("fullscreenchange", onFullscreenChange);
}, []);
return { fullscreenElement, toggleFullscreen };
};
export default useViewportContextState;

View File

@@ -1,16 +1,9 @@
import { useEffect, useRef } from "react";
import useFullscreen from "components/apps/Photos/useFullscreen";
import { useProcesses } from "contexts/process";
import { useSession } from "contexts/session";
import { useProcessesRef } from "hooks/useProcessesRef";
import { haltEvent, toggleShowDesktop } from "utils/functions";
type NavigatorWithKeyboard = Navigator & {
keyboard?: {
lock?: (keys?: string[]) => void;
unlock?: () => void;
};
};
import { useViewport } from "contexts/viewport";
const openStartMenu = (): void =>
(
@@ -42,7 +35,7 @@ const useGlobalKeyboardShortcuts = (): void => {
const { closeWithTransition, maximize, minimize, open } = useProcesses();
const processesRef = useProcessesRef();
const { foregroundId } = useSession();
const { fullscreen, toggleFullscreen } = useFullscreen();
const { fullscreenElement, toggleFullscreen } = useViewport();
const altBindingsRef = useRef<Record<string, () => void>>({});
const shiftBindingsRef = useRef<Record<string, () => void>>({
E: () => open("FileExplorer"),
@@ -82,17 +75,14 @@ const useGlobalKeyboardShortcuts = (): void => {
);
} else if (ctrlKey && altKey && altBindingsRef.current?.[keyName]) {
altBindingsRef.current?.[keyName]?.();
} else if (fullscreen) {
} else if (fullscreenElement === document.documentElement) {
if (keyName === "META") metaDown = true;
else if (altKey && altBindingsRef.current?.[keyName]) {
haltEvent(event);
altBindingsRef.current?.[keyName]?.();
} else if (keyName === "ESCAPE") {
setTimeout(
// eslint-disable-next-line unicorn/consistent-destructuring
() => !event.defaultPrevented && document.exitFullscreen(),
0
);
if (document.pointerLockElement) document.exitPointerLock();
else toggleFullscreen();
} else if (
metaDown &&
metaCombos.has(keyName) &&
@@ -105,29 +95,16 @@ const useGlobalKeyboardShortcuts = (): void => {
}
};
const onKeyUp = (event: KeyboardEvent): void => {
if (metaDown && fullscreen && event.key?.toUpperCase() === "META") {
if (
metaDown &&
fullscreenElement === document.documentElement &&
event.key?.toUpperCase() === "META"
) {
metaDown = false;
if (metaComboUsed) metaComboUsed = false;
else openStartMenu();
}
};
const onFullScreen = ({ target }: Event): void => {
if (target === document.documentElement) {
try {
if (fullscreen) {
(navigator as NavigatorWithKeyboard)?.keyboard?.lock?.([
"MetaLeft",
"MetaRight",
"Escape",
]);
} else {
(navigator as NavigatorWithKeyboard)?.keyboard?.unlock?.();
}
} catch {
// Ignore failure to lock keys
}
}
};
document.addEventListener("keydown", onKeyDown, {
capture: true,
@@ -136,16 +113,16 @@ const useGlobalKeyboardShortcuts = (): void => {
capture: true,
passive: true,
});
document.addEventListener("fullscreenchange", onFullScreen, {
passive: true,
});
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("keyup", onKeyUp);
document.removeEventListener("fullscreenchange", onFullScreen);
document.removeEventListener("keydown", onKeyDown, {
capture: true,
});
document.removeEventListener("keyup", onKeyUp, {
capture: true,
});
};
}, [fullscreen, toggleFullscreen]);
}, [fullscreenElement, toggleFullscreen]);
useEffect(() => {
altBindingsRef.current = {

View File

@@ -6,22 +6,25 @@ import { FileSystemProvider } from "contexts/fileSystem";
import { MenuProvider } from "contexts/menu";
import { ProcessProvider } from "contexts/process";
import { SessionProvider } from "contexts/session";
import { ViewportProvider } from "contexts/viewport";
const App = ({ Component, pageProps }: AppProps): React.ReactElement => (
<ProcessProvider>
<FileSystemProvider>
<SessionProvider>
<ErrorBoundary>
<Metadata />
<StyledApp>
<MenuProvider>
<Component {...pageProps} />
</MenuProvider>
</StyledApp>
</ErrorBoundary>
</SessionProvider>
</FileSystemProvider>
</ProcessProvider>
<ViewportProvider>
<ProcessProvider>
<FileSystemProvider>
<SessionProvider>
<ErrorBoundary>
<Metadata />
<StyledApp>
<MenuProvider>
<Component {...pageProps} />
</MenuProvider>
</StyledApp>
</ErrorBoundary>
</SessionProvider>
</FileSystemProvider>
</ProcessProvider>
</ViewportProvider>
);
export default App;