Consolidate event handling to zoom hook

This commit is contained in:
Sam Becker 2025-01-26 12:54:20 -06:00
parent 2195379b74
commit bbe49d3a0d
4 changed files with 45 additions and 79 deletions

View File

@ -1,69 +0,0 @@
'use client';
import { useEffect, useCallback, RefObject } from 'react';
import { MdFullscreen, MdFullscreenExit } from 'react-icons/md';
import { clsx } from 'clsx/lite';
import { useAppState } from '@/state/AppState';
import LoaderButton from './primitives/LoaderButton';
export default function FullscreenButton({
className,
imageRef,
}: {
className?: string;
imageRef: RefObject<HTMLDivElement | null>;
}) {
const { isFullscreen, setIsFullscreen, isCommandKOpen } = useAppState();
// Toggle fullscreen mode
const toggleFullscreen = useCallback(async () => {
if (!document.fullscreenElement) {
if (isCommandKOpen) return;
await imageRef.current?.requestFullscreen();
setIsFullscreen?.(true);
} else {
await document.exitFullscreen();
setIsFullscreen?.(false);
}
}, [imageRef, setIsFullscreen, isCommandKOpen]);
// Toggle fullscreen on 'f' key press
const handleKeyDown = useCallback((event: KeyboardEvent) => {
if (event.key === 'f' || event.key === 'F') {
toggleFullscreen();
}
}, [toggleFullscreen]);
// Handle fullscreen change (e.g, switching tabs in fullscreen mode)
const handleFullscreenChange = useCallback(() => {
if (!document.fullscreenElement) {
setIsFullscreen?.(false);
}
}, [setIsFullscreen]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, [handleKeyDown, handleFullscreenChange]);
return (
<LoaderButton
title="Toggle Fullscreen"
className={clsx(
className,
'text-medium absolute bottom-2 right-2 bg-white p-2 rounded',
'hidden md:block',
)}
icon={isFullscreen ? <MdFullscreenExit size={18} />
: <MdFullscreen size={18} />}
spinnerColor="light-gray"
styleAs="link"
onClick={toggleFullscreen}
/>
);
}

View File

@ -1,12 +1,19 @@
import { RefObject, useEffect, useRef } from 'react';
import { useAppState } from '@/state/AppState';
import { RefObject, useCallback, useEffect, useRef } from 'react';
import Viewer from 'viewerjs';
const EVENT_SHOWN = 'shown';
const EVENT_HIDDEN = 'hidden';
const EVENT_KEYDOWN = 'keydown';
export default function useImageZoomControls(
imageRef: RefObject<HTMLDivElement | null>,
isEnabled?: boolean,
) {
const viewerRef = useRef<Viewer | null>(null);
const { isCommandKOpen, setShouldRespondToKeyboardCommands } = useAppState();
useEffect(() => {
if (imageRef.current && isEnabled) {
viewerRef.current = new Viewer(imageRef.current, {
@ -26,4 +33,41 @@ export default function useImageZoomControls(
};
}
}, [imageRef, isEnabled]);
// On shown, disable keyboard commands
const onShown = useCallback(() =>
setShouldRespondToKeyboardCommands?.(false),
[setShouldRespondToKeyboardCommands]);
useEffect(() => {
const imageRefCurrent = imageRef.current;
imageRefCurrent?.addEventListener(EVENT_SHOWN, onShown);
return () => {
imageRefCurrent?.removeEventListener(EVENT_SHOWN, onShown);
};
}, [imageRef, onShown]);
// On hide, reenable keyboard commands
const onHide = useCallback(() =>
setShouldRespondToKeyboardCommands?.(true),
[setShouldRespondToKeyboardCommands]);
useEffect(() => {
const imageRefCurrent = imageRef.current;
imageRefCurrent?.addEventListener(EVENT_HIDDEN, onHide);
return () => {
imageRefCurrent?.removeEventListener(EVENT_HIDDEN, onHide);
};
}, [imageRef, onHide]);
// On 'F' keydown, toggle fullscreen
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (!isCommandKOpen && e.key.toUpperCase() === 'F') {
viewerRef.current?.show();
}
}, [isCommandKOpen]);
useEffect(() => {
document.addEventListener(EVENT_KEYDOWN, handleKeyDown);
return () => {
document.removeEventListener(EVENT_KEYDOWN, handleKeyDown);
};
}, [handleKeyDown]);
}

View File

@ -39,9 +39,6 @@ export interface AppStateContext {
setShouldDebugImageFallbacks?: Dispatch<SetStateAction<boolean>>
shouldShowBaselineGrid?: boolean
setShouldShowBaselineGrid?: Dispatch<SetStateAction<boolean>>
// FULLSCREEN
isFullscreen?: boolean
setIsFullscreen?: Dispatch<SetStateAction<boolean>>
}
export const AppStateContext = createContext<AppStateContext>({});

View File

@ -52,9 +52,6 @@ export default function AppStateProvider({
useState(false);
const [shouldShowBaselineGrid, setShouldShowBaselineGrid] =
useState(false);
// FULLSCREEN
const [isFullscreen, setIsFullscreen] =
useState(false);
const invalidateSwr = useCallback(() => setSwrTimestamp(Date.now()), []);
@ -125,9 +122,6 @@ export default function AppStateProvider({
setShouldDebugImageFallbacks,
shouldShowBaselineGrid,
setShouldShowBaselineGrid,
// FULLSCREEN
isFullscreen,
setIsFullscreen,
}}
>
{children}