/
kreyndelHam
/
Konstructorium
Обзор
Документация
Войти
/
kreyndelHam
/
Konstructorium
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
dev
frontend/src/utils/imageUtils.js
118 строк
3 KB
MihahamYT
Images working
24 май 2026, 16:44
24 май 2026, 16:44
63a5a31
Код
Авторство
О чём код?
/** Bump when media proxy or URL normalization changes to bust stale browser cache. */ const MEDIA_CACHE_VERSION = '5' /** Static fallback served by the SPA when product media cannot be decoded. */ export const PRODUCT_IMAGE_PLACEHOLDER = '/placeholder-image.svg' /** * Extract a same-origin `/media/...` path from absolute or relative backend URLs. * * @param {string} imageUrl * @returns {string|null} */ const extractMediaPath = (imageUrl) => { const match = String(imageUrl).trim().match(/\/media\/[^?\s#]+/) if (!match) { return null } return match[0].replace(/\/+/g, '/') } /** * Append a cache-buster query param so browsers do not reuse stale 404 responses. * * @param {string} path * @returns {string} */ const withMediaCacheBuster = (path) => { const separator = path.includes('?') ? '&' : '?' return `${path}${separator}v=${MEDIA_CACHE_VERSION}` } /** * Normalize image URLs from the backend for nginx (Docker) and Vite dev proxy. * * @param {string|null|undefined} imageUrl * @returns {string} */ export const getImageUrl = (imageUrl) => { if (!imageUrl) { return PRODUCT_IMAGE_PLACEHOLDER } const mediaPath = extractMediaPath(imageUrl) if (mediaPath) { return withMediaCacheBuster(mediaPath) } let normalizedUrl = String(imageUrl).trim().replace(/^\/+/, '') if (!normalizedUrl.startsWith('media/')) { normalizedUrl = `media/${normalizedUrl}` } return withMediaCacheBuster(`/${normalizedUrl}`.replace(/\/+/g, '/')) } /** * Return product image records that have a resolvable media URL. * * @param {{ images?: Array<{ image?: string|null }> }|null|undefined} product * @returns {Array<{ image?: string|null }>} */ export const getVisibleProductImages = (product) => { return (product?.images || []).filter((imageRecord) => Boolean(imageRecord?.image)) } /** * Resolve the primary product image URL for card/list views. * * @param {{ images?: Array<{ image?: string|null }> }|null|undefined} product * @returns {string} */ export const getPrimaryProductImageUrl = (product) => { const primaryImage = getVisibleProductImages(product)[0]?.image return getImageUrl(primaryImage) } /** * Check whether an image URL returns a successful response. * * @param {string} url * @returns {Promise<boolean>} */ export const checkImageExists = async (url) => { try { const response = await fetch(url, { method: 'HEAD' }) return response.ok } catch (error) { console.error('Error checking image:', error) return false } } /** * Recover from a failed product image load without leaving a broken card. * * First failure swaps to the static SVG placeholder. A second failure optionally * reveals a sibling placeholder element (used on catalog cards). * * @param {React.SyntheticEvent<HTMLImageElement>} event * @param {{ revealSibling?: boolean }} [options] */ export const handleProductImageError = (event, { revealSibling = false } = {}) => { const img = event.currentTarget if (img.dataset.fallbackApplied === '1') { if (revealSibling) { img.style.display = 'none' const sibling = img.nextElementSibling if (sibling instanceof HTMLElement) { sibling.style.display = 'flex' } } return } img.dataset.fallbackApplied = '1' img.src = PRODUCT_IMAGE_PLACEHOLDER }