bbox/polygon durch selections ersetzen

- bbox und polygon Felder in Directus versteckt (Daten bleiben)
- Alle Auswahlen laufen nur noch über das selections-Feld
- CanvasObject, DirectusObject, API und Zeichenlogik umgestellt
- Objekte mit mehreren Auswahlen werden korrekt auf Canvas gezeichnet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 21:36:22 +02:00
parent 807c733770
commit e18d9a5796
5 changed files with 31 additions and 47 deletions

View File

@@ -32,7 +32,7 @@ export function directusAssetUrl(mediaId: string, token: string): string {
return `${DIRECTUS_URL}/assets/${mediaId}?access_token=${token}`
}
import type { DirectusObject, BBox, Point } from './types'
import type { DirectusObject, Selection } from './types'
export async function getDirectusObjects(pictureId: string, token: string): Promise<DirectusObject[]> {
const res = await fetch(`/api/directus/objects?picture_id=${pictureId}`, {
@@ -45,9 +45,7 @@ export async function getDirectusObjects(pictureId: string, token: string): Prom
export async function createDirectusObject(payload: {
picture: string
bbox: BBox | null
polygon: Point[] | null
selections: import('./types').Selection[] | null
selections: Selection[] | null
user_notes: string | null
parent: string | null
}, token: string): Promise<DirectusObject> {

View File

@@ -81,45 +81,42 @@ export default forwardRef<DrawCanvasHandle, Props>(function DrawCanvas(
ctx.lineWidth = isSelected ? 3 : 2
ctx.setLineDash(isSelected ? [2, 2] : [4, 3])
const { polygon, bbox } = obj
if (polygon && polygon.length >= 3) {
ctx.beginPath()
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
for (let i = 1; i < polygon.length; i++) ctx.lineTo(polygon[i].x * scale, polygon[i].y * scale)
ctx.closePath()
ctx.fill()
ctx.stroke()
} else if (bbox) {
ctx.fillRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
const drawShape = (sel: { mode: string; bbox?: { x: number; y: number; width: number; height: number } | null; polygon?: { x: number; y: number }[] | null }) => {
const { polygon, bbox } = sel as { polygon?: { x: number; y: number }[] | null; bbox?: { x: number; y: number; width: number; height: number } | null }
if (polygon && polygon.length >= 3) {
ctx.beginPath()
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
for (let i = 1; i < polygon.length; i++) ctx.lineTo(polygon[i].x * scale, polygon[i].y * scale)
ctx.closePath()
ctx.fill()
ctx.stroke()
} else if (bbox) {
ctx.fillRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
}
}
for (const sel of obj.selections ?? []) drawShape(sel)
// White highlight ring for selected object
if (isSelected) {
ctx.strokeStyle = '#ffffff'
ctx.lineWidth = 2
ctx.setLineDash([])
if (polygon && polygon.length >= 3) {
ctx.beginPath()
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
for (let i = 1; i < polygon.length; i++) ctx.lineTo(polygon[i].x * scale, polygon[i].y * scale)
ctx.closePath()
ctx.stroke()
} else if (bbox) {
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
}
for (const sel of obj.selections ?? []) drawShape(sel)
}
// Index number in object center
// Index number: center of first selection
const indexLabel = typeof obj.index === 'number' ? String(obj.index) : ''
if (indexLabel) {
const first = obj.selections?.[0] as { polygon?: { x: number; y: number }[] | null; bbox?: { x: number; y: number; width: number; height: number } | null } | undefined
let cx = 0, cy = 0
if (bbox) {
cx = (bbox.x + bbox.width / 2) * scale
cy = (bbox.y + bbox.height / 2) * scale
} else if (polygon && polygon.length > 0) {
const xs = polygon.map(p => p.x)
const ys = polygon.map(p => p.y)
if (first?.bbox) {
cx = (first.bbox.x + first.bbox.width / 2) * scale
cy = (first.bbox.y + first.bbox.height / 2) * scale
} else if (first?.polygon && first.polygon.length > 0) {
const xs = first.polygon.map((p: { x: number; y: number }) => p.x)
const ys = first.polygon.map((p: { x: number; y: number }) => p.y)
cx = ((Math.min(...xs) + Math.max(...xs)) / 2) * scale
cy = ((Math.min(...ys) + Math.max(...ys)) / 2) * scale
}

View File

@@ -50,8 +50,7 @@ export default function DrawIt() {
const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({
id: obj.id,
visible: obj.visible !== false,
bbox: obj.bbox,
polygon: obj.polygon,
selections: obj.selections,
index: i + 1,
hierarchy: 1,
}))
@@ -88,11 +87,8 @@ export default function DrawIt() {
if (!currentPicture || !token || currentSelections.length === 0) return
setSaving(true)
try {
const first = currentSelections[0]
const obj = await createDirectusObject({
picture: currentPicture.id,
bbox: first.mode === 'rect' ? (first.bbox ?? null) : null,
polygon: first.mode === 'polygon' ? (first.polygon ?? null) : null,
selections: currentSelections,
user_notes: userNotes.trim() || null,
parent: parentId,
@@ -182,7 +178,7 @@ export default function DrawIt() {
/>
<div className="object-item-text">
<strong>Objekt {i + 1}</strong>
<span>{obj.bbox ? `Rect ${Math.round(obj.bbox.width)}×${Math.round(obj.bbox.height)}` : obj.polygon ? `Poly ${obj.polygon.length} Pkt.` : ''}</span>
<span>{obj.selections?.length ? `${obj.selections.length} Auswahl(en)` : ''}</span>
{obj.parent && <span style={{ color: 'var(--primary-muted-fg)', fontSize: 11 }}> Kind von #{objects.findIndex(o => o.id === obj.parent) + 1}</span>}
</div>
<button

View File

@@ -29,8 +29,7 @@ export interface Sentence {
export interface CanvasObject {
id: string
visible?: boolean
bbox?: BBox | null
polygon?: Point[] | null
selections?: Selection[] | null
hierarchy?: number
index?: number
}
@@ -40,8 +39,6 @@ export interface DirectusObject {
id: string
status: string
picture: string
bbox: BBox | null
polygon: Point[] | null
selections: Selection[] | null
user_notes: string | null
parent: string | null