fix: blurhash cache miss, design dropdown visibility + add Hauptwörter panel
- DrawCanvas: call onImageLoad for cached images (fixes permanent blur) - DrawIt: reset imageLoaded immediately on navigation (shows blur right away) - Design dropdown: always visible when picture loaded, no longer gated on designOptions or objects count - Fertigstellen: always visible when picture loaded, disabled when no objects - Hauptwörter: new panel above object list for picture-level words (db_words_db_pictures) - Backend: DELETE /api/directus/db-pictures/<id>/words/<junction_id> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<DbWord[]>([])
|
||||
const [pictureWordInput, setPictureWordInput] = useState('')
|
||||
|
||||
const canvasRef = useRef<DrawCanvasHandle>(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() {
|
||||
<div className="workspace">
|
||||
{/* Left sidebar: saved objects */}
|
||||
<aside className="sidebar">
|
||||
{currentPicture && (
|
||||
<div className="sidebar-panel">
|
||||
<h3 className="sidebar-heading">Hauptwörter</h3>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 6 }}>
|
||||
{pictureWords.length === 0 && (
|
||||
<span style={{ fontSize: 11, color: 'var(--text-2)' }}>Noch keine Hauptwörter</span>
|
||||
)}
|
||||
{pictureWords.map(w => (
|
||||
<span key={w.junction_id} style={{
|
||||
display: 'flex', alignItems: 'center', gap: 3,
|
||||
padding: '2px 8px', background: '#dcfce7', color: '#166534',
|
||||
borderRadius: 9999, fontSize: 11,
|
||||
}}>
|
||||
{w.titel_de}
|
||||
<button
|
||||
onClick={() => handleRemovePictureWord(w.junction_id!)}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#16a34a', padding: 0, fontSize: 13 }}
|
||||
>×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
<input
|
||||
type="text"
|
||||
value={pictureWordInput}
|
||||
onChange={e => setPictureWordInput(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') handleAddPictureWord() }}
|
||||
placeholder="Hauptwort 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 }}
|
||||
/>
|
||||
<button
|
||||
onClick={handleAddPictureWord}
|
||||
style={{ padding: '4px 10px', borderRadius: 'var(--r-sm)', background: '#16a34a', color: '#fff', border: 'none', cursor: 'pointer', fontSize: 12 }}
|
||||
>+</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="sidebar-panel" style={{ flex: 1 }}>
|
||||
<h3 className="sidebar-heading">
|
||||
Objekte
|
||||
@@ -338,14 +412,13 @@ export default function DrawIt() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{currentPicture && designOptions.length > 0 && (
|
||||
{currentPicture && (
|
||||
<div className="sidebar-panel">
|
||||
<h3 className="sidebar-heading">Design</h3>
|
||||
<select
|
||||
value={currentPicture.design || ''}
|
||||
onChange={async e => {
|
||||
const value = e.target.value
|
||||
// Optimistic update
|
||||
setPictureList(prev => prev.map(p => p.id === currentPicture.id ? { ...p, design: value || null } : p))
|
||||
try {
|
||||
await fetch(`/api/directus/db-pictures/${currentPicture.id}`, {
|
||||
@@ -369,13 +442,13 @@ export default function DrawIt() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{objects.length > 0 && (
|
||||
{currentPicture && (
|
||||
<div className="sidebar-panel">
|
||||
<button
|
||||
className="btn-primary btn-sm btn-block"
|
||||
onClick={finishPicture}
|
||||
disabled={finishing}
|
||||
style={{ background: 'var(--success, #16a34a)' }}
|
||||
disabled={finishing || objects.length === 0}
|
||||
style={{ background: objects.length > 0 ? 'var(--success, #16a34a)' : undefined, opacity: objects.length === 0 ? 0.5 : 1 }}
|
||||
>
|
||||
{finishing ? 'Wird fertiggestellt…' : '✅ Fertigstellen'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user