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