(null);
+
+ useEffect(() => {
+ if (containerRef.current && enableImageActions) {
+ viewerRef.current = new Viewer(containerRef.current, {
+ inline: false,
+ button: true,
+ navbar: false,
+ title: false,
+ toolbar: {
+ zoomIn: 1,
+ zoomOut: 1,
+ reset: 1,
+ tooltip: 1,
+ },
+ });
+ return () => {
+ viewerRef.current?.destroy();
+ };
+ }
+ }, [enableImageActions]);
+
+ return (
+ <>
+
+
+ {children}
+ {enableImageActions && (
+
+ )}
+
+ >
+ );
+}
diff --git a/src/components/image/ImageLarge.tsx b/src/components/image/ImageLarge.tsx
index 18569d92..339e9ba9 100644
--- a/src/components/image/ImageLarge.tsx
+++ b/src/components/image/ImageLarge.tsx
@@ -15,4 +15,4 @@ export default function ImageLarge(props: ImageProps) {
height: Math.round(IMAGE_WIDTH_LARGE / aspectRatio),
}} />
);
-};
+};
\ No newline at end of file
diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx
index beb67cf4..aab19b82 100644
--- a/src/photo/PhotoDetailPage.tsx
+++ b/src/photo/PhotoDetailPage.tsx
@@ -11,6 +11,7 @@ import HiddenHeader from '@/tag/HiddenHeader';
import FocalLengthHeader from '@/focal/FocalLengthHeader';
import PhotoHeader from './PhotoHeader';
import { JSX } from 'react';
+import { IMAGE_ACTIONS_ENABLED } from '@/site/config';
export default function PhotoDetailPage({
photo,
@@ -112,6 +113,7 @@ export default function PhotoDetailPage({
shouldShareSimulation={simulation !== undefined}
shouldScrollOnShare={false}
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
+ enableImageActions={IMAGE_ACTIONS_ENABLED}
/>,
]}
/>
diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx
index 59fc577a..b0648b27 100644
--- a/src/photo/PhotoLarge.tsx
+++ b/src/photo/PhotoLarge.tsx
@@ -36,6 +36,7 @@ import { useRef } from 'react';
import useOnVisible from '@/utility/useOnVisible';
import PhotoDate from './PhotoDate';
import { useAppState } from '@/state/AppState';
+import ImageActions from '@/components/image/ImageActions';
export default function PhotoLarge({
photo,
@@ -56,6 +57,7 @@ export default function PhotoLarge({
shouldShareFocalLength,
includeFavoriteInAdminMenu,
onVisible,
+ enableImageActions = false,
}: {
photo: Photo
className?: string
@@ -76,6 +78,7 @@ export default function PhotoLarge({
shouldScrollOnShare?: boolean
includeFavoriteInAdminMenu?: boolean
onVisible?: () => void
+ enableImageActions?: boolean
}) {
const ref = useRef(null);
@@ -143,17 +146,22 @@ export default function PhotoLarge({
arePhotosMatted && 'h-[90%]',
arePhotosMatted && matteContentWidthForAspectRatio(),
)}>
-
+
+ alt={altTextForPhoto(photo)}
+ src={photo.url}
+ aspectRatio={photo.aspectRatio}
+ blurDataURL={photo.blurData}
+ blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
+ priority={priority}
+ />
+
}
contentSide={
diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx
index 3b2ee2c9..29438d9d 100644
--- a/src/site/SiteChecklistClient.tsx
+++ b/src/site/SiteChecklistClient.tsx
@@ -78,6 +78,7 @@ export default function SiteChecklistClient({
isPublicApiEnabled,
isPriorityOrderEnabled,
isOgTextBottomAligned,
+ isImageActionsEnabled,
// Misc
baseUrl,
commitSha,
@@ -612,6 +613,15 @@ export default function SiteChecklistClient({
keep OG image text bottom aligned (default is {'"top"'}):
{renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])}
+
+ Set environment variable to {'"1"'} to enable fullscreen and zoom
+ actions when clicking on an image:
+ {renderEnvVars(['NEXT_PUBLIC_IMAGE_ACTIONS'])}
+
>}
diff --git a/src/site/config.ts b/src/site/config.ts
index 70912080..249c59c4 100644
--- a/src/site/config.ts
+++ b/src/site/config.ts
@@ -205,6 +205,8 @@ export const PRIORITY_ORDER_ENABLED =
process.env.NEXT_PUBLIC_IGNORE_PRIORITY_ORDER !== '1';
export const OG_TEXT_BOTTOM_ALIGNMENT =
(process.env.NEXT_PUBLIC_OG_TEXT_ALIGNMENT ?? '').toUpperCase() === 'BOTTOM';
+export const IMAGE_ACTIONS_ENABLED =
+ process.env.NEXT_PUBLIC_IMAGE_ACTIONS === '1';
// INTERNAL
@@ -281,6 +283,7 @@ export const CONFIG_CHECKLIST_STATUS = {
isPublicApiEnabled: PUBLIC_API_ENABLED,
isPriorityOrderEnabled: PRIORITY_ORDER_ENABLED,
isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT,
+ isImageActionsEnabled: IMAGE_ACTIONS_ENABLED,
// MISC
baseUrl: BASE_URL,
commitSha: VERCEL_GIT_COMMIT_SHA_SHORT,
diff --git a/src/state/AppState.ts b/src/state/AppState.ts
index 69975e9e..9dcde3ca 100644
--- a/src/state/AppState.ts
+++ b/src/state/AppState.ts
@@ -39,6 +39,9 @@ export interface AppStateContext {
setShouldDebugImageFallbacks?: Dispatch>
shouldShowBaselineGrid?: boolean
setShouldShowBaselineGrid?: Dispatch>
+ // FULLSCREEN
+ isFullscreen?: boolean
+ setIsFullscreen?: Dispatch>
}
export const AppStateContext = createContext({});
diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx
index e8619fec..0f04d631 100644
--- a/src/state/AppStateProvider.tsx
+++ b/src/state/AppStateProvider.tsx
@@ -52,6 +52,9 @@ export default function AppStateProvider({
useState(false);
const [shouldShowBaselineGrid, setShouldShowBaselineGrid] =
useState(false);
+ // FULLSCREEN
+ const [isFullscreen, setIsFullscreen] =
+ useState(false);
const invalidateSwr = useCallback(() => setSwrTimestamp(Date.now()), []);
@@ -122,6 +125,9 @@ export default function AppStateProvider({
setShouldDebugImageFallbacks,
shouldShowBaselineGrid,
setShouldShowBaselineGrid,
+ // FULLSCREEN
+ isFullscreen,
+ setIsFullscreen,
}}
>
{children}