Refine image zoom ref handling
This commit is contained in:
parent
0ee0e120ca
commit
b57283e428
@ -300,7 +300,7 @@ Vercel Postgres can be switched to another Postgres-compatible, pooling provider
|
||||
> If you don't see a recipe, first try syncing your photo from the ••• menu, or from `/admin/photos`. If the data looks incorrect, open an issue with the file in question attached in order for it to be investigated. Fujifilm file specifications have evolved over time and recipe parsing may need to be adjusted based on camera model/vintage.
|
||||
|
||||
#### How do I hide Fujifilm content such as a recipes and film simulations?
|
||||
> This can be accomplished by setting `NEXT_PUBLIC_CATEGORY_VISIBILITY` (which has a default value of `tags, cameras, recipes, simulations`) to simply `tags, cameras`.
|
||||
> This can be accomplished by setting `NEXT_PUBLIC_CATEGORY_VISIBILITY` (which has a default value of `tags,cameras,lenses,recipes,films`) to `tags,cameras,lenses`.
|
||||
|
||||
#### Why do my images appear flipped/rotated incorrectly?
|
||||
> For a number of reasons, only EXIF orientations: 1, 3, 6, and 8 are supported. Orientations 2, 4, 5, and 7—which make use of mirroring—are not supported.
|
||||
|
||||
@ -7,19 +7,17 @@ import { clsx} from 'clsx/lite';
|
||||
import Image, { ImageProps } from 'next/image';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function ImageWithFallback(props: ImageProps & {
|
||||
export default function ImageWithFallback({
|
||||
className,
|
||||
classNameImage = 'object-cover h-full',
|
||||
priority,
|
||||
blurDataURL,
|
||||
blurCompatibilityLevel = 'low',
|
||||
...props
|
||||
}: ImageProps & {
|
||||
blurCompatibilityLevel?: 'none' | 'low' | 'high'
|
||||
classNameImage?: string
|
||||
}) {
|
||||
const {
|
||||
className,
|
||||
classNameImage = 'object-cover h-full',
|
||||
priority,
|
||||
blurDataURL,
|
||||
blurCompatibilityLevel = 'low',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { shouldDebugImageFallbacks } = useAppState();
|
||||
|
||||
const [wasCached, setWasCached] = useState(true);
|
||||
@ -72,7 +70,7 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
)}
|
||||
>
|
||||
<Image {...{
|
||||
...rest,
|
||||
...props,
|
||||
ref: imgRef,
|
||||
priority,
|
||||
className: classNameImage,
|
||||
@ -81,7 +79,7 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
}} />
|
||||
<div className={clsx(
|
||||
'@container',
|
||||
'absolute inset-0',
|
||||
'absolute inset-0 pointer-events-none',
|
||||
'overflow-hidden',
|
||||
(showFallback || shouldDebugImageFallbacks) &&
|
||||
'transition-opacity duration-300 ease-in',
|
||||
@ -92,7 +90,7 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
)}>
|
||||
{(BLUR_ENABLED && blurDataURL)
|
||||
? <img {...{
|
||||
...rest,
|
||||
...props,
|
||||
src: blurDataURL,
|
||||
className: clsx(
|
||||
getBlurClass(),
|
||||
|
||||
@ -12,27 +12,27 @@ export type ZoomControlsRef = {
|
||||
export default function ZoomControls({
|
||||
ref,
|
||||
children,
|
||||
isEnabled,
|
||||
shouldZoomOnFKeydown,
|
||||
...props
|
||||
}: {
|
||||
ref?: RefObject<ZoomControlsRef | null>
|
||||
children: ReactNode
|
||||
selectImageElement?:
|
||||
(container: HTMLElement | null) => HTMLImageElement | null
|
||||
isEnabled?: boolean
|
||||
shouldZoomOnFKeydown?: boolean
|
||||
}) {
|
||||
const refContainer = useRef<HTMLDivElement>(null);
|
||||
const refImageContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
open,
|
||||
reset,
|
||||
zoomTo,
|
||||
zoomLevel,
|
||||
viewerContainerRef,
|
||||
} = useImageZoomControls(
|
||||
refContainer,
|
||||
isEnabled,
|
||||
shouldZoomOnFKeydown,
|
||||
);
|
||||
refViewerContainer,
|
||||
} = useImageZoomControls({
|
||||
refImageContainer,
|
||||
...props,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) { ref.current = { open, zoomTo }; }
|
||||
@ -57,12 +57,12 @@ export default function ZoomControls({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={refContainer}
|
||||
className={clsx('h-full', isEnabled && 'cursor-zoom-in')}
|
||||
ref={refImageContainer}
|
||||
className={clsx('h-full', props.isEnabled && 'cursor-zoom-in')}
|
||||
>
|
||||
{children}
|
||||
{viewerContainerRef.current
|
||||
? createPortal(button, viewerContainerRef.current)
|
||||
{refViewerContainer.current
|
||||
? createPortal(button, refViewerContainer.current)
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,17 +1,28 @@
|
||||
import useMetaThemeColor from '@/utility/useMetaThemeColor';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ComponentProps,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import Viewer from 'viewerjs';
|
||||
import ZoomControls from './ZoomControls';
|
||||
|
||||
export default function useImageZoomControls(
|
||||
imageRef: RefObject<HTMLDivElement | null>,
|
||||
isEnabled?: boolean,
|
||||
shouldExpandOnFKeydown?: boolean,
|
||||
) {
|
||||
export default function useImageZoomControls({
|
||||
refImageContainer,
|
||||
selectImageElement,
|
||||
isEnabled,
|
||||
shouldZoomOnFKeydown,
|
||||
} : {
|
||||
refImageContainer: RefObject<HTMLElement | null>
|
||||
} & Omit<ComponentProps<typeof ZoomControls>, 'ref' | 'children'>) {
|
||||
const viewerRef = useRef<Viewer | null>(null);
|
||||
|
||||
const viewerContainerRef = useRef<HTMLDivElement>(null);
|
||||
const refViewerContainer = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { setShouldRespondToKeyboardCommands } = useAppState();
|
||||
|
||||
@ -37,69 +48,68 @@ export default function useImageZoomControls(
|
||||
|
||||
// On 'F' keydown, toggle fullscreen
|
||||
const handleKeyDown = useCallback(() => {
|
||||
if (shouldExpandOnFKeydown) { open(); }
|
||||
}, [shouldExpandOnFKeydown, open]);
|
||||
if (shouldZoomOnFKeydown) { open(); }
|
||||
}, [shouldZoomOnFKeydown, open]);
|
||||
useKeydownHandler(handleKeyDown, ['F']);
|
||||
|
||||
const initialize = useCallback(() => {
|
||||
if (imageRef.current && isEnabled) {
|
||||
viewerRef.current = new Viewer(imageRef.current, {
|
||||
navbar: false,
|
||||
title: false,
|
||||
toolbar: {
|
||||
zoomIn: 1,
|
||||
reset: 2,
|
||||
zoomOut: 3,
|
||||
},
|
||||
ready: ({ target }) => {
|
||||
viewerContainerRef.current =
|
||||
(target as any).viewer.viewer as HTMLDivElement;
|
||||
},
|
||||
url: (image: HTMLImageElement) => {
|
||||
// Addresses Safari bug where images don't load
|
||||
image.loading = 'eager';
|
||||
return image.src;
|
||||
},
|
||||
show: () => {
|
||||
setShouldRespondToKeyboardCommands?.(false);
|
||||
setColorLight('#000');
|
||||
},
|
||||
hide: () => {
|
||||
// Optimizes Safari status bar animation
|
||||
setTimeout(() => setColorLight(undefined), 300);
|
||||
},
|
||||
hidden: () => {
|
||||
setShouldRespondToKeyboardCommands?.(true);
|
||||
},
|
||||
zoom: ({ detail: { ratio } }) => {
|
||||
setZoomLevel(ratio);
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
if (isEnabled) {
|
||||
const imageRef = (
|
||||
selectImageElement?.(refImageContainer.current) ??
|
||||
refImageContainer.current
|
||||
);
|
||||
if (imageRef) {
|
||||
viewerRef.current = new Viewer(imageRef, {
|
||||
navbar: false,
|
||||
title: false,
|
||||
toolbar: {
|
||||
zoomIn: 1,
|
||||
reset: 2,
|
||||
zoomOut: 3,
|
||||
},
|
||||
ready: ({ target }) => {
|
||||
refViewerContainer.current =
|
||||
(target as any).viewer.viewer as HTMLDivElement;
|
||||
},
|
||||
url: (image: HTMLImageElement) => {
|
||||
// Addresses Safari bug where images don't load
|
||||
image.loading = 'eager';
|
||||
return image.src;
|
||||
},
|
||||
show: () => {
|
||||
setShouldRespondToKeyboardCommands?.(false);
|
||||
setColorLight('#000');
|
||||
},
|
||||
hide: () => {
|
||||
// Optimizes Safari status bar animation
|
||||
setTimeout(() => setColorLight(undefined), 300);
|
||||
},
|
||||
hidden: () => {
|
||||
setShouldRespondToKeyboardCommands?.(true);
|
||||
},
|
||||
zoom: ({ detail: { ratio } }) => {
|
||||
setZoomLevel(ratio);
|
||||
},
|
||||
});
|
||||
return () => {
|
||||
viewerRef.current?.destroy();
|
||||
viewerRef.current = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [
|
||||
imageRef,
|
||||
isEnabled,
|
||||
refImageContainer,
|
||||
selectImageElement,
|
||||
setShouldRespondToKeyboardCommands,
|
||||
]);
|
||||
|
||||
const cleanUp = useCallback(() => {
|
||||
viewerRef.current?.destroy();
|
||||
viewerRef.current = null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
return cleanUp;
|
||||
}, [initialize, cleanUp]);
|
||||
|
||||
return {
|
||||
initialize,
|
||||
cleanUp,
|
||||
open,
|
||||
close,
|
||||
reset,
|
||||
zoomTo,
|
||||
zoomLevel,
|
||||
viewerContainerRef,
|
||||
refViewerContainer,
|
||||
};
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ import {
|
||||
} from '@/app/config';
|
||||
import AdminPhotoMenu from '@/admin/AdminPhotoMenu';
|
||||
import { RevalidatePhoto } from './InfinitePhotoScroll';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import useVisible from '@/utility/useVisible';
|
||||
import PhotoDate from './PhotoDate';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
@ -67,7 +67,7 @@ export default function PhotoLarge({
|
||||
showLens = true,
|
||||
showFilm = true,
|
||||
showRecipe = true,
|
||||
showZoomControls: showZoomControlsProp = true,
|
||||
showZoomControls: _showZoomControls = true,
|
||||
shouldZoomOnFKeydown = true,
|
||||
shouldShare = true,
|
||||
shouldShareCamera,
|
||||
@ -123,7 +123,13 @@ export default function PhotoLarge({
|
||||
filmCount,
|
||||
} = useCategoryCountsForPhoto(photo);
|
||||
|
||||
const showZoomControls = showZoomControlsProp && areZoomControlsShown;
|
||||
const showZoomControls = _showZoomControls && areZoomControlsShown;
|
||||
const selectZoomImageElement = useCallback(
|
||||
(container: HTMLElement | null) => Array
|
||||
.from(container?.getElementsByTagName('img') ?? [])
|
||||
// Ignore fallback blur images
|
||||
.filter((img) => !img.src.startsWith('data:image'))[0]
|
||||
, []);
|
||||
|
||||
const refRecipe = useRef<HTMLDivElement>(null);
|
||||
const refRecipeButton = useRef<HTMLButtonElement>(null);
|
||||
@ -200,6 +206,7 @@ export default function PhotoLarge({
|
||||
)}>
|
||||
<ZoomControls
|
||||
ref={zoomControlsRef}
|
||||
selectImageElement={selectZoomImageElement}
|
||||
{...{ isEnabled: showZoomControls, shouldZoomOnFKeydown }}
|
||||
>
|
||||
<ImageLarge
|
||||
|
||||
Loading…
Reference in New Issue
Block a user