Add image fallback debug logging

This commit is contained in:
Sam Becker 2025-06-24 10:14:08 -05:00
parent 6d7c5d6903
commit e1cd7d23f1
8 changed files with 54 additions and 15 deletions

View File

@ -11,6 +11,7 @@ import {
HTML_LANG, HTML_LANG,
NAV_CAPTION, NAV_CAPTION,
SITE_FEEDS_ENABLED, SITE_FEEDS_ENABLED,
ADMIN_DEBUG_TOOLS_ENABLED,
} from '@/app/config'; } from '@/app/config';
import AppStateProvider from '@/state/AppStateProvider'; import AppStateProvider from '@/state/AppStateProvider';
import ToasterWithThemes from '@/toast/ToasterWithThemes'; import ToasterWithThemes from '@/toast/ToasterWithThemes';
@ -91,7 +92,7 @@ export default function RootLayout({
// Center on large screens // Center on large screens
'3xl:flex flex-col items-center', '3xl:flex flex-col items-center',
)}> )}>
<AppStateProvider> <AppStateProvider areAdminDebugToolsEnabled={ADMIN_DEBUG_TOOLS_ENABLED}>
<AppTextProvider> <AppTextProvider>
<ThemeColors /> <ThemeColors />
<ThemeProvider attribute="class" defaultTheme={DEFAULT_THEME}> <ThemeProvider attribute="class" defaultTheme={DEFAULT_THEME}>

View File

@ -1,7 +1,6 @@
import CommandKClient from './CommandKClient'; import CommandKClient from './CommandKClient';
import { getPhotosMetaCached } from '@/photo/cache'; import { getPhotosMetaCached } from '@/photo/cache';
import { photoQuantityText } from '@/photo'; import { photoQuantityText } from '@/photo';
import { ADMIN_DEBUG_TOOLS_ENABLED } from '../app/config';
import { getDataForCategoriesCached } from '@/category/cache'; import { getDataForCategoriesCached } from '@/category/cache';
import { getAppText } from '@/i18n/state/server'; import { getAppText } from '@/i18n/state/server';
@ -21,7 +20,6 @@ export default async function CommandK() {
return ( return (
<CommandKClient <CommandKClient
{...categories} {...categories}
showDebugTools={ADMIN_DEBUG_TOOLS_ENABLED}
footer={photoQuantityText(count, appText, false)} footer={photoQuantityText(count, appText, false)}
/> />
); );

View File

@ -122,10 +122,8 @@ export default function CommandKClient({
recipes, recipes,
films, films,
focalLengths, focalLengths,
showDebugTools,
footer, footer,
}: { }: {
showDebugTools?: boolean
footer?: string footer?: string
} & PhotoSetCategories) { } & PhotoSetCategories) {
const pathname = usePathname(); const pathname = usePathname();
@ -146,6 +144,7 @@ export default function CommandKClient({
isGridHighDensity, isGridHighDensity,
areZoomControlsShown, areZoomControlsShown,
arePhotosMatted, arePhotosMatted,
areAdminDebugToolsEnabled,
shouldShowBaselineGrid, shouldShowBaselineGrid,
shouldDebugImageFallbacks, shouldDebugImageFallbacks,
shouldDebugInsights, shouldDebugInsights,
@ -406,7 +405,7 @@ export default function CommandKClient({
}], }],
}]; }];
if (isUserSignedIn && showDebugTools) { if (isUserSignedIn && areAdminDebugToolsEnabled) {
clientSections.push({ clientSections.push({
heading: 'Debug Tools', heading: 'Debug Tools',
accessory: <RiToolsFill size={16} className="translate-x-[-1px]" />, accessory: <RiToolsFill size={16} className="translate-x-[-1px]" />,
@ -537,7 +536,7 @@ export default function CommandKClient({
annotation: <IconLock narrow />, annotation: <IconLock narrow />,
path: PATH_ADMIN_CONFIGURATION, path: PATH_ADMIN_CONFIGURATION,
}); });
if (showDebugTools) { if (areAdminDebugToolsEnabled) {
adminSection.items.push({ adminSection.items.push({
label: 'Baseline Overview', label: 'Baseline Overview',
annotation: <BiLockAlt />, annotation: <BiLockAlt />,

View File

@ -10,6 +10,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
export default function ImageWithFallback({ export default function ImageWithFallback({
className, className,
classNameImage = 'object-cover h-full', classNameImage = 'object-cover h-full',
debug,
priority, priority,
blurDataURL, blurDataURL,
blurCompatibilityLevel = 'low', blurCompatibilityLevel = 'low',
@ -17,8 +18,12 @@ export default function ImageWithFallback({
}: ImageProps & { }: ImageProps & {
blurCompatibilityLevel?: 'none' | 'low' | 'high' blurCompatibilityLevel?: 'none' | 'low' | 'high'
classNameImage?: string classNameImage?: string
debug?: boolean
}) { }) {
const { shouldDebugImageFallbacks } = useAppState(); const {
shouldDebugImageFallbacks,
areAdminDebugToolsEnabled,
} = useAppState();
const [wasCached, setWasCached] = useState(true); const [wasCached, setWasCached] = useState(true);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@ -39,10 +44,28 @@ export default function ImageWithFallback({
); );
}, []); }, []);
const shouldDebugFallback = areAdminDebugToolsEnabled && debug;
const debugFallbackStyles = useCallback(() => {
const stylesMap = refFallback.current?.computedStyleMap();
const styles = stylesMap
? Array.from(stylesMap.entries()).reduce((acc, [key, value]) => {
acc[key] = value.toString();
return acc;
}, {} as Record<string, string>) : {};
const opacity = (stylesMap?.get('opacity') as CSSUnitValue)?.value;
return {
styles,
opacity,
classList: refFallback.current?.classList,
};
}, []);
const outerTimeout = useRef<NodeJS.Timeout>(undefined);
const innerTimeout = useRef<NodeJS.Timeout>(undefined);
useEffect(() => { useEffect(() => {
if (!isLoading && !didError) { if (!isLoading && !didError) {
let innerTimeout: NodeJS.Timeout; outerTimeout.current = setTimeout(() => {
const timeout = setTimeout(() => {
if (refFallback.current) { if (refFallback.current) {
const fallbackOpacity = (refFallback const fallbackOpacity = (refFallback
.current .current
@ -54,22 +77,32 @@ export default function ImageWithFallback({
if (fallbackOpacity === 0) { if (fallbackOpacity === 0) {
// Image has loaded and fallback is already hidden // Image has loaded and fallback is already hidden
setHideFallback(true); setHideFallback(true);
if (shouldDebugFallback) {
console.log('Hide fallback: 01', debugFallbackStyles());
}
} else { } else {
// Image has loaded but fallback is still visible // Image has loaded but fallback is still visible
// Delay hiding fallback to avoid abrupt transition // Delay hiding fallback to avoid abrupt transition
innerTimeout = setTimeout(() =>{ innerTimeout.current = setTimeout(() =>{
console.log('Delayed hide fallback');
setHideFallback(true); setHideFallback(true);
if (shouldDebugFallback) {
console.log('Hide fallback: 02', debugFallbackStyles());
}
}, 1000); }, 1000);
} }
} }
}, 1000); }, 1000);
return () => { return () => {
clearTimeout(timeout); clearTimeout(outerTimeout.current);
clearTimeout(innerTimeout); clearTimeout(innerTimeout.current);
}; };
} }
}, [isLoading, didError]); }, [
isLoading,
didError,
shouldDebugFallback,
debugFallbackStyles,
]);
const showFallback = const showFallback =
!wasCached && !wasCached &&

View File

@ -78,6 +78,7 @@ export default function PhotoLarge({
includeFavoriteInAdminMenu, includeFavoriteInAdminMenu,
onVisible, onVisible,
showAdminKeyCommands, showAdminKeyCommands,
debugImageFallback,
}: { }: {
photo: Photo photo: Photo
className?: string className?: string
@ -104,6 +105,7 @@ export default function PhotoLarge({
includeFavoriteInAdminMenu?: boolean includeFavoriteInAdminMenu?: boolean
onVisible?: () => void onVisible?: () => void
showAdminKeyCommands?: boolean showAdminKeyCommands?: boolean
debugImageFallback?: boolean
}) { }) {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const refZoomControls = useRef<ZoomControlsRef>(null); const refZoomControls = useRef<ZoomControlsRef>(null);
@ -224,6 +226,7 @@ export default function PhotoLarge({
blurDataURL={photo.blurData} blurDataURL={photo.blurData}
blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)} blurCompatibilityMode={doesPhotoNeedBlurCompatibility(photo)}
priority={priority} priority={priority}
debug={debugImageFallback}
/> />
</ZoomControls> </ZoomControls>
<div className={clsx( <div className={clsx(

View File

@ -35,6 +35,7 @@ export default function PhotosLarge({
onVisible={index === photos.length - 1 onVisible={index === photos.length - 1
? onLastPhotoVisible ? onLastPhotoVisible
: undefined} : undefined}
debugImageFallback={index === 0}
/>)} />)}
itemKeys={photos.map(photo => photo.id)} itemKeys={photos.map(photo => photo.id)}
/> />

View File

@ -64,6 +64,7 @@ export type AppStateContextType = {
setUploadState?: (uploadState: Partial<UploadState>) => void setUploadState?: (uploadState: Partial<UploadState>) => void
resetUploadState?: () => void resetUploadState?: () => void
// DEBUG // DEBUG
areAdminDebugToolsEnabled?: boolean
isGridHighDensity?: boolean isGridHighDensity?: boolean
setIsGridHighDensity?: Dispatch<SetStateAction<boolean>> setIsGridHighDensity?: Dispatch<SetStateAction<boolean>>
areZoomControlsShown?: boolean areZoomControlsShown?: boolean

View File

@ -30,8 +30,10 @@ import { toastSuccess } from '@/toast';
export default function AppStateProvider({ export default function AppStateProvider({
children, children,
areAdminDebugToolsEnabled,
}: { }: {
children: ReactNode children: ReactNode
areAdminDebugToolsEnabled?: boolean
}) { }) {
const router = useRouter(); const router = useRouter();
@ -243,6 +245,7 @@ export default function AppStateProvider({
setUploadState, setUploadState,
resetUploadState, resetUploadState,
// DEBUG // DEBUG
areAdminDebugToolsEnabled,
isGridHighDensity, isGridHighDensity,
setIsGridHighDensity, setIsGridHighDensity,
areZoomControlsShown, areZoomControlsShown,