Add viewerjs support
This commit is contained in:
parent
cc30c2ea49
commit
4a7c988f54
@ -40,7 +40,8 @@
|
||||
"sonner": "^1.7.1",
|
||||
"swr": "^2.3.0",
|
||||
"ts-exif-parser": "^0.2.2",
|
||||
"use-debounce": "^10.0.4"
|
||||
"use-debounce": "^10.0.4",
|
||||
"viewerjs": "^1.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "15.1.4",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -104,6 +104,9 @@ importers:
|
||||
use-debounce:
|
||||
specifier: ^10.0.4
|
||||
version: 10.0.4(react@19.0.0)
|
||||
viewerjs:
|
||||
specifier: ^1.11.7
|
||||
version: 1.11.7
|
||||
devDependencies:
|
||||
'@next/bundle-analyzer':
|
||||
specifier: 15.1.4
|
||||
@ -4255,6 +4258,9 @@ packages:
|
||||
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
|
||||
viewerjs@1.11.7:
|
||||
resolution: {integrity: sha512-0JuVqOmL5v1jmEAlG5EBDR3XquxY8DWFQbFMprOXgaBB0F7Q/X9xWdEaQc59D8xzwkdUgXEMSSknTpriq95igg==}
|
||||
|
||||
vue@3.4.27:
|
||||
resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==}
|
||||
peerDependencies:
|
||||
@ -9467,6 +9473,8 @@ snapshots:
|
||||
'@types/istanbul-lib-coverage': 2.0.6
|
||||
convert-source-map: 2.0.0
|
||||
|
||||
viewerjs@1.11.7: {}
|
||||
|
||||
vue@3.4.27(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.4.27
|
||||
|
||||
@ -77,6 +77,13 @@ export default async function PhotoPage({
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
return (
|
||||
<PhotoDetailPage {...{ photo, photos, photosGrid }} />
|
||||
<PhotoDetailPage
|
||||
{...{
|
||||
photo,
|
||||
photos,
|
||||
photosGrid,
|
||||
enableImageActions: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ export default function ImageLarge(props: ImageProps) {
|
||||
blurCompatibilityLevel: blurCompatibilityMode ? 'high' : 'none',
|
||||
width: IMAGE_WIDTH_LARGE,
|
||||
height: Math.round(IMAGE_WIDTH_LARGE / aspectRatio),
|
||||
allowFullscreen: true,
|
||||
}} />
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,11 +7,14 @@ import { clsx} from 'clsx/lite';
|
||||
import Image, { ImageProps } from 'next/image';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import FullscreenButton from '../FullscreenButton';
|
||||
import Viewer from 'viewerjs';
|
||||
import 'viewerjs/dist/viewer.css';
|
||||
|
||||
export default function ImageWithFallback(props: ImageProps & {
|
||||
blurCompatibilityLevel?: 'none' | 'low' | 'high'
|
||||
imgClassName?: string
|
||||
allowFullscreen?: boolean
|
||||
enableImageActions?: boolean
|
||||
}) {
|
||||
const {
|
||||
className,
|
||||
@ -19,7 +22,7 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
blurDataURL,
|
||||
blurCompatibilityLevel = 'low',
|
||||
imgClassName = 'object-cover h-full',
|
||||
allowFullscreen,
|
||||
enableImageActions = false,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@ -34,7 +37,10 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
|
||||
const [hideFallback, setHideFallback] = useState(false);
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const viewerRef = useRef<Viewer | null>(null);
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
const { isFullscreen } = useAppState();
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(
|
||||
@ -53,6 +59,37 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
}
|
||||
}, [isLoading, didError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current && enableImageActions) {
|
||||
viewerRef.current = new Viewer(containerRef.current, {
|
||||
inline: false,
|
||||
button: true,
|
||||
navbar: false,
|
||||
title: false,
|
||||
toolbar: {
|
||||
zoomIn: 1,
|
||||
zoomOut: 1,
|
||||
oneToOne: 1,
|
||||
reset: 1,
|
||||
prev: 0,
|
||||
play: {
|
||||
show: 0,
|
||||
size: 'large',
|
||||
},
|
||||
next: 0,
|
||||
rotateLeft: 1,
|
||||
rotateRight: 1,
|
||||
flipHorizontal: 1,
|
||||
flipVertical: 1,
|
||||
tooltip: 1,
|
||||
},
|
||||
});
|
||||
return () => {
|
||||
viewerRef.current?.destroy();
|
||||
};
|
||||
}
|
||||
}, [enableImageActions]);
|
||||
|
||||
const showFallback =
|
||||
!wasCached &&
|
||||
!hideFallback;
|
||||
@ -73,6 +110,7 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
className,
|
||||
'flex relative',
|
||||
)}
|
||||
ref={containerRef}
|
||||
>
|
||||
{(showFallback || shouldDebugImageFallbacks) &&
|
||||
<div className={clsx(
|
||||
@ -99,15 +137,18 @@ export default function ImageWithFallback(props: ImageProps & {
|
||||
'bg-gray-100/50 dark:bg-gray-900/50',
|
||||
)} />}
|
||||
</div>}
|
||||
<Image {...{
|
||||
...rest,
|
||||
ref: imgRef,
|
||||
priority,
|
||||
className: imgClassName,
|
||||
onLoad,
|
||||
onError,
|
||||
}} />
|
||||
{allowFullscreen && <FullscreenButton imageRef={imgRef} />}
|
||||
<Image
|
||||
{...rest}
|
||||
ref={imgRef}
|
||||
priority={priority}
|
||||
className={clsx(
|
||||
imgClassName,
|
||||
!isFullscreen && enableImageActions && 'cursor-zoom-in',
|
||||
)}
|
||||
onLoad={onLoad}
|
||||
onError={onError}
|
||||
/>
|
||||
{enableImageActions && <FullscreenButton imageRef={imgRef} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -14,4 +14,5 @@ export interface ImageProps {
|
||||
alt: string
|
||||
blurDataURL?: string
|
||||
priority?: boolean
|
||||
enableImageActions?: boolean
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ export default function PhotoDetailPage({
|
||||
dateRange,
|
||||
shouldShare,
|
||||
includeFavoriteInAdminMenu,
|
||||
enableImageActions,
|
||||
}: {
|
||||
photo: Photo
|
||||
photos: Photo[]
|
||||
@ -34,6 +35,7 @@ export default function PhotoDetailPage({
|
||||
dateRange?: PhotoDateRange
|
||||
shouldShare?: boolean
|
||||
includeFavoriteInAdminMenu?: boolean
|
||||
enableImageActions?: boolean
|
||||
} & PhotoSetCategory) {
|
||||
let customHeader: JSX.Element | undefined;
|
||||
|
||||
@ -112,6 +114,7 @@ export default function PhotoDetailPage({
|
||||
shouldShareSimulation={simulation !== undefined}
|
||||
shouldScrollOnShare={false}
|
||||
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
|
||||
enableImageActions={enableImageActions}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -55,6 +55,7 @@ export default function PhotoLarge({
|
||||
shouldShareFocalLength,
|
||||
includeFavoriteInAdminMenu,
|
||||
onVisible,
|
||||
enableImageActions = false,
|
||||
}: {
|
||||
photo: Photo
|
||||
className?: string
|
||||
@ -75,6 +76,7 @@ export default function PhotoLarge({
|
||||
shouldScrollOnShare?: boolean
|
||||
includeFavoriteInAdminMenu?: boolean
|
||||
onVisible?: () => void
|
||||
enableImageActions?: boolean
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -143,6 +145,7 @@ export default function PhotoLarge({
|
||||
blurDataURL={photo.blurData}
|
||||
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
|
||||
priority={priority}
|
||||
enableImageActions={enableImageActions}
|
||||
/>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user