From 32c659414a471e7e2ece52d5612f7938b931dad1 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 9 May 2024 12:31:57 -0500 Subject: [PATCH 1/5] Add basic matte views to large photos --- src/components/CommandKClient.tsx | 12 ++++++++++++ src/components/ImageBlurFallback.tsx | 10 +++++----- src/components/ImageLarge.tsx | 3 +++ src/photo/PhotoLarge.tsx | 19 +++++++++++++++++-- src/site/globals.css | 4 ++++ src/state/AppState.ts | 4 ++++ src/state/AppStateProvider.tsx | 6 +++++- 7 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index 7c566112..0f538e3d 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -70,6 +70,8 @@ export default function CommandKClient({ isUserSignedIn, setUserEmail, isCommandKOpen: isOpen, + matteSetting, + setMatteSetting, shouldShowBaselineGrid, shouldDebugBlur, setIsCommandKOpen: setIsOpen, @@ -197,6 +199,16 @@ export default function CommandKClient({ heading: 'Debug Tools', accessory: , items: [{ + label: 'Toggle Matte Setting', + action: () => setMatteSetting?.(prev => { + if (!prev) { + return 'light'; + } else if (prev === 'light') { + return 'dark'; + } + }), + annotation: Boolean(matteSetting) ? : undefined, + }, { label: 'Toggle Blur Debug', action: () => setShouldDebugBlur?.(prev => !prev), annotation: shouldDebugBlur ? : undefined, diff --git a/src/components/ImageBlurFallback.tsx b/src/components/ImageBlurFallback.tsx index acdd58fc..9007309c 100644 --- a/src/components/ImageBlurFallback.tsx +++ b/src/components/ImageBlurFallback.tsx @@ -8,13 +8,15 @@ import Image, { ImageProps } from 'next/image'; import { useCallback, useEffect, useRef, useState } from 'react'; export default function ImageBlurFallback(props: ImageProps & { - blurCompatibilityLevel?: 'none' | 'low' | 'high'; + blurCompatibilityLevel?: 'none' | 'low' | 'high' + imgClassName?: string }) { const { className, priority, blurDataURL, blurCompatibilityLevel = 'low', + imgClassName = 'object-cover h-full', ...rest } = props; @@ -29,8 +31,6 @@ export default function ImageBlurFallback(props: ImageProps & { const [hideBlurPlaceholder, setHideBlurPlaceholder] = useState(false); - const imageClassName = 'object-cover h-full'; - const imgRef = useRef(null); useEffect(() => { @@ -84,7 +84,7 @@ export default function ImageBlurFallback(props: ImageProps & { ...rest, src: blurDataURL, className: clsx( - imageClassName, + imgClassName, getBlurClass(), ), }} /> @@ -97,7 +97,7 @@ export default function ImageBlurFallback(props: ImageProps & { ...rest, ref: imgRef, priority, - className: imageClassName, + className: imgClassName, onLoad, onError, }} /> diff --git a/src/components/ImageLarge.tsx b/src/components/ImageLarge.tsx index 4740e7c5..cffdcb6c 100644 --- a/src/components/ImageLarge.tsx +++ b/src/components/ImageLarge.tsx @@ -3,6 +3,7 @@ import ImageBlurFallback from './ImageBlurFallback'; export default function ImageLarge({ className, + imgClassName, src, alt, aspectRatio, @@ -11,6 +12,7 @@ export default function ImageLarge({ priority, }: { className?: string + imgClassName?: string src: string alt: string aspectRatio: number @@ -21,6 +23,7 @@ export default function ImageLarge({ return ( = 1 ? 'max-h-[80%]' : 'max-h-[90%]' + )} alt={altTextForPhoto(photo)} src={photo.url} aspectRatio={photo.aspectRatio} diff --git a/src/site/globals.css b/src/site/globals.css index c4b8cf43..4ab0d2a9 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -162,6 +162,10 @@ @apply bg-gray-900 dark:bg-gray-100 } + .bg-dim { + @apply + bg-gray-200/40 dark:bg-gray-800/40 + } /* Utilities: Baseline Grid */ .space-y-baseline { @apply diff --git a/src/state/AppState.ts b/src/state/AppState.ts index 83f3ceb4..5e9c8f95 100644 --- a/src/state/AppState.ts +++ b/src/state/AppState.ts @@ -1,9 +1,13 @@ import { Dispatch, SetStateAction, createContext, useContext } from 'react'; import { AnimationConfig } from '@/components/AnimateItems'; +export type MatteSetting = 'light' | 'dark' | undefined; + export interface AppStateContext { previousPathname?: string hasLoaded?: boolean + matteSetting?: MatteSetting + setMatteSetting?: Dispatch> swrTimestamp?: number invalidateSwr?: () => void userEmail?: string diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx index 3504c89d..69607565 100644 --- a/src/state/AppStateProvider.tsx +++ b/src/state/AppStateProvider.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect, ReactNode, useCallback } from 'react'; -import { AppStateContext } from './AppState'; +import { AppStateContext, MatteSetting } from './AppState'; import { AnimationConfig } from '@/components/AnimateItems'; import usePathnames from '@/utility/usePathnames'; import { getAuthAction } from '@/auth/actions'; @@ -16,6 +16,8 @@ export default function AppStateProvider({ const [hasLoaded, setHasLoaded] = useState(false); + const [matteSetting, setMatteSetting] = + useState(); const [swrTimestamp, setSwrTimestamp] = useState(Date.now()); const [userEmail, setUserEmail] = @@ -50,6 +52,8 @@ export default function AppStateProvider({ value={{ previousPathname, hasLoaded, + matteSetting, + setMatteSetting, swrTimestamp, invalidateSwr, setHasLoaded, From a76fa30331e47ad3acee0d9bf25edf6f5b51941a Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 9 May 2024 13:40:46 -0500 Subject: [PATCH 2/5] Document matte setting --- README.md | 1 + src/components/ChecklistRow.tsx | 2 +- src/site/SiteChecklistClient.tsx | 14 ++++++++++++++ src/site/config.ts | 7 +++++++ src/state/AppStateProvider.tsx | 3 ++- 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d22110fc..940cc140 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Application behavior can be changed by configuring the following environment var - `NEXT_PUBLIC_PRO_MODE = 1` enables higher quality image storage (results in increased storage usage) - `NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES = 1` enables static optimization for pages, i.e., renders pages at build time (results in increased project usage)—⚠️ _Experimental_ - `NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES = 1` enables static optimization for OG images, i.e., renders images at build time (results in increased project usage)—⚠️ _Experimental_ +- `NEXT_PUBLIC_MATTE_SETTING = light` constrains the size of each photo, and enables a surrounding border (can be set to `light` or `dark`) - `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage) - `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data - `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order diff --git a/src/components/ChecklistRow.tsx b/src/components/ChecklistRow.tsx index efec5c21..4b4c68e4 100644 --- a/src/components/ChecklistRow.tsx +++ b/src/components/ChecklistRow.tsx @@ -29,7 +29,7 @@ export default function ChecklistRow({ />
{title} diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 546ce113..e07129cb 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -44,6 +44,7 @@ export default function SiteChecklistClient({ isStaticallyOptimized, arePagesStaticallyOptimized, areOGImagesStaticallyOptimized, + matteSetting, isBlurEnabled, isGeoPrivacyEnabled, isPriorityOrderEnabled, @@ -377,6 +378,19 @@ export default function SiteChecklistClient({ 'translate-y-[4.5px]', )} + + Set environment variable to {'"light"'} or {'"dark"'} to + {' '} + constrain the size of each photo, and enable a surrounding border: + {renderEnvVars(['NEXT_PUBLIC_MATTE_SETTING'])} + (); + useState(MATTE_SETTING); const [swrTimestamp, setSwrTimestamp] = useState(Date.now()); const [userEmail, setUserEmail] = From 577371e28fa2045210a665cc59e1774e2a97831e Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 9 May 2024 19:09:05 -0500 Subject: [PATCH 3/5] Refine photo matte implementation --- README.md | 2 +- src/components/CommandKClient.tsx | 16 ++++------- src/components/ImageBlurFallback.tsx | 3 +- src/photo/PhotoLarge.tsx | 42 +++++++++++++++------------- src/site/SiteChecklistClient.tsx | 26 ++++++++--------- src/site/config.ts | 10 ++----- src/site/globals.css | 4 --- src/state/AppState.ts | 6 ++-- src/state/AppStateProvider.tsx | 12 ++++---- 9 files changed, 53 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 940cc140..55ec381e 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Application behavior can be changed by configuring the following environment var - `NEXT_PUBLIC_PRO_MODE = 1` enables higher quality image storage (results in increased storage usage) - `NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES = 1` enables static optimization for pages, i.e., renders pages at build time (results in increased project usage)—⚠️ _Experimental_ - `NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES = 1` enables static optimization for OG images, i.e., renders images at build time (results in increased project usage)—⚠️ _Experimental_ -- `NEXT_PUBLIC_MATTE_SETTING = light` constrains the size of each photo, and enables a surrounding border (can be set to `light` or `dark`) +- `NEXT_PUBLIC_MATTE_PHOTOS = 1` constrains the size of each photo, and enables a surrounding border (potentially useful for photos with tall aspect ratios) - `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage) - `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data - `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index 0f538e3d..c57696d5 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -70,13 +70,13 @@ export default function CommandKClient({ isUserSignedIn, setUserEmail, isCommandKOpen: isOpen, - matteSetting, - setMatteSetting, + arePhotosMatted, shouldShowBaselineGrid, shouldDebugBlur, setIsCommandKOpen: setIsOpen, setShouldRespondToKeyboardCommands, setShouldShowBaselineGrid, + setArePhotosMatted, setShouldDebugBlur, } = useAppState(); @@ -199,15 +199,9 @@ export default function CommandKClient({ heading: 'Debug Tools', accessory: , items: [{ - label: 'Toggle Matte Setting', - action: () => setMatteSetting?.(prev => { - if (!prev) { - return 'light'; - } else if (prev === 'light') { - return 'dark'; - } - }), - annotation: Boolean(matteSetting) ? : undefined, + label: 'Toggle Photo Matting', + action: () => setArePhotosMatted?.(prev => !prev), + annotation: arePhotosMatted ? : undefined, }, { label: 'Toggle Blur Debug', action: () => setShouldDebugBlur?.(prev => !prev), diff --git a/src/components/ImageBlurFallback.tsx b/src/components/ImageBlurFallback.tsx index 9007309c..0b922fb5 100644 --- a/src/components/ImageBlurFallback.tsx +++ b/src/components/ImageBlurFallback.tsx @@ -75,8 +75,9 @@ export default function ImageBlurFallback(props: ImageProps & {
{(BLUR_ENABLED && props.blurDataURL) diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 3adb10b8..71eaf8a0 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -69,7 +69,7 @@ export default function PhotoLarge({ useOnVisible(ref, onVisible); - const { matteSetting } = useAppState(); + const { arePhotosMatted } = useAppState(); return ( - = 1 ? 'max-h-[80%]' : 'max-h-[90%]' - )} - alt={altTextForPhoto(photo)} - src={photo.url} - aspectRatio={photo.aspectRatio} - blurData={photo.blurData} - blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)} - priority={priority} - /> +
= 1 + ? 'h-[80%]' + : 'h-[90%]', + )}> + +
} contentSide={ `{variable}` @@ -135,7 +135,7 @@ export default function SiteChecklistClient({
; const renderEnvVars = (variables: string[]) => -
+
{variables.map(envVar => renderEnvVar(envVar))}
; @@ -370,26 +370,24 @@ export default function SiteChecklistClient({ {renderSubStatus( arePagesStaticallyOptimized ? 'checked' : 'optional', renderEnvVars(['NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES']), - 'translate-y-[4.5px]', + 'translate-y-[3.5px]', )} {renderSubStatus( areOGImagesStaticallyOptimized ? 'checked' : 'optional', renderEnvVars(['NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES']), - 'translate-y-[4.5px]', + 'translate-y-[3.5px]', )} - Set environment variable to {'"light"'} or {'"dark"'} to + Set environment variable to {'"1"'} to constrain the size {' '} - constrain the size of each photo, and enable a surrounding border: - {renderEnvVars(['NEXT_PUBLIC_MATTE_SETTING'])} + of each photo, and enable a surrounding border: + {renderEnvVars(['NEXT_PUBLIC_MATTE_PHOTOS'])} > + arePhotosMatted?: boolean + setArePhotosMatted?: Dispatch> swrTimestamp?: number invalidateSwr?: () => void userEmail?: string diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx index bb4ceebf..9e0a2701 100644 --- a/src/state/AppStateProvider.tsx +++ b/src/state/AppStateProvider.tsx @@ -1,12 +1,12 @@ 'use client'; import { useState, useEffect, ReactNode, useCallback } from 'react'; -import { AppStateContext, MatteSetting } from './AppState'; +import { AppStateContext } from './AppState'; import { AnimationConfig } from '@/components/AnimateItems'; import usePathnames from '@/utility/usePathnames'; import { getAuthAction } from '@/auth/actions'; import useSWR from 'swr'; -import { MATTE_SETTING } from '@/site/config'; +import { MATTE_PHOTOS } from '@/site/config'; export default function AppStateProvider({ children, @@ -17,8 +17,8 @@ export default function AppStateProvider({ const [hasLoaded, setHasLoaded] = useState(false); - const [matteSetting, setMatteSetting] = - useState(MATTE_SETTING); + const [arePhotosMatted, setArePhotosMatted] = + useState(MATTE_PHOTOS); const [swrTimestamp, setSwrTimestamp] = useState(Date.now()); const [userEmail, setUserEmail] = @@ -53,8 +53,8 @@ export default function AppStateProvider({ value={{ previousPathname, hasLoaded, - matteSetting, - setMatteSetting, + arePhotosMatted, + setArePhotosMatted, swrTimestamp, invalidateSwr, setHasLoaded, From 55db7b5766c41f8ba93f0b6d614ffedc5f8b78e8 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 9 May 2024 19:22:40 -0500 Subject: [PATCH 4/5] Remove active interaction on large photos --- src/photo/PhotoLarge.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 71eaf8a0..f781699a 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -77,10 +77,8 @@ export default function PhotoLarge({ contentMain={ From 95260e52222ee4d00eb896377ccca177449cb110 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 9 May 2024 21:34:21 -0500 Subject: [PATCH 5/5] Add matting to FAQ --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 55ec381e..b1ed746a 100644 --- a/README.md +++ b/README.md @@ -209,10 +209,10 @@ FAQ > This template statically optimizes core views such as `/` and `/grid` to minimize visitor load times. Consequently, when photos are added, edited, or removed, it might take several minutes for those changes to propagate. If it seems like a change is not taking effect, try navigating to `/admin/configuration` and clicking "Clear Cache." #### Why don’t my OG images load when I share a link? -> Many services such as iMessage, Slack, and X, require near-instant responses when unfurling link-based content. In order to guarantee sufficient responsiveness, consider rendering pages and image assets ahead of time by enabling static optimization by setting `NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES` and `NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES` to `1`. Keep in mind that this will increase platform usage. +> Many services such as iMessage, Slack, and X, require near-instant responses when unfurling link-based content. In order to guarantee sufficient responsiveness, consider rendering pages and image assets ahead of time by enabling static optimization by setting `NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES = 1` and `NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES = 1`. Keep in mind that this will increase platform usage. -#### Why are my thumbnails square? -> Absent configuration, the default grid aspect ratio is `1`. It can be set to any number (for instance `1.5` for 3:2 images) via `NEXT_PUBLIC_GRID_ASPECT_RATIO` or ignored entirely by setting to `0`. +#### Why do my vertical images take up so much space? +> By default, all photos are shown full-width, regardless of orientation. Enable matting to showcase horizontal and vertical photos at a similar scale by setting `NEXT_PUBLIC_MATTE_PHOTOS = 1`. #### My images/content have fallen out of sync with my database and/or my production site no longer matches local development. What do I do? > Navigate to `/admin/configuration` and click "Clear Cache." @@ -220,6 +220,9 @@ FAQ #### I'm seeing server-side runtime errors when loading a page after updating my fork. What do I do? > Navigate to `/admin/configuration` and click "Clear Cache." If this doesn't help, [open an issue](https://github.com/sambecker/exif-photo-blog/issues/new). +#### Why are my thumbnails square? +> Absent configuration, the default grid aspect ratio is `1`. `NEXT_PUBLIC_GRID_ASPECT_RATIO` can be set to any number (for instance, `1.5` for 3:2 images) or ignored by setting to `0`. + #### Why aren't my Fujifilm simulations importing alongside EXIF data? > Fujifilm simulation data is stored in vendor-specific Makernote binaries embedded in EXIF data. Under certain circumstances an intermediary may strip out this data. For instance, there is a known issue on iOS where editing an image, e.g., cropping it, causes Makernote data loss. If your simulation data appears to be missing, try importing the original file as it was stored by the camera. Additionally, if you can confirm the simulation mode on camera, you can then edit the photo record and manually select it.