diff --git a/app.py b/app.py index 2d9a42e..9827875 100644 --- a/app.py +++ b/app.py @@ -1814,6 +1814,13 @@ def directus_db_picture_words(pic_id): return jsonify({"ok": True, "saved": saved}) +@app.route("/api/directus/db-pictures//words/", methods=["DELETE"]) +def directus_db_picture_word_delete(pic_id, junction_id): + token = request.headers.get("Authorization", "") + _directus("DELETE", f"/items/db_words_db_pictures/{junction_id}", token) + return jsonify({"ok": True}) + + @app.route("/api/directus/db-objects//pairs", methods=["GET", "POST"]) def directus_db_object_pairs(obj_id): token = request.headers.get("Authorization", "") diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 9af18d7..72cde36 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -498,6 +498,33 @@ export async function deleteDbObjectWord( return res.json() } +// Add a single word to a picture +export async function addDbPictureWord( + pictureId: string, + word: { titel_de: string; level: number }, + token: string +) { + const res = await fetch(`/api/directus/db-pictures/${pictureId}/words`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: token }, + body: JSON.stringify({ words: [word] }), + }) + return res.json() +} + +// Remove a single word from a picture by junction ID +export async function deleteDbPictureWord( + pictureId: string, + junctionId: string | number, + token: string +) { + const res = await fetch(`/api/directus/db-pictures/${pictureId}/words/${junctionId}`, { + method: 'DELETE', + headers: { Authorization: token }, + }) + return res.json() +} + export async function updateDbPair( pairId: string, payload: { diff --git a/frontend/src/components/DrawCanvas.tsx b/frontend/src/components/DrawCanvas.tsx index 6be2cea..e365349 100644 --- a/frontend/src/components/DrawCanvas.tsx +++ b/frontend/src/components/DrawCanvas.tsx @@ -272,6 +272,7 @@ export default forwardRef(function DrawCanvas( const cached = imageCache.get(imageSrc) if (cached) { applyImage(cached) + onImageLoad?.() // ← notify parent so blurhash hides for cached images return } diff --git a/frontend/src/pages/DrawIt.tsx b/frontend/src/pages/DrawIt.tsx index 9ee0c88..ed82154 100644 --- a/frontend/src/pages/DrawIt.tsx +++ b/frontend/src/pages/DrawIt.tsx @@ -12,6 +12,9 @@ import { getDbObjectWords, addDbObjectWord, deleteDbObjectWord, + getDbPictureWords, + addDbPictureWord, + deleteDbPictureWord, directusAssetUrl, getDesignOptions, } from '../api' @@ -59,6 +62,8 @@ export default function DrawIt() { const [statusMsg, setStatusMsg] = useState('') const [statusError, setStatusError] = useState(false) const [imageLoaded, setImageLoaded] = useState(false) + const [pictureWords, setPictureWords] = useState([]) + const [pictureWordInput, setPictureWordInput] = useState('') const canvasRef = useRef(null) @@ -68,6 +73,11 @@ export default function DrawIt() { return () => clearTimeout(t) }, [currentIndex]) + // Reset imageLoaded immediately on navigation so blurhash shows right away + useEffect(() => { + setImageLoaded(false) + }, [currentIndex]) + const currentPicture: DbPicture | null = debouncedIndex >= 0 && debouncedIndex < pictureList.length ? pictureList[debouncedIndex] : null @@ -93,13 +103,15 @@ export default function DrawIt() { getDesignOptions(token).then(setDesignOptions).catch(console.error) }, [token]) - // Load objects when picture changes, then load each object's word + // Load objects when picture changes, then load each object's words and picture words useEffect(() => { if (!currentPicture || !token) { setObjects([]); setSelectedObjectId(null) setObjectWords({}) setWordInputs({}) setImageLoaded(false) + setPictureWords([]) + setPictureWordInput('') return } getDbObjects(currentPicture.id, token) @@ -118,6 +130,10 @@ export default function DrawIt() { }) }) .catch(console.error) + // Load picture-level words + getDbPictureWords(currentPicture.id, token) + .then(words => setPictureWords(words)) + .catch(() => setPictureWords([])) }, [currentPicture?.id, token]) const showStatus = (msg: string, isError = false) => { @@ -233,6 +249,26 @@ export default function DrawIt() { } } + const handleAddPictureWord = async () => { + if (!token || !currentPicture) return + const titel_de = pictureWordInput.trim() + if (!titel_de) return + try { + await addDbPictureWord(currentPicture.id, { titel_de, level: 50 }, token) + const words = await getDbPictureWords(currentPicture.id, token) + setPictureWords(words) + setPictureWordInput('') + } catch (e) { showStatus('Fehler beim Hinzufügen.', true) } + } + + const handleRemovePictureWord = async (junctionId: string | number) => { + if (!token || !currentPicture) return + try { + await deleteDbPictureWord(currentPicture.id, junctionId, token) + setPictureWords(prev => prev.filter(w => w.junction_id !== junctionId)) + } catch (e) { showStatus('Fehler beim Entfernen.', true) } + } + const deleteObject = async (objId: string) => { if (!token) return try { @@ -270,6 +306,44 @@ export default function DrawIt() {
{/* Left sidebar: saved objects */}