diff --git a/app.py b/app.py index b5b0bdc..2d9a42e 100644 --- a/app.py +++ b/app.py @@ -1710,10 +1710,20 @@ def _find_or_create_db_word(titel_de: str, level: int, token: str) -> tuple: def directus_db_pictures(): token = request.headers.get("Authorization", "") pic_status = request.args.get("status", "draft") - data, status = _directus("GET", f"/items/db_pictures?filter[status][_eq]={pic_status}&fields=id,picture,blurhash,status&sort=date_created", token) + data, status = _directus("GET", f"/items/db_pictures?filter[status][_eq]={pic_status}&fields=id,picture,blurhash,status,design&sort=date_created", token) return jsonify(data), status +@app.route("/api/directus/db-pictures/design-options", methods=["GET"]) +def directus_db_pictures_design_options(): + token = request.headers.get("Authorization", "") + data, status = _directus("GET", "/fields/db_pictures/design", token) + if status != 200: + return jsonify({"choices": []}), 200 + choices = (data.get("data") or data).get("meta", {}).get("options", {}).get("choices", []) + return jsonify({"choices": choices}) + + @app.route("/api/directus/db-pictures/", methods=["PATCH"]) def directus_db_picture_patch(pic_id): token = request.headers.get("Authorization", "") diff --git a/frontend/src/api.ts b/frontend/src/api.ts index fb5fb95..9af18d7 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -330,6 +330,14 @@ export async function purgeAllOrphans(token: string): Promise<{ orphans_removed: import type { DbPicture, DbObject, DbWord, DbPair } from './types' +export async function getDesignOptions(token: string): Promise<{ text: string; value: string; color?: string }[]> { + const res = await fetch('/api/directus/db-pictures/design-options', { + headers: { Authorization: token }, + }) + const data = await res.json() + return data.choices || [] +} + export async function getDbPictures(token: string, status = 'draft'): Promise { const res = await fetch(`/api/directus/db-pictures?status=${status}`, { headers: { Authorization: `Bearer ${token}` }, diff --git a/frontend/src/pages/DrawIt.tsx b/frontend/src/pages/DrawIt.tsx index c5c1769..9ee0c88 100644 --- a/frontend/src/pages/DrawIt.tsx +++ b/frontend/src/pages/DrawIt.tsx @@ -13,6 +13,7 @@ import { addDbObjectWord, deleteDbObjectWord, directusAssetUrl, + getDesignOptions, } from '../api' import { useAuth } from '../context/AuthContext' import type { DbPicture, DbObject, DbWord, Selection, CanvasObject } from '../types' @@ -47,6 +48,9 @@ export default function DrawIt() { const [objectWords, setObjectWords] = useState>({}) // per-object word input values: objectId → current input text const [wordInputs, setWordInputs] = useState>({}) + const [pendingWords, setPendingWords] = useState([]) + const [newWordInput, setNewWordInput] = useState('') + const [designOptions, setDesignOptions] = useState<{ text: string; value: string }[]>([]) const [editingNotes, setEditingNotes] = useState<{ id: string; notes: string } | null>(null) const [mode, setMode] = useState<'rect' | 'polygon'>('polygon') const [hasSelection, setHasSelection] = useState(false) @@ -83,6 +87,12 @@ export default function DrawIt() { .catch(console.error) }, [token]) + // Load design options once on mount + useEffect(() => { + if (!token) return + getDesignOptions(token).then(setDesignOptions).catch(console.error) + }, [token]) + // Load objects when picture changes, then load each object's word useEffect(() => { if (!currentPicture || !token) { @@ -132,6 +142,13 @@ export default function DrawIt() { } } + const handleAddPendingWord = () => { + const w = newWordInput.trim() + if (!w || pendingWords.includes(w)) return + setPendingWords(prev => [...prev, w]) + setNewWordInput('') + } + const handleRemoveObjectWord = async (objId: string, junctionId: string | number) => { if (!token) return try { @@ -162,10 +179,22 @@ export default function DrawIt() { selections: currentSelections, user_notes: userNotes.trim() || null, }, token) + // Save pending words to the new object + const savedWords: DbWord[] = [] + for (const w of pendingWords) { + try { + const result = await addDbObjectWord(obj.id, { titel_de: w, level: 50 }, token) + if (result.ok) { + savedWords.push({ junction_id: result.junction_id, word_id: result.word_id, titel_de: w, level: 50, status: 'draft' }) + } + } catch {} + } setObjects(prev => [...prev, { ...obj, visible: true }]) - setObjectWords(prev => ({ ...prev, [obj.id]: [] })) + setObjectWords(prev => ({ ...prev, [obj.id]: savedWords })) setCurrentSelections([]) setUserNotes('') + setPendingWords([]) + setNewWordInput('') canvasRef.current?.resetSelection() showStatus('Objekt gespeichert.') } catch (e) { @@ -303,50 +332,43 @@ export default function DrawIt() { )} - {/* Per-object multi-word chips */} -
e.stopPropagation()}> - -
- {(objectWords[obj.id] || []).map(w => ( - - {w.titel_de} - - - ))} -
-
- setWordInputs(prev => ({ ...prev, [obj.id]: e.target.value }))} - onKeyDown={e => { if (e.key === 'Enter') handleAddObjectWord(obj.id) }} - placeholder="Wort hinzufügen…" - style={{ - flex: 1, padding: '3px 7px', borderRadius: 'var(--r-sm)', - border: '1px solid var(--border)', background: 'var(--surface-2)', - color: 'var(--text-1)', fontFamily: 'var(--font)', fontSize: 12, - }} - /> - -
-
))} )} + {currentPicture && designOptions.length > 0 && ( +
+

Design

+ +
+ )} + {objects.length > 0 && (
+ +
+

+ Wörter + {selectedObjectId + ? ` (Objekt ${objects.findIndex(o => o.id === selectedObjectId) + 1})` + : ' (neues Objekt)'} +

+ + {/* Existing word chips for selected object */} + {selectedObjectId && ( +
+ {(objectWords[selectedObjectId] || []).map(w => ( + + {w.titel_de} + + + ))} + {(objectWords[selectedObjectId] || []).length === 0 && ( + Noch keine Wörter + )} +
+ )} + + {/* Pending words for new object */} + {!selectedObjectId && pendingWords.length > 0 && ( +
+ {pendingWords.map((w, i) => ( + + {w} + + + ))} +
+ )} + + {/* Add word input */} +
+ selectedObjectId + ? setWordInputs(prev => ({ ...prev, [selectedObjectId]: e.target.value })) + : setNewWordInput(e.target.value)} + onKeyDown={e => { + if (e.key === 'Enter') selectedObjectId ? handleAddObjectWord(selectedObjectId) : handleAddPendingWord() + }} + placeholder="Wort hinzufügen…" + style={{ flex:1, padding:'4px 8px', borderRadius:'var(--r-sm)', border:'1px solid var(--border)', background:'var(--surface-2)', color:'var(--text-1)', fontFamily:'var(--font)', fontSize:12 }} + /> + +
+
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index f3a368c..4ec204e 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -61,6 +61,7 @@ export interface DbPicture { picture: string // UUID → directus_files (für asset URL) blurhash: string | null status: string + design: string | null } export interface DbObject {