diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8f51435..bcb85bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "content-mentor-frontend", "version": "0.1.0", "dependencies": { + "blurhash": "^2.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0" @@ -1223,6 +1224,12 @@ "node": ">=6.0.0" } }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 39140bf..642e265 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "blurhash": "^2.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0" diff --git a/frontend/src/components/BlurhashCanvas.tsx b/frontend/src/components/BlurhashCanvas.tsx new file mode 100644 index 0000000..4f2dfa1 --- /dev/null +++ b/frontend/src/components/BlurhashCanvas.tsx @@ -0,0 +1,50 @@ +import { useEffect, useRef } from 'react' +import { decode } from 'blurhash' + +interface Props { + hash: string + width?: number + height?: number + style?: React.CSSProperties +} + +/** + * Rendert einen Blurhash-Hash als Canvas-Platzhalter. + * Wird als absolut positionierter Layer unter dem echten Bild gelegt + * und verschwindet sobald das echte Bild geladen ist. + */ +export default function BlurhashCanvas({ hash, width = 32, height = 32, style }: Props) { + const canvasRef = useRef(null) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas || !hash) return + try { + const pixels = decode(hash, width, height) + const ctx = canvas.getContext('2d') + if (!ctx) return + const imageData = ctx.createImageData(width, height) + imageData.data.set(pixels) + ctx.putImageData(imageData, 0, 0) + } catch { + // Ungültiger Hash → kein Crash + } + }, [hash, width, height]) + + return ( + + ) +} diff --git a/frontend/src/components/DrawCanvas.tsx b/frontend/src/components/DrawCanvas.tsx index b205a8c..6be2cea 100644 --- a/frontend/src/components/DrawCanvas.tsx +++ b/frontend/src/components/DrawCanvas.tsx @@ -22,11 +22,12 @@ interface Props { selectedObjectId: string | null mode: 'rect' | 'polygon' onHasSelection: (has: boolean) => void + onImageLoad?: () => void readOnly?: boolean } export default forwardRef(function DrawCanvas( - { imageSrc, objects, selectedObjectId, mode, onHasSelection, readOnly = false }, + { imageSrc, objects, selectedObjectId, mode, onHasSelection, onImageLoad, readOnly = false }, ref ) { const canvasRef = useRef(null) @@ -278,6 +279,7 @@ export default forwardRef(function DrawCanvas( img.onload = () => { imageCache.set(imageSrc, img) applyImage(img) + onImageLoad?.() } img.onerror = () => console.error('Fehler beim Laden des Bildes:', imageSrc) img.src = imageSrc diff --git a/frontend/src/pages/DrawIt.tsx b/frontend/src/pages/DrawIt.tsx index ed9fa19..04a454b 100644 --- a/frontend/src/pages/DrawIt.tsx +++ b/frontend/src/pages/DrawIt.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react' import DrawCanvas, { type DrawCanvasHandle } from '../components/DrawCanvas' +import BlurhashCanvas from '../components/BlurhashCanvas' import Topbar from '../components/Topbar' import { getDbPictures, @@ -57,6 +58,7 @@ export default function DrawIt() { const [finishing, setFinishing] = useState(false) const [statusMsg, setStatusMsg] = useState('') const [statusError, setStatusError] = useState(false) + const [imageLoaded, setImageLoaded] = useState(false) const canvasRef = useRef(null) @@ -94,6 +96,7 @@ export default function DrawIt() { if (!currentPicture || !token) { setObjects([]); setSelectedObjectId(null) setPictureWords([]); setPendingWords([]) + setImageLoaded(false) return } getDbObjects(currentPicture.id, token) @@ -315,8 +318,17 @@ export default function DrawIt() {
+ {/* Blurhash-Platzhalter: sichtbar solange das echte Bild noch lädt */} + {currentPicture?.blurhash && !imageLoaded && ( + + )} setImageLoaded(true)} />
diff --git a/frontend/src/pages/GenerateIt.tsx b/frontend/src/pages/GenerateIt.tsx index bc2e881..1aa4f2e 100644 --- a/frontend/src/pages/GenerateIt.tsx +++ b/frontend/src/pages/GenerateIt.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from 'react' import DrawCanvas, { type DrawCanvasHandle } from '../components/DrawCanvas' +import BlurhashCanvas from '../components/BlurhashCanvas' import Topbar from '../components/Topbar' import { getDbPictures, @@ -285,6 +286,7 @@ export default function GenerateIt() { const [currentIndex, setCurrentIndex] = useState(-1) const [dbObjects, setDbObjects] = useState([]) const [selectedObjId, setSelectedObjId] = useState(null) + const [imageLoaded, setImageLoaded] = useState(false) const [pairs, setPairs] = useState([]) const [pairsLoading, setPairsLoading] = useState(false) @@ -310,7 +312,7 @@ export default function GenerateIt() { // Load db_objects when picture changes useEffect(() => { if (!currentPicture || !token) { - setDbObjects([]); setSelectedObjId(null); setPairs([]) + setDbObjects([]); setSelectedObjId(null); setPairs([]); setImageLoaded(false) return } getDbObjects(currentPicture.id, token) @@ -404,7 +406,16 @@ export default function GenerateIt() { {/* Center: Canvas */}
-
+
+ {/* Blurhash-Platzhalter: sichtbar solange das echte Bild noch lädt */} + {currentPicture?.blurhash && !imageLoaded && ( + + )} {}} + onImageLoad={() => setImageLoaded(true)} readOnly />