Compare commits
7 Commits
claude/aff
...
0340f9bb7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0340f9bb7d | ||
|
|
5e0de3014e | ||
|
|
860391bcbe | ||
|
|
cc782c0ef0 | ||
|
|
9acc1d93b4 | ||
|
|
08cce17976 | ||
|
|
202d4333a8 |
63
app.py
63
app.py
@@ -157,7 +157,10 @@ def directus_picture_words(pic_id):
|
|||||||
if not title_de:
|
if not title_de:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
wid, _ = _find_or_create_word(title_de, level, token)
|
wid, is_new = _find_or_create_word(title_de, level, token)
|
||||||
|
if not is_new:
|
||||||
|
# Wort existiert bereits → Level aktualisieren
|
||||||
|
_directus("PATCH", f"/items/words/{wid}", token, {"level": level})
|
||||||
_ensure_link(
|
_ensure_link(
|
||||||
"words_pictures",
|
"words_pictures",
|
||||||
{"words_id": wid, "pictures_id": pic_id},
|
{"words_id": wid, "pictures_id": pic_id},
|
||||||
@@ -1572,6 +1575,64 @@ def setup_directus_schema():
|
|||||||
"failed": len(failed), "results": results})
|
"failed": len(failed), "results": results})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/setup-words-pictures", methods=["POST"])
|
||||||
|
def setup_words_pictures():
|
||||||
|
"""
|
||||||
|
Einmalig ausführen: Konfiguriert die M2M-Relation words ↔ pictures
|
||||||
|
via words_pictures Junction in Directus.
|
||||||
|
Idempotent – bereits vorhandene Felder/Relationen werden übersprungen.
|
||||||
|
"""
|
||||||
|
token = request.headers.get("Authorization", "")
|
||||||
|
results = []
|
||||||
|
|
||||||
|
def do(label, method, path, body=None):
|
||||||
|
data, status = _directus(method, path, token, body)
|
||||||
|
ok = status in (200, 201, 204)
|
||||||
|
results.append({"label": label, "status": status, "ok": ok,
|
||||||
|
"err": None if ok else (data.get("errors") or data)})
|
||||||
|
return ok
|
||||||
|
|
||||||
|
# Sicherstellen dass Junction-Collection existiert
|
||||||
|
_ensure_junction("words_pictures", "words_id", "pictures_id", token)
|
||||||
|
|
||||||
|
# special=m2m auf Alias-Felder setzen (idempotent)
|
||||||
|
do("patch pictures.linked_words special", "PATCH", "/fields/pictures/linked_words", {
|
||||||
|
"type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{words_id.title_de}}"},
|
||||||
|
"note": "Verknüpfte Safe Words"},
|
||||||
|
})
|
||||||
|
do("patch words.linked_pictures special", "PATCH", "/fields/words/linked_pictures", {
|
||||||
|
"type": "alias",
|
||||||
|
"meta": {"special": ["m2m"], "interface": "list-m2m",
|
||||||
|
"options": {"template": "{{pictures_id.media}}"},
|
||||||
|
"note": "Verknüpfte Bilder"},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Relation words_pictures.words_id → words
|
||||||
|
do("relation words_pictures.words_id→words", "POST", "/relations", {
|
||||||
|
"collection": "words_pictures", "field": "words_id",
|
||||||
|
"related_collection": "words",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "pictures_id",
|
||||||
|
"one_field": "linked_pictures",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
# Relation words_pictures.pictures_id → pictures
|
||||||
|
do("relation words_pictures.pictures_id→pictures", "POST", "/relations", {
|
||||||
|
"collection": "words_pictures", "field": "pictures_id",
|
||||||
|
"related_collection": "pictures",
|
||||||
|
"schema": {"on_delete": "CASCADE"},
|
||||||
|
"meta": {"junction_field": "words_id",
|
||||||
|
"one_field": "linked_words",
|
||||||
|
"one_deselect_action": "nullify"},
|
||||||
|
})
|
||||||
|
|
||||||
|
failed = [r for r in results if not r["ok"]]
|
||||||
|
return jsonify({"ok": len(failed) == 0, "total": len(results),
|
||||||
|
"failed": len(failed), "results": results})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default function DrawIt() {
|
|||||||
|
|
||||||
const [pictureList, setPictureList] = useState<DirectusPicture[]>([])
|
const [pictureList, setPictureList] = useState<DirectusPicture[]>([])
|
||||||
const [currentIndex, setCurrentIndex] = useState(-1)
|
const [currentIndex, setCurrentIndex] = useState(-1)
|
||||||
|
const [debouncedIndex, setDebouncedIndex] = useState(-1)
|
||||||
const [objects, setObjects] = useState<DirectusObject[]>([])
|
const [objects, setObjects] = useState<DirectusObject[]>([])
|
||||||
const [selectedObjectId, setSelectedObjectId] = useState<string | null>(null)
|
const [selectedObjectId, setSelectedObjectId] = useState<string | null>(null)
|
||||||
const [currentSelections, setCurrentSelections] = useState<Selection[]>([])
|
const [currentSelections, setCurrentSelections] = useState<Selection[]>([])
|
||||||
@@ -52,6 +53,12 @@ export default function DrawIt() {
|
|||||||
|
|
||||||
const canvasRef = useRef<DrawCanvasHandle>(null)
|
const canvasRef = useRef<DrawCanvasHandle>(null)
|
||||||
|
|
||||||
|
// Debounce: Bild erst laden wenn 350ms keine weitere Navigation
|
||||||
|
useEffect(() => {
|
||||||
|
const t = setTimeout(() => setDebouncedIndex(currentIndex), 350)
|
||||||
|
return () => clearTimeout(t)
|
||||||
|
}, [currentIndex])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (safeWordInputVisible) safeWordInputRef.current?.focus()
|
if (safeWordInputVisible) safeWordInputRef.current?.focus()
|
||||||
}, [safeWordInputVisible])
|
}, [safeWordInputVisible])
|
||||||
@@ -83,8 +90,9 @@ export default function DrawIt() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currentPicture folgt dem debouncedIndex → lädt erst wenn Navigation pausiert
|
||||||
const currentPicture: DirectusPicture | null =
|
const currentPicture: DirectusPicture | null =
|
||||||
currentIndex >= 0 && currentIndex < pictureList.length ? pictureList[currentIndex] : null
|
debouncedIndex >= 0 && debouncedIndex < pictureList.length ? pictureList[debouncedIndex] : null
|
||||||
|
|
||||||
// Map DirectusObject → CanvasObject for rendering
|
// Map DirectusObject → CanvasObject for rendering
|
||||||
const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({
|
const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user