}
{(loadingState === 'loading' || loadingState === 'loaded') &&

{
- if (onLoad) {
- onLoad();
- } else {
- setLoadingStateInternal('loaded');
- }
- }}
- onError={() => {
- if (onFail) {
- onFail();
- } else {
- setLoadingStateInternal('failed');
- }
+ onLoadStart={() => setLoadingState('loading')}
+ onLoad={() => setLoadingState('loaded')}
+ onError={e => {
+ setLoadingState('failed');
if (retryTime !== undefined) {
+ setLoadingState('loading');
setTimeout(() => {
- setLoadingStateInternal('loading');
+ e.currentTarget.src = '';
+ e.currentTarget.src = path;
}, retryTime);
}
}}
diff --git a/src/components/og/OGTile.tsx b/src/components/og/OGTile.tsx
index 47c33554..dd2564f3 100644
--- a/src/components/og/OGTile.tsx
+++ b/src/components/og/OGTile.tsx
@@ -6,7 +6,10 @@ import Link from 'next/link';
import useVisible from '@/utility/useVisible';
import OGLoaderImage from './OGLoaderImage';
-export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
+export type OGTilePropsCore = Omit<
+ ComponentProps
,
+ 'title' | 'description' | 'path' | 'pathImage'
+>;
export default function OGTile({
path,
diff --git a/src/components/og/OGTooltip.tsx b/src/components/og/OGTooltip.tsx
index 017a87f2..74a35e7b 100644
--- a/src/components/og/OGTooltip.tsx
+++ b/src/components/og/OGTooltip.tsx
@@ -1,8 +1,16 @@
-import { ComponentProps, ReactNode } from 'react';
-import TooltipPrimitive from '../primitives/TooltipPrimitive';
+import { ComponentProps, ReactNode, useRef, useEffect } from 'react';
import OGLoaderImage from './OGLoaderImage';
import { IMAGE_OG_DIMENSION } from '@/image-response';
import clsx from 'clsx/lite';
+import { useOGTooltipState } from './state';
+import useSupportsHover from '@/utility/useSupportsHover';
+
+const { aspectRatio } = IMAGE_OG_DIMENSION;
+
+const width = 300;
+const height = width / aspectRatio;
+const offsetAbove = -1;
+const offsetBelow = -6;
export default function OGTooltip({
children,
@@ -12,35 +20,53 @@ export default function OGTooltip({
children :ReactNode
caption?: ReactNode
} & ComponentProps) {
- const { aspectRatio } = IMAGE_OG_DIMENSION;
- return (
-
-
- {caption &&
- {caption}
-
}
+ const ref = useRef(null);
+
+ const { showTooltip, dismissTooltip } = useOGTooltipState();
+
+ const supportsHover = useSupportsHover();
+
+ useEffect(() => {
+ const trigger = ref.current;
+ return () => dismissTooltip?.(trigger);
+ }, [dismissTooltip]);
+
+ const content =
+
+
+ {caption &&
+ {caption}
}
+
;
+
+ return (
+ supportsHover &&
+ showTooltip?.(
+ ref.current,
+ { content, width, height, offsetAbove, offsetBelow },
+ )}
+ onMouseLeave={() => supportsHover &&
+ dismissTooltip?.(ref.current)}
>
{children}
-
+
);
}
diff --git a/src/components/og/OGTooltipProvider.tsx b/src/components/og/OGTooltipProvider.tsx
new file mode 100644
index 00000000..e7bd2b19
--- /dev/null
+++ b/src/components/og/OGTooltipProvider.tsx
@@ -0,0 +1,131 @@
+'use client';
+
+import {
+ CSSProperties,
+ ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { OGTooltipContext, Tooltip } from './state';
+import { AnimatePresence, motion } from 'framer-motion';
+import MenuSurface from '../primitives/MenuSurface';
+
+const DELAY_INITIAL_HOVER = 200;
+const DELAY_DISMISS = 200;
+
+const VIEWPORT_SAFE_AREA = 12;
+const TOOLTIP_MARGIN = 12;
+
+export default function OGTooltipProvider({
+ children,
+}: {
+ children: ReactNode
+}) {
+ const [currentTooltip, setCurrentTooltip] = useState();
+ const [tooltipStyle, setTooltipStyle] = useState();
+
+ const currentTriggerRef = useRef(null);
+
+ const timeoutInitialHoverRef = useRef(undefined);
+ const timeoutDismissRef = useRef(undefined);
+
+ const clearTimeouts = useCallback(() => {
+ clearTimeout(timeoutInitialHoverRef.current);
+ timeoutInitialHoverRef.current = undefined;
+ clearTimeout(timeoutDismissRef.current);
+ timeoutDismissRef.current = undefined;
+ }, []);
+
+ const clearState = useCallback((delay = 0) => {
+ clearTimeouts();
+ if (delay) {
+ timeoutDismissRef.current = setTimeout(() => {
+ setCurrentTooltip(undefined);
+ currentTriggerRef.current = null;
+ }, delay);
+ } else {
+ setCurrentTooltip(undefined);
+ currentTriggerRef.current = null;
+ }
+ }, [clearTimeouts]);
+
+ const showTooltip = useCallback((
+ _trigger: HTMLElement | null,
+ tooltip: Tooltip,
+ ) => {
+ if (_trigger) {
+ currentTriggerRef.current = _trigger;
+ const displayTooltip = () => {
+ // Update current trigger ref on display
+ currentTriggerRef.current = _trigger;
+ setCurrentTooltip(tooltip);
+ const trigger = _trigger.getBoundingClientRect();
+ const top =
+ trigger.top - (tooltip.height + TOOLTIP_MARGIN) < VIEWPORT_SAFE_AREA
+ // Position below trigger
+ ? trigger.bottom + TOOLTIP_MARGIN + tooltip.offsetBelow
+ // Position above trigger
+ : trigger.top - (tooltip.height + TOOLTIP_MARGIN)
+ + tooltip.offsetAbove;
+ const horizontalOffset =
+ // eslint-disable-next-line max-len
+ window.innerWidth - (trigger.left + tooltip.width) < VIEWPORT_SAFE_AREA
+ ? { right: VIEWPORT_SAFE_AREA }
+ : { left: trigger.left };
+ setTooltipStyle({ top, ...horizontalOffset });
+ clearTimeouts();
+ };
+ if (currentTooltip) {
+ // Don't apply delay if tooltip's already visible
+ displayTooltip();
+ } else {
+ timeoutInitialHoverRef.current =
+ setTimeout(displayTooltip, DELAY_INITIAL_HOVER);
+ }
+ }
+ }, [currentTooltip, clearTimeouts]);
+
+ const dismissTooltip = useCallback((trigger: HTMLElement | null) => {
+ if (trigger === currentTriggerRef.current) {
+ clearState(DELAY_DISMISS);
+ }
+ }, [clearState]);
+
+ useEffect(() => {
+ const onWindowChange = () => clearState(0);
+ window.addEventListener('mouseup', onWindowChange);
+ window.addEventListener('mousewheel', onWindowChange);
+ window.addEventListener('resize', onWindowChange);
+ return () => {
+ window.removeEventListener('mouseup', onWindowChange);
+ window.removeEventListener('mousewheel', onWindowChange);
+ window.removeEventListener('resize', onWindowChange);
+ };
+ }, [clearState]);
+
+ return (
+
+
+
+ {currentTooltip &&
+
+
+ {currentTooltip.content}
+
+ }
+
+
+ {children}
+
+ );
+}
diff --git a/src/components/og/state.ts b/src/components/og/state.ts
new file mode 100644
index 00000000..08b2a5c8
--- /dev/null
+++ b/src/components/og/state.ts
@@ -0,0 +1,18 @@
+import { createContext, ReactNode, use } from 'react';
+
+export type Tooltip = {
+ content: ReactNode
+ width: number
+ height: number
+ offsetAbove: number
+ offsetBelow: number
+}
+
+export type OGTooltipState = {
+ showTooltip?: (trigger: HTMLElement | null, tooltip: Tooltip) => void
+ dismissTooltip?: (trigger: HTMLElement | null) => void
+}
+
+export const OGTooltipContext = createContext({});
+
+export const useOGTooltipState = () => use(OGTooltipContext);
diff --git a/src/film/FilmOGTile.tsx b/src/film/FilmOGTile.tsx
index 238205c4..ddd74f2d 100644
--- a/src/film/FilmOGTile.tsx
+++ b/src/film/FilmOGTile.tsx
@@ -5,44 +5,31 @@ import {
pathForFilm,
pathForFilmImage,
} from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
import { descriptionForFilmPhotos, titleForFilm } from '.';
import { useAppText } from '@/i18n/state/client';
export default function FilmOGTile({
film,
photos,
- loadingState: loadingStateExternal,
- riseOnHover,
- onLoad,
- onFail,
- retryTime,
count,
dateRange,
+ ...props
}: {
film: string
photos: Photo[]
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
count?: number
dateRange?: PhotoDateRange
-}) {
+} & OGTilePropsCore) {
const appText = useAppText();
return (
);
};
diff --git a/src/focal/FocalLengthOGTile.tsx b/src/focal/FocalLengthOGTile.tsx
index d1b81cff..ee552b56 100644
--- a/src/focal/FocalLengthOGTile.tsx
+++ b/src/focal/FocalLengthOGTile.tsx
@@ -5,49 +5,37 @@ import {
pathForFocalLength,
pathForFocalLengthImage,
} from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
import { descriptionForFocalLengthPhotos, titleForFocalLength } from '.';
import { useAppText } from '@/i18n/state/client';
export default function FocalLengthOGTile({
focal,
photos,
- loadingState: loadingStateExternal,
- riseOnHover,
- onLoad,
- onFail,
- retryTime,
count,
dateRange,
+ ...props
}: {
focal: number
photos: Photo[]
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
count?: number
dateRange?: PhotoDateRange
-}) {
+} & OGTilePropsCore) {
const appText = useAppText();
return (
);
};
diff --git a/src/i18n/locales/bd-bn.ts b/src/i18n/locales/bd-bn.ts
index 98e0caa3..5bed115a 100644
--- a/src/i18n/locales/bd-bn.ts
+++ b/src/i18n/locales/bd-bn.ts
@@ -1,117 +1,118 @@
-export { bn as default } from 'date-fns/locale/bn';
-
-export const TEXT = {
- photo: {
- photo: 'ছবি',
- photoPlural: 'ছবিগুলো',
- taken: 'তোলা হয়েছে',
- created: 'তৈরি হয়েছে',
- updated: 'আপডেট হয়েছে',
- copied: 'ছবির লিংক কপি হয়েছে',
- },
- category: {
- camera: 'ক্যামেরা',
- cameraPlural: 'ক্যামেরাসমূহ',
- cameraTitle: '{{camera}} দিয়ে তোলা',
- cameraShare: '{{camera}} দিয়ে তোলা ছবিগুলো',
- lens: 'লেন্স',
- lensPlural: 'লেন্সগুলো',
- tag: 'ট্যাগ',
- tagPlural: 'ট্যাগসমূহ',
- taggedPhotos: 'ট্যাগকৃত ছবি',
- taggedPhrase: '{{tag}} ট্যাগ দেওয়া ছবি',
- taggedFavs: 'পছন্দের ছবি',
- recipe: 'রেসিপি',
- recipePlural: 'রেসিপিসমূহ',
- recipeShare: '{{recipe}} রেসিপি ছবিগুলো',
- film: 'ফিল্ম',
- filmPlural: 'ফিল্মসমূহ',
- filmShare: '{{film}} দিয়ে তোলা ছবিগুলো',
- focalLength: 'ফোকাল দৈর্ঘ্য',
- focalLengthPlural: 'ফোকাল দৈর্ঘ্যগুলো',
- focalLengthTitle: '{{focal}} ফোকাল দৈর্ঘ্য',
- focalLengthShare: '{{focal}} এ তোলা ছবিগুলো',
- },
- nav: {
- home: 'হোম',
- feed: 'ফিড',
- grid: 'গ্রিড',
- admin: 'অ্যাডমিন',
- search: 'সার্চ',
- prev: 'পূর্ববর্তী',
- prevShort: 'পূর্ব',
- next: 'পরবর্তী',
- nextShort: 'পরবর্তী',
- },
- cmdk: {
- placeholder: 'ছবি, ভিউ, সেটিংস অনুসন্ধান করুন ...',
- searching: 'অনুসন্ধান হচ্ছে ...',
- noResults: 'কোনো ফলাফল পাওয়া যায়নি',
- },
- tooltip: {
- '35mm': '৩৫মিমি সমতুল্য',
- zoom: 'জুম ইন',
- sharePhoto: 'ছবি শেয়ার করুন',
- recipeInfo: 'রেসিপি তথ্য',
- recipeCopy: 'রেসিপি কপি করুন',
- download: 'মূল ফাইল ডাউনলোড করুন',
- },
- theme: {
- theme: 'থিম',
- system: 'সিস্টেম',
- light: 'লাইট মোড',
- dark: 'ডার্ক মোড',
- },
- auth: {
- signIn: 'সাইন ইন',
- signOut: 'সাইন আউট',
- email: 'অ্যাডমিন ইমেইল',
- password: 'অ্যাডমিন পাসওয়ার্ড',
- invalidEmailPassword: 'ইমেইল বা পাসওয়ার্ড ভুল',
- },
- admin: {
- uploadPhotos: 'ছবি আপলোড করুন',
- upload: 'আপলোড',
- uploadPlural: 'আপলোডসমূহ',
- uploading: 'আপলোড হচ্ছে',
- update: 'আপডেট',
- updatePlural: 'আপডেটসমূহ',
- managePhotos: 'ছবি ব্যবস্থাপনা করুন',
- manageCameras: 'ক্যামেরা ব্যবস্থাপনা করুন',
- manageLenses: 'লেন্স ব্যবস্থাপনা করুন',
- manageTags: 'ট্যাগ ব্যবস্থাপনা করুন',
- manageRecipes: 'রেসিপি ব্যবস্থাপনা করুন',
- batchEdit: 'একসাথে ছবিগুলো এডিট করুন ...',
- batchEditShort: 'ব্যাচ এডিট ...',
- batchExitEdit: 'ব্যাচ এডিট থেকে বের হোন',
- appInsights: 'অ্যাপ ইনসাইট',
- appConfig: 'অ্যাপ কনফিগারেশন',
- edit: 'এডিট',
- favorite: 'পছন্দ',
- unfavorite: 'পছন্দ অপসারণ',
- hide: 'লুকান',
- unhide: 'দেখান',
- download: 'ডাউনলোড',
- sync: 'সিঙ্ক',
- delete: 'ডিলিট',
- deleteConfirm: 'আপনি কি "{{photoTitle}}" মুছে ফেলতে চান?',
- },
- onboarding: {
- setupComplete: 'সেটআপ সম্পন্ন!',
- setupIncomplete: 'সেটআপ সম্পূর্ণ করুন',
- setupSignIn: 'ছবি আপলোড করতে সাইন ইন করুন',
- setupFirstPhoto: 'আপনার প্রথম ছবি যোগ করুন',
- setupConfig: 'পরিবেশ ভেরিয়েবল সম্পাদনা করে সাইটের নাম এবং অন্যান্য কনফিগারেশন পরিবর্তন করুন',
- },
- misc: {
- loading: 'লোড হচ্ছে ...',
- finishing: 'সম্পন্ন হচ্ছে ...',
- uploading: 'আপলোড হচ্ছে',
- repo: 'তৈরি হয়েছে',
- copyPhrase: '{{label}} কপি হয়েছে',
- },
- utility: {
- paginate: '{{index}} / {{count}}',
- paginateAction: '{{action}} - {{index}} / {{count}}',
- },
-};
+export { bn as default } from 'date-fns/locale/bn';
+
+export const TEXT = {
+ photo: {
+ photo: 'ছবি',
+ photoPlural: 'ছবিগুলো',
+ taken: 'তোলা হয়েছে',
+ created: 'তৈরি হয়েছে',
+ updated: 'আপডেট হয়েছে',
+ copied: 'ছবির লিংক কপি হয়েছে',
+ },
+ category: {
+ camera: 'ক্যামেরা',
+ cameraPlural: 'ক্যামেরাসমূহ',
+ cameraTitle: '{{camera}} দিয়ে তোলা',
+ cameraShare: '{{camera}} দিয়ে তোলা ছবিগুলো',
+ lens: 'লেন্স',
+ lensPlural: 'লেন্সগুলো',
+ tag: 'ট্যাগ',
+ tagPlural: 'ট্যাগসমূহ',
+ taggedPhotos: 'ট্যাগকৃত ছবি',
+ taggedPhrase: '{{tag}} ট্যাগ দেওয়া ছবি',
+ taggedFavs: 'পছন্দের ছবি',
+ recipe: 'রেসিপি',
+ recipePlural: 'রেসিপিসমূহ',
+ recipeShare: '{{recipe}} রেসিপি ছবিগুলো',
+ film: 'ফিল্ম',
+ filmPlural: 'ফিল্মসমূহ',
+ filmShare: '{{film}} দিয়ে তোলা ছবিগুলো',
+ focalLength: 'ফোকাল দৈর্ঘ্য',
+ focalLengthPlural: 'ফোকাল দৈর্ঘ্যগুলো',
+ focalLengthTitle: '{{focal}} ফোকাল দৈর্ঘ্য',
+ focalLengthShare: '{{focal}} এ তোলা ছবিগুলো',
+ },
+ nav: {
+ home: 'হোম',
+ feed: 'ফিড',
+ grid: 'গ্রিড',
+ admin: 'অ্যাডমিন',
+ search: 'সার্চ',
+ prev: 'পূর্ববর্তী',
+ prevShort: 'পূর্ব',
+ next: 'পরবর্তী',
+ nextShort: 'পরবর্তী',
+ },
+ cmdk: {
+ placeholder: 'ছবি, ভিউ, সেটিংস অনুসন্ধান করুন ...',
+ searching: 'অনুসন্ধান হচ্ছে ...',
+ noResults: 'কোনো ফলাফল পাওয়া যায়নি',
+ },
+ tooltip: {
+ '35mm': '৩৫মিমি সমতুল্য',
+ zoom: 'জুম ইন',
+ sharePhoto: 'ছবি শেয়ার করুন',
+ recipeInfo: 'রেসিপি তথ্য',
+ recipeCopy: 'রেসিপি কপি করুন',
+ download: 'মূল ফাইল ডাউনলোড করুন',
+ },
+ theme: {
+ theme: 'থিম',
+ system: 'সিস্টেম',
+ light: 'লাইট মোড',
+ dark: 'ডার্ক মোড',
+ },
+ auth: {
+ signIn: 'সাইন ইন',
+ signOut: 'সাইন আউট',
+ email: 'অ্যাডমিন ইমেইল',
+ password: 'অ্যাডমিন পাসওয়ার্ড',
+ invalidEmailPassword: 'ইমেইল বা পাসওয়ার্ড ভুল',
+ },
+ admin: {
+ uploadPhotos: 'ছবি আপলোড করুন',
+ upload: 'আপলোড',
+ uploadPlural: 'আপলোডসমূহ',
+ uploading: 'আপলোড হচ্ছে',
+ update: 'আপডেট',
+ updatePlural: 'আপডেটসমূহ',
+ managePhotos: 'ছবি ব্যবস্থাপনা করুন',
+ manageCameras: 'ক্যামেরা ব্যবস্থাপনা করুন',
+ manageLenses: 'লেন্স ব্যবস্থাপনা করুন',
+ manageTags: 'ট্যাগ ব্যবস্থাপনা করুন',
+ manageRecipes: 'রেসিপি ব্যবস্থাপনা করুন',
+ batchEdit: 'একসাথে ছবিগুলো এডিট করুন ...',
+ batchEditShort: 'ব্যাচ এডিট ...',
+ batchExitEdit: 'ব্যাচ এডিট থেকে বের হোন',
+ appInsights: 'অ্যাপ ইনসাইট',
+ appConfig: 'অ্যাপ কনফিগারেশন',
+ edit: 'এডিট',
+ favorite: 'পছন্দ',
+ unfavorite: 'পছন্দ অপসারণ',
+ hide: 'লুকান',
+ unhide: 'দেখান',
+ download: 'ডাউনলোড',
+ sync: 'সিঙ্ক',
+ delete: 'ডিলিট',
+ deleteConfirm: 'আপনি কি "{{photoTitle}}" মুছে ফেলতে চান?',
+ },
+ onboarding: {
+ setupComplete: 'সেটআপ সম্পন্ন!',
+ setupIncomplete: 'সেটআপ সম্পূর্ণ করুন',
+ setupSignIn: 'ছবি আপলোড করতে সাইন ইন করুন',
+ setupFirstPhoto: 'আপনার প্রথম ছবি যোগ করুন',
+ // eslint-disable-next-line max-len
+ setupConfig: 'পরিবেশ ভেরিয়েবল সম্পাদনা করে সাইটের নাম এবং অন্যান্য কনফিগারেশন পরিবর্তন করুন',
+ },
+ misc: {
+ loading: 'লোড হচ্ছে ...',
+ finishing: 'সম্পন্ন হচ্ছে ...',
+ uploading: 'আপলোড হচ্ছে',
+ repo: 'তৈরি হয়েছে',
+ copyPhrase: '{{label}} কপি হয়েছে',
+ },
+ utility: {
+ paginate: '{{index}} / {{count}}',
+ paginateAction: '{{action}} - {{index}} / {{count}}',
+ },
+};
diff --git a/src/lens/LensOGTile.tsx b/src/lens/LensOGTile.tsx
index 79bd9948..a43c5b0d 100644
--- a/src/lens/LensOGTile.tsx
+++ b/src/lens/LensOGTile.tsx
@@ -1,6 +1,6 @@
import { Photo, PhotoDateRange } from '@/photo';
import { pathForLens, pathForLensImage } from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
import { Lens } from '.';
import { titleForLens, descriptionForLensPhotos } from './meta';
import { useAppText } from '@/i18n/state/client';
@@ -8,27 +8,19 @@ import { useAppText } from '@/i18n/state/client';
export default function LensOGTile({
lens,
photos,
- loadingState: loadingStateExternal,
- riseOnHover,
- onLoad,
- onFail,
- retryTime,
count,
dateRange,
+ ...props
}: {
lens: Lens
photos: Photo[]
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
count?: number
dateRange?: PhotoDateRange
-}) {
+} & OGTilePropsCore) {
const appText = useAppText();
return (
);
};
diff --git a/src/photo/PhotoOGTile.tsx b/src/photo/PhotoOGTile.tsx
index a2cc70d5..628decd5 100644
--- a/src/photo/PhotoOGTile.tsx
+++ b/src/photo/PhotoOGTile.tsx
@@ -7,35 +7,23 @@ import {
} from '@/photo';
import { PhotoSetCategory } from '../category';
import { pathForPhoto, pathForPhotoImage } from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
export default function PhotoOGTile({
photo,
- loadingState: loadingStateExternal,
riseOnHover,
- onLoad,
- onFail,
retryTime,
onVisible,
...categories
}: {
photo: Photo
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
- onVisible?: () => void
-} & PhotoSetCategory) {
+} & PhotoSetCategory & OGTilePropsCore) {
return (
;
export default function StaggeredOgPhotos({
photos,
- maxConcurrency = DEFAULT_MAX_CONCURRENCY,
onLastPhotoVisible,
}: {
photos: Photo[]
maxConcurrency?: number
onLastPhotoVisible?: () => void
}) {
- const [loadingState, setLoadingState] = useState(
- photos.reduce((acc, photo) => ({
- ...acc,
- [photo.id]: 'unloaded' as const,
- }), {} as PhotoLoadingState),
- );
-
- const recomputeLoadingState = useCallback((
- updatedState: PhotoLoadingState = {},
- ) => setLoadingState(currentLoadingState => {
- const initialLoadingState = {
- ...currentLoadingState,
- ...updatedState,
- };
- const updatedLoadingState = {
- ...currentLoadingState,
- ...updatedState,
- };
-
- let imagesLoadingCount = 0;
- Object.entries(initialLoadingState).forEach(([id, state]) => {
- if (state === 'loading') {
- imagesLoadingCount++;
- } else if (imagesLoadingCount < maxConcurrency && state === 'unloaded') {
- updatedLoadingState[id] = 'loading';
- imagesLoadingCount++;
- }
- });
-
- return updatedLoadingState;
- })
- , [maxConcurrency]);
-
- useEffect(() => {
- recomputeLoadingState();
- }, [recomputeLoadingState]);
-
return (
{photos.map((photo, index) =>
recomputeLoadingState({ [photo.id]: 'loaded' })}
- onFail={() => recomputeLoadingState({ [photo.id]: 'failed' })}
onVisible={index === photos.length - 1
? onLastPhotoVisible
: undefined}
diff --git a/src/recipe/RecipeOGTile.tsx b/src/recipe/RecipeOGTile.tsx
index b2d81e03..9977d64a 100644
--- a/src/recipe/RecipeOGTile.tsx
+++ b/src/recipe/RecipeOGTile.tsx
@@ -1,33 +1,25 @@
import { Photo, PhotoDateRange } from '@/photo';
import { pathForRecipe, pathForRecipeImage } from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
import { descriptionForRecipePhotos, titleForRecipe } from '.';
import { useAppText } from '@/i18n/state/client';
export default function RecipeOGTile({
recipe,
photos,
- loadingState: loadingStateExternal,
- riseOnHover,
- onLoad,
- onFail,
- retryTime,
count,
dateRange,
+ ...props
}: {
recipe: string
photos: Photo[]
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
count?: number
dateRange?: PhotoDateRange
-}) {
+} & OGTilePropsCore) {
const appText = useAppText();
return (
);
};
diff --git a/src/tag/TagOGTile.tsx b/src/tag/TagOGTile.tsx
index d6a34cdb..340e677c 100644
--- a/src/tag/TagOGTile.tsx
+++ b/src/tag/TagOGTile.tsx
@@ -2,34 +2,26 @@
import { Photo, PhotoDateRange } from '@/photo';
import { pathForTag, pathForTagImage } from '@/app/paths';
-import OGTile, { OGLoadingState } from '@/components/og/OGTile';
+import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
import { descriptionForTaggedPhotos, titleForTag } from '.';
import { useAppText } from '@/i18n/state/client';
export default function TagOGTile({
tag,
photos,
- loadingState: loadingStateExternal,
- riseOnHover,
- onLoad,
- onFail,
- retryTime,
count,
dateRange,
+ ...props
}: {
tag: string
photos: Photo[]
- loadingState?: OGLoadingState
- onLoad?: () => void
- onFail?: () => void
- riseOnHover?: boolean
- retryTime?: number
count?: number
dateRange?: PhotoDateRange
-}) {
+} & OGTilePropsCore) {
const appText = useAppText();
return (
);
};