From 86069fa54c4e49903cbab4b81132ef4ecf2698e4 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 9 Oct 2026 23:23:02 +0200 Subject: [PATCH] u --- src/components/article/ThumbHashImage.jsx | 143 ++++------------------ src/components/tiles/ArticleTile.jsx | 6 - src/components/tiles/CategoryTile.jsx | 1 - src/routes/artikel/marken.jsx | 4 + 4 files changed, 26 insertions(+), 128 deletions(-) diff --git a/src/components/article/ThumbHashImage.jsx b/src/components/article/ThumbHashImage.jsx index b73a18c..a7e8d69 100644 --- a/src/components/article/ThumbHashImage.jsx +++ b/src/components/article/ThumbHashImage.jsx @@ -1,126 +1,27 @@ -import { useEffect, useRef } from 'react'; +import { PureComponent } from 'react'; import Box from '@mui/material/Box'; -import { thumbHashToRGBA, thumbHashToDataURL } from 'thumbhash'; +import { thumbHashToDataURL } from 'thumbhash'; -const dataUrlCache = (globalThis.__THUMB_DATA_URL_CACHE__ ??= new Map()); -const loadedImageHashes = (globalThis.__LOADED_IMAGE_HASHES__ ??= new Set()); -export const imageCache = (globalThis.__IMAGE_CACHE__ ??= new Map()); +export default class ThumbHashImage extends PureComponent { + constructor(props) { + super(props); + const bytes = Uint8Array.from(atob(props.thumbHash), (c) => c.charCodeAt(0)); + this.url = thumbHashToDataURL(bytes); + } -export function isImageLoaded(fullHash) { - if (!fullHash) return false; - return loadedImageHashes.has(fullHash) || imageCache.has(fullHash); -} - -export function markImageLoaded(fullHash, img) { - if (!fullHash) return; - loadedImageHashes.add(fullHash); - if (img) { - imageCache.set(fullHash, img); + render() { + return ( + + ); } } - -export function getCachedImage(fullHash) { - if (!fullHash) return null; - const img = imageCache.get(fullHash); - if (img && img.complete && img.naturalWidth) { - return img; - } - return null; -} - -export function getThumbHashDataUrl(thumbHash) { - if (!thumbHash || typeof thumbHash !== 'string') return null; - let url = dataUrlCache.get(thumbHash); - if (!url) { - try { - const bytes = Uint8Array.from(atob(thumbHash), (c) => c.charCodeAt(0)); - url = thumbHashToDataURL(bytes); - dataUrlCache.set(thumbHash, url); - } catch (err) { - console.warn('[ThumbHashImage] thumbHashToDataURL failed:', err); - return null; - } - } - return url; -} - -export function decodeThumbHash(thumbHash) { - if (!thumbHash || typeof thumbHash !== 'string') return null; - try { - const bytes = Uint8Array.from(atob(thumbHash), (c) => c.charCodeAt(0)); - return thumbHashToRGBA(bytes); - } catch (err) { - console.warn('[ThumbHashImage] decode failed:', err); - return null; - } -} - -export function getThumbHashDimensions(thumbHash) { - const decoded = decodeThumbHash(thumbHash); - if (!decoded) return null; - return { w: decoded.w, h: decoded.h }; -} - -function paintThumbHash(canvas, thumbHash, explicitWidth, explicitHeight) { - const parent = canvas.parentElement; - const rect = parent ? parent.getBoundingClientRect() : null; - const width = explicitWidth || rect?.width; - const height = explicitHeight || rect?.height; - if (!width || !height) return; - - const decoded = decodeThumbHash(thumbHash); - if (!decoded) return; - const { w, h, rgba } = decoded; - - const src = document.createElement('canvas'); - src.width = w; - src.height = h; - src.getContext('2d').putImageData( - new ImageData(new Uint8ClampedArray(rgba), w, h), - 0, - 0, - ); - - canvas.width = Math.round(width); - canvas.height = Math.round(height); - const ctx = canvas.getContext('2d'); - if (!ctx) return; - - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = 'high'; - ctx.drawImage(src, 0, 0, canvas.width, canvas.height); -} - -export default function ThumbHashImage({ thumbHash, width, height, sx }) { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas || !thumbHash) return; - - paintThumbHash(canvas, thumbHash, width, height); - - const parent = canvas.parentElement; - if (!parent || typeof ResizeObserver === 'undefined') return undefined; - - const observer = new ResizeObserver(() => { - paintThumbHash(canvas, thumbHash, width, height); - }); - observer.observe(parent); - return () => observer.disconnect(); - }, [thumbHash, width, height]); - - return ( - - ); -} diff --git a/src/components/tiles/ArticleTile.jsx b/src/components/tiles/ArticleTile.jsx index f2faf4f..aac760c 100644 --- a/src/components/tiles/ArticleTile.jsx +++ b/src/components/tiles/ArticleTile.jsx @@ -7,12 +7,6 @@ import { TILE_SIZE, Tile, tileBaseSx } from 'dashgui/tiles'; import { formatPrice, getArticleGross } from '../../utils/price'; import companyAutomat from '../../automats/companyAutomat'; import { articleSku } from '../article/articleRoute'; -import { - getThumbHashDataUrl, - getCachedImage, - isImageLoaded, - markImageLoaded, -} from '../article/ThumbHashImage'; import { BrandLabel } from '../brand'; import { thumb } from '../../api'; import { diff --git a/src/components/tiles/CategoryTile.jsx b/src/components/tiles/CategoryTile.jsx index 9b97986..2382ed0 100644 --- a/src/components/tiles/CategoryTile.jsx +++ b/src/components/tiles/CategoryTile.jsx @@ -10,7 +10,6 @@ import PointOfSaleIcon from '@mui/icons-material/PointOfSale'; import PhotoCameraIcon from '@mui/icons-material/PhotoCamera'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import DeleteForeverOutlinedIcon from '@mui/icons-material/DeleteForeverOutlined'; -import { getThumbHashDataUrl, isImageLoaded, markImageLoaded } from '../article/ThumbHashImage'; import { thumb } from '../../api'; import { tileBaseSx } from 'dashgui/tiles'; import { diff --git a/src/routes/artikel/marken.jsx b/src/routes/artikel/marken.jsx index 0207474..103c882 100644 --- a/src/routes/artikel/marken.jsx +++ b/src/routes/artikel/marken.jsx @@ -1,5 +1,6 @@ import { PureComponent, createRef } from 'react'; import Box from '@mui/material/Box'; +import List from '@mui/material/List'; import CircularProgress from '@mui/material/CircularProgress'; import BackendError from '../../components/BackendError'; import FillSpace from 'dashgui/FillSpace'; @@ -78,6 +79,9 @@ class ArtikelMarkenPage extends PureComponent { + + {brands[0] ? : null} + ); }