Add quick zoom button to zoom controls
This commit is contained in:
parent
3c04ca840f
commit
33a430dcfd
64
src/components/image/ZoomControls.tsx
Normal file
64
src/components/image/ZoomControls.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import clsx from 'clsx/lite';
|
||||
import { ReactNode, RefObject, useEffect, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import useImageZoomControls from './useImageZoomControls';
|
||||
import { RiCollapseDiagonalLine, RiExpandDiagonalLine } from 'react-icons/ri';
|
||||
|
||||
export type ZoomControlsRef = {
|
||||
open: () => void
|
||||
zoom: (zoomLevel?: number) => void
|
||||
}
|
||||
|
||||
export default function ZoomControls({
|
||||
ref,
|
||||
children,
|
||||
isEnabled,
|
||||
shouldZoomOnFKeydown,
|
||||
}: {
|
||||
ref?: RefObject<ZoomControlsRef | null>
|
||||
children: ReactNode
|
||||
isEnabled?: boolean
|
||||
shouldZoomOnFKeydown?: boolean
|
||||
}) {
|
||||
const refContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { open, zoom, zoomLevel, isShown } = useImageZoomControls(
|
||||
refContainer,
|
||||
isEnabled,
|
||||
shouldZoomOnFKeydown,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) { ref.current = { open, zoom }; }
|
||||
}, [ref, open, zoom]);
|
||||
|
||||
const shouldZoomTo2x = zoomLevel < 2;
|
||||
|
||||
const button =
|
||||
<button
|
||||
className={clsx(
|
||||
isShown ? 'inline-flex' : 'hidden',
|
||||
'fixed top-[20px] right-[70px] z-[100000]',
|
||||
'size-10 items-center justify-center',
|
||||
'rounded-full border-none',
|
||||
'text-white bg-black/50 hover:bg-black/85',
|
||||
)}
|
||||
onClick={() => zoom(shouldZoomTo2x ? 2 : 1)}
|
||||
>
|
||||
{shouldZoomTo2x
|
||||
? <RiCollapseDiagonalLine className="shrink-0" size={20} />
|
||||
: <RiExpandDiagonalLine className="shrink-0" size={20} />}
|
||||
</button>;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={refContainer}
|
||||
className={clsx('h-full', isEnabled && 'cursor-zoom-in')}
|
||||
>
|
||||
{children}
|
||||
{typeof window !== 'undefined'
|
||||
? createPortal(button, document.body)
|
||||
: button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -13,12 +13,36 @@ export default function useImageZoomControls(
|
||||
|
||||
const { setShouldRespondToKeyboardCommands } = useAppState();
|
||||
|
||||
const [isShown, setIsShown] = useState(false);
|
||||
const [zoomLevel, setZoomLevel] = useState(1);
|
||||
const [colorLight, setColorLight] = useState<string>();
|
||||
|
||||
useMetaThemeColor({ colorLight });
|
||||
|
||||
const open = useCallback(() => {
|
||||
viewerRef.current?.show();
|
||||
}, [viewerRef]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
viewerRef.current?.hide();
|
||||
}, [viewerRef]);
|
||||
|
||||
const zoom = useCallback((zoomLevel = 1) => {
|
||||
viewerRef.current?.zoomTo(zoomLevel);
|
||||
}, [viewerRef]);
|
||||
|
||||
// On 'F' keydown, toggle fullscreen
|
||||
const handleKeyDown = useCallback(() => {
|
||||
if (shouldExpandOnFKeydown) {
|
||||
viewerRef.current?.show();
|
||||
}
|
||||
}, [shouldExpandOnFKeydown]);
|
||||
useKeydownHandler(handleKeyDown, ['F']);
|
||||
|
||||
useEffect(() => {
|
||||
if (imageRef.current && isEnabled) {
|
||||
const closeButton = document
|
||||
.getElementsByClassName('viewer-close')[0] as HTMLElement;
|
||||
viewerRef.current = new Viewer(imageRef.current, {
|
||||
navbar: false,
|
||||
title: false,
|
||||
@ -34,39 +58,49 @@ export default function useImageZoomControls(
|
||||
show: () => {
|
||||
setShouldRespondToKeyboardCommands?.(false);
|
||||
setColorLight('#000');
|
||||
setIsShown(true);
|
||||
if (closeButton) { closeButton.style.display = 'none'; }
|
||||
},
|
||||
hide: () => {
|
||||
setTimeout(() => setColorLight(undefined), 300);
|
||||
setTimeout(() => {
|
||||
setColorLight(undefined);
|
||||
setIsShown(false);
|
||||
}, 300);
|
||||
},
|
||||
hidden: () => {
|
||||
setShouldRespondToKeyboardCommands?.(true);
|
||||
},
|
||||
zoom: ({ detail: { ratio } }) => {
|
||||
setZoomLevel(ratio);
|
||||
},
|
||||
view: () => {
|
||||
const container = document
|
||||
.getElementsByClassName('viewer-container')[0];
|
||||
if (container) {
|
||||
const closeButton = document
|
||||
.getElementsByClassName('viewer-close')[0] as HTMLElement;
|
||||
if (closeButton) { closeButton.style.display = 'inline-flex'; }
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
viewerRef.current?.destroy();
|
||||
viewerRef.current = null;
|
||||
};
|
||||
}
|
||||
}, [imageRef, isEnabled, setShouldRespondToKeyboardCommands]);
|
||||
|
||||
const open = useCallback(() => {
|
||||
viewerRef.current?.show();
|
||||
}, [viewerRef]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
viewerRef.current?.hide();
|
||||
}, [viewerRef]);
|
||||
|
||||
// On 'F' keydown, toggle fullscreen
|
||||
const handleKeyDown = useCallback(() => {
|
||||
if (shouldExpandOnFKeydown) {
|
||||
viewerRef.current?.show();
|
||||
}
|
||||
}, [shouldExpandOnFKeydown]);
|
||||
useKeydownHandler(handleKeyDown, ['F']);
|
||||
}, [
|
||||
imageRef,
|
||||
isEnabled,
|
||||
zoom,
|
||||
setShouldRespondToKeyboardCommands,
|
||||
]);
|
||||
|
||||
return {
|
||||
open,
|
||||
close,
|
||||
zoom,
|
||||
zoomLevel,
|
||||
isShown,
|
||||
};
|
||||
}
|
||||
|
||||
@ -36,10 +36,10 @@ import { useRef } from 'react';
|
||||
import useVisible from '@/utility/useVisible';
|
||||
import PhotoDate from './PhotoDate';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import useImageZoomControls from '@/components/image/useImageZoomControls';
|
||||
import { LuExpand } from 'react-icons/lu';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import ZoomControls, { ZoomControlsRef } from '@/components/image/ZoomControls';
|
||||
|
||||
export default function PhotoLarge({
|
||||
photo,
|
||||
@ -85,7 +85,8 @@ export default function PhotoLarge({
|
||||
onVisible?: () => void
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const refZoomControlsContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const zoomControlsRef = useRef<ZoomControlsRef>(null);
|
||||
|
||||
const {
|
||||
areZoomControlsShown,
|
||||
@ -105,12 +106,6 @@ export default function PhotoLarge({
|
||||
|
||||
useVisible({ ref, onVisible });
|
||||
|
||||
const { open } = useImageZoomControls(
|
||||
refZoomControlsContainer,
|
||||
showZoomControls,
|
||||
shouldZoomOnFKeydown,
|
||||
);
|
||||
|
||||
const hasTitle =
|
||||
showTitle &&
|
||||
Boolean(photo.title);
|
||||
@ -152,9 +147,9 @@ export default function PhotoLarge({
|
||||
arePhotosMatted && 'h-[90%]',
|
||||
arePhotosMatted && matteContentWidthForAspectRatio(),
|
||||
)}>
|
||||
<div
|
||||
ref={refZoomControlsContainer}
|
||||
className={clsx('h-full', showZoomControls && 'cursor-zoom-in')}
|
||||
<ZoomControls
|
||||
ref={zoomControlsRef}
|
||||
{...{ isEnabled: showZoomControls, shouldZoomOnFKeydown }}
|
||||
>
|
||||
<ImageLarge
|
||||
className={clsx(arePhotosMatted && 'h-full')}
|
||||
@ -167,7 +162,7 @@ export default function PhotoLarge({
|
||||
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
|
||||
priority={priority}
|
||||
/>
|
||||
</div>
|
||||
</ZoomControls>
|
||||
</div>;
|
||||
|
||||
const largePhotoContainerClassName = clsx(arePhotosMatted &&
|
||||
@ -297,9 +292,9 @@ export default function PhotoLarge({
|
||||
// Prevent collision with admin button
|
||||
!hasNonDateContent && isUserSignedIn && 'md:pr-7',
|
||||
)}
|
||||
// Created at is a naive datetime which
|
||||
// 'createdAt' is a naive datetime which
|
||||
// does not require a timezone and will not
|
||||
// cause server/client time mismatch
|
||||
// cause server/client time mismatches
|
||||
timezone={null}
|
||||
hideTime={!SHOW_TAKEN_AT_TIME}
|
||||
/>
|
||||
@ -311,7 +306,7 @@ export default function PhotoLarge({
|
||||
<LoaderButton
|
||||
title="Open Image Viewer"
|
||||
icon={<LuExpand size={15} />}
|
||||
onClick={open}
|
||||
onClick={() => zoomControlsRef.current?.open()}
|
||||
styleAs="link"
|
||||
className="text-medium translate-y-[0.25px]"
|
||||
hideFocusOutline
|
||||
@ -322,10 +317,12 @@ export default function PhotoLarge({
|
||||
photo={photo}
|
||||
tag={shouldShareTag ? primaryTag : undefined}
|
||||
camera={shouldShareCamera ? camera : undefined}
|
||||
// eslint-disable-next-line max-len
|
||||
simulation={shouldShareSimulation? photo.filmSimulation : undefined}
|
||||
// eslint-disable-next-line max-len
|
||||
focal={shouldShareFocalLength ? photo.focalLength : undefined}
|
||||
simulation={shouldShareSimulation
|
||||
? photo.filmSimulation
|
||||
: undefined}
|
||||
focal={shouldShareFocalLength
|
||||
? photo.focalLength
|
||||
: undefined}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>}
|
||||
{ALLOW_PUBLIC_DOWNLOADS &&
|
||||
|
||||
Loading…
Reference in New Issue
Block a user