146 lines
5.7 KiB
TypeScript
146 lines
5.7 KiB
TypeScript
|
|
import { useState, useEffect } from 'react';
|
|
import { ResponsiveData } from '@/components/ResponsiveImage';
|
|
|
|
interface UseResponsiveImageProps {
|
|
src: string | File | null;
|
|
responsiveSizes?: number[];
|
|
formats?: string[];
|
|
enabled?: boolean;
|
|
apiUrl?: string;
|
|
}
|
|
|
|
// Module-level cache to deduplicate requests
|
|
// Key: stringified request params, Value: Promise<ResponsiveData>
|
|
const requestCache = new Map<string, Promise<ResponsiveData>>();
|
|
|
|
export const useResponsiveImage = ({
|
|
src,
|
|
responsiveSizes = [180, 640, 1024],
|
|
formats = ['avif', 'webp', 'jpeg'],
|
|
enabled = true,
|
|
apiUrl,
|
|
}: UseResponsiveImageProps) => {
|
|
const [data, setData] = useState<ResponsiveData | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
const generateResponsiveImages = async () => {
|
|
if (!src || !enabled) {
|
|
if (isMounted) {
|
|
// Only reset if disabled or no src, but if we already have data we might want to keep it?
|
|
// actually if distinct src change, reset. if just disabled, maybe do nothing?
|
|
// For now, if disabled, we just don't start.
|
|
if (!src) {
|
|
setData(null);
|
|
setLoading(false);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (isMounted) {
|
|
setLoading(true);
|
|
setError(null);
|
|
}
|
|
|
|
try {
|
|
// Only cache string URLs (remote images)
|
|
// File objects are harder to cache reliably and usually come from user input anyway
|
|
if (typeof src === 'string') {
|
|
// Check for Data URI
|
|
if (src.startsWith('data:')) {
|
|
if (isMounted) {
|
|
setData({
|
|
img: {
|
|
src: src,
|
|
width: 0, // Unknown dimensions without parsing
|
|
height: 0,
|
|
format: 'unknown'
|
|
},
|
|
sources: [] // No alternative sources for raw data URI
|
|
});
|
|
setLoading(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const cacheKey = JSON.stringify({ src, sizes: responsiveSizes, formats, apiUrl });
|
|
|
|
if (!requestCache.has(cacheKey)) {
|
|
const requestPromise = (async () => {
|
|
const formData = new FormData();
|
|
formData.append('url', src);
|
|
formData.append('sizes', JSON.stringify(responsiveSizes));
|
|
formData.append('formats', JSON.stringify(formats));
|
|
|
|
const serverUrl = apiUrl || import.meta.env.VITE_SERVER_IMAGE_API_URL || 'http://192.168.1.11:3333';
|
|
const response = await fetch(`${serverUrl}/api/images/responsive`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const txt = await response.text();
|
|
// Remove from cache on error so it can be retried
|
|
requestCache.delete(cacheKey);
|
|
throw new Error(txt || 'Failed to generate responsive images');
|
|
}
|
|
|
|
return response.json();
|
|
})();
|
|
|
|
requestCache.set(cacheKey, requestPromise);
|
|
}
|
|
|
|
const result = await requestCache.get(cacheKey)!;
|
|
if (isMounted) {
|
|
setData(result);
|
|
}
|
|
} else {
|
|
// Handle File objects (no caching)
|
|
const formData = new FormData();
|
|
formData.append('file', src);
|
|
formData.append('sizes', JSON.stringify(responsiveSizes));
|
|
formData.append('formats', JSON.stringify(formats));
|
|
|
|
const serverUrl = apiUrl || import.meta.env.VITE_SERVER_IMAGE_API_URL || '';
|
|
const response = await fetch(`${serverUrl}/api/images/responsive`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const txt = await response.text();
|
|
throw new Error(txt || 'Failed to generate responsive images');
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (isMounted) {
|
|
setData(result);
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
console.error('Error generating responsive images:', err);
|
|
if (isMounted) {
|
|
setError(err.message);
|
|
}
|
|
} finally {
|
|
if (isMounted) {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
generateResponsiveImages();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, [src, JSON.stringify(responsiveSizes), JSON.stringify(formats), enabled]);
|
|
|
|
return { data, loading, error };
|
|
};
|