import { useState, useEffect, useCallback, useRef } from 'react' import DrawCanvas, { type DrawCanvasHandle } from '../components/DrawCanvas' import Topbar from '../components/Topbar' import { getDbPictures, updateDbPictureStatus, getDbObjects, createDbObject, updateDbObject, deleteDbObject, getDbPictureWords, saveDbPictureWords, directusAssetUrl, } from '../api' import { useAuth } from '../context/AuthContext' import type { DbPicture, DbObject, DbWord, Selection, CanvasObject } from '../types' const ChevronLeftIcon = () => ( ) const ChevronRightIcon = () => ( ) const TrashIcon = () => ( ) export default function DrawIt() { const { token } = useAuth() const [pictureList, setPictureList] = useState([]) const [currentIndex, setCurrentIndex] = useState(-1) const [debouncedIndex, setDebouncedIndex] = useState(-1) const [objects, setObjects] = useState([]) const [selectedObjectId, setSelectedObjectId] = useState(null) const [currentSelections, setCurrentSelections] = useState([]) const [userNotes, setUserNotes] = useState('') // pending words (not yet saved) const [pendingWords, setPendingWords] = useState<{ titel_de: string; level: number }[]>([]) const [wordInput, setWordInput] = useState('') const [wordLevel, setWordLevel] = useState(50) const [wordInputVisible, setWordInputVisible] = useState(false) const wordInputRef = useRef(null) // saved words from Directus const [pictureWords, setPictureWords] = useState([]) const [savingWords, setSavingWords] = useState(false) const [editingNotes, setEditingNotes] = useState<{ id: string; notes: string } | null>(null) const [mode, setMode] = useState<'rect' | 'polygon'>('polygon') const [hasSelection, setHasSelection] = useState(false) const [saving, setSaving] = useState(false) const [finishing, setFinishing] = useState(false) const [statusMsg, setStatusMsg] = useState('') const [statusError, setStatusError] = useState(false) const canvasRef = useRef(null) // Debounce: only load picture data after 350ms of no navigation useEffect(() => { const t = setTimeout(() => setDebouncedIndex(currentIndex), 350) return () => clearTimeout(t) }, [currentIndex]) useEffect(() => { if (wordInputVisible) wordInputRef.current?.focus() }, [wordInputVisible]) const currentPicture: DbPicture | null = debouncedIndex >= 0 && debouncedIndex < pictureList.length ? pictureList[debouncedIndex] : null const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({ id: obj.id, visible: obj.visible !== false, selections: obj.selections, index: i + 1, hierarchy: 1, })) // Load db_pictures with status=draft useEffect(() => { if (!token) return getDbPictures(token, 'draft') .then(pics => { setPictureList(pics); setCurrentIndex(pics.length > 0 ? 0 : -1) }) .catch(console.error) }, [token]) // Load objects + words when picture changes useEffect(() => { if (!currentPicture || !token) { setObjects([]); setSelectedObjectId(null) setPictureWords([]); setPendingWords([]) return } getDbObjects(currentPicture.id, token) .then(objs => { setObjects(objs.map(o => ({ ...o, visible: true }))); setSelectedObjectId(null) }) .catch(console.error) getDbPictureWords(currentPicture.id, token) .then(setPictureWords) .catch(console.error) }, [currentPicture?.id, token]) const showStatus = (msg: string, isError = false) => { setStatusMsg(msg); setStatusError(isError) } const handleHasSelection = useCallback((has: boolean) => setHasSelection(has), []) const addWord = () => { const titel = wordInput.trim() if (!titel || pendingWords.some(w => w.titel_de === titel) || pictureWords.some(w => w.titel_de === titel)) { setWordInput(''); return } setPendingWords(prev => [...prev, { titel_de: titel, level: wordLevel }]) setWordInput('') setWordLevel(50) setWordInputVisible(false) } const saveWords = async () => { if (!currentPicture || !token || pendingWords.length === 0) return setSavingWords(true) try { await saveDbPictureWords(currentPicture.id, pendingWords, token) const updated = await getDbPictureWords(currentPicture.id, token) setPictureWords(updated) setPendingWords([]) showStatus('Wörter gespeichert.') } catch (e) { showStatus(e instanceof Error ? e.message : 'Fehler beim Speichern der Wörter.', true) } finally { setSavingWords(false) } } const addSelection = () => { const sel = canvasRef.current?.getCurrentSelection() if (!sel) { showStatus('Bitte zuerst einen Bereich auswählen.', true); return } setCurrentSelections(prev => { const next = [...prev, sel]; showStatus(`Auswahl ${next.length} hinzugefügt.`); return next }) canvasRef.current?.resetSelection() setHasSelection(false) } const saveObject = async () => { if (!currentPicture || !token || currentSelections.length === 0) return setSaving(true) try { const obj = await createDbObject({ picture: currentPicture.id, selections: currentSelections, user_notes: userNotes.trim() || null, }, token) setObjects(prev => [...prev, { ...obj, visible: true }]) setCurrentSelections([]) setUserNotes('') canvasRef.current?.resetSelection() showStatus('Objekt gespeichert.') } catch (e) { showStatus(e instanceof Error ? e.message : 'Fehler beim Speichern.', true) } finally { setSaving(false) } } // Mark picture as objects_created and remove from list const finishPicture = async () => { if (!currentPicture || !token) return setFinishing(true) try { await updateDbPictureStatus(currentPicture.id, 'objects_created', token) setPictureList(prev => prev.filter(p => p.id !== currentPicture.id)) setCurrentIndex(i => Math.max(0, i - 1)) setObjects([]) showStatus('Bild fertiggestellt.') } catch (e) { showStatus(e instanceof Error ? e.message : 'Fehler.', true) } finally { setFinishing(false) } } const saveNoteEdit = async () => { if (!editingNotes || !token) return try { await updateDbObject(editingNotes.id, { user_notes: editingNotes.notes }, token) setObjects(prev => prev.map(o => o.id === editingNotes.id ? { ...o, user_notes: editingNotes.notes } : o)) setEditingNotes(null) showStatus('Notizen gespeichert.') } catch (e) { showStatus(e instanceof Error ? e.message : 'Fehler.', true) } } const deleteObject = async (objId: string) => { if (!token) return try { await deleteDbObject(objId, token) setObjects(prev => prev.filter(o => o.id !== objId)) if (selectedObjectId === objId) setSelectedObjectId(null) showStatus('Objekt gelöscht.') } catch (e) { showStatus(e instanceof Error ? e.message : 'Fehler beim Löschen.', true) } } const imageNav = (
{pictureList.length > 0 ? <>{currentIndex + 1}/{pictureList.length} : Keine Bilder}
) return (
{/* Left sidebar: saved objects */}