feat(draw): Linien-Typ-Abfrage – Normal / Leitung / Rettungsachse (v1.4.13)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 31m14s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 31m14s
- Beim Zeichnen von Linie/Pfeil fragt der Dialog jetzt den Linientyp ab: Normal (gewählte Farbe), Leitung (blau, dicker), Rettungsachse (grün, gestrichelt) - Typ in properties.lineType gespeichert; Leitung/Rettungsachse setzen konventionelle Farbe + Stärke, Normal behält die Zeichenfarbe - Eigener MapLibre-Layer mit Dash-Muster für Rettungsachse (klar als freizuhaltende Achse erkennbar) - Typ-Auswahl auch ohne Label übernehmbar; Änderung wird live gesynct - ROADMAP: 5.1 abgehakt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import { RightSidebar } from '@/components/layout/right-sidebar'
|
||||
import { ProjectDialog } from '@/components/dialogs/project-dialog'
|
||||
import { ExerciseCockpit } from '@/components/exercise/exercise-cockpit'
|
||||
import { TextDialog } from '@/components/dialogs/text-dialog'
|
||||
import { LineLabelDialog } from '@/components/dialogs/line-label-dialog'
|
||||
import { LineLabelDialog, LINE_KINDS, type LineKind } from '@/components/dialogs/line-label-dialog'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useAuth } from '@/components/providers/auth-provider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -668,22 +668,38 @@ export default function AppPage() {
|
||||
broadcastFeatures(newFeatures)
|
||||
}, [addAudit, broadcastFeatures])
|
||||
|
||||
const handleLineLabelConfirm = useCallback((label: string) => {
|
||||
if (pendingLineFeature && label) {
|
||||
setFeatures(prev => prev.map(f =>
|
||||
f.id === pendingLineFeature.id
|
||||
? { ...f, properties: { ...f.properties, label } }
|
||||
: f
|
||||
))
|
||||
// Wendet den gewählten Linientyp an: setzt properties.lineType und – bei Leitung/
|
||||
// Rettungsachse – die konventionelle Farbe/Stärke (Normal behält die Zeichenfarbe).
|
||||
const applyLineKind = useCallback((f: DrawFeature, kind: LineKind, label: string): DrawFeature => {
|
||||
const def = LINE_KINDS.find(k => k.value === kind)
|
||||
const properties: Record<string, any> = { ...f.properties, lineType: kind }
|
||||
if (def && kind !== 'normal') {
|
||||
properties.color = def.color
|
||||
properties.width = def.width
|
||||
}
|
||||
if (label) properties.label = label
|
||||
return { ...f, properties }
|
||||
}, [])
|
||||
|
||||
const finalizeLineFeature = useCallback((kind: LineKind, label: string) => {
|
||||
if (pendingLineFeature) {
|
||||
const next = featuresRef.current.map(f =>
|
||||
f.id === pendingLineFeature.id ? applyLineKind(f, kind, label) : f
|
||||
)
|
||||
setFeatures(next)
|
||||
broadcastFeatures(next)
|
||||
}
|
||||
setPendingLineFeature(null)
|
||||
setIsLineLabelDialogOpen(false)
|
||||
}, [pendingLineFeature])
|
||||
}, [pendingLineFeature, applyLineKind, broadcastFeatures])
|
||||
|
||||
const handleLineLabelSkip = useCallback(() => {
|
||||
setPendingLineFeature(null)
|
||||
setIsLineLabelDialogOpen(false)
|
||||
}, [])
|
||||
const handleLineLabelConfirm = useCallback((label: string, kind: LineKind) => {
|
||||
finalizeLineFeature(kind, label)
|
||||
}, [finalizeLineFeature])
|
||||
|
||||
const handleLineLabelSkip = useCallback((kind: LineKind) => {
|
||||
finalizeLineFeature(kind, '')
|
||||
}, [finalizeLineFeature])
|
||||
|
||||
const handleSymbolDrop = useCallback((iconId: string, coordinates: [number, number], imageUrl?: string) => {
|
||||
const currentZoom = mapRef.current?.getZoom() || 17
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -12,28 +13,44 @@ import {
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog'
|
||||
|
||||
export type LineKind = 'normal' | 'leitung' | 'rettungsachse'
|
||||
|
||||
// Zentrale Definition der Linientypen — Farbe/Stärke werden beim Zeichnen übernommen.
|
||||
export const LINE_KINDS: { value: LineKind; label: string; color: string; width: number; dashed: boolean; hint: string }[] = [
|
||||
{ value: 'normal', label: 'Normal', color: '', width: 0, dashed: false, hint: 'Freie Linie in gewählter Farbe' },
|
||||
{ value: 'leitung', label: 'Leitung', color: '#2563eb', width: 4, dashed: false, hint: 'Schlauchleitung (blau)' },
|
||||
{ value: 'rettungsachse', label: 'Rettungsachse', color: '#16a34a', width: 6, dashed: true, hint: 'Freihalten — Rettungs-/Zufahrtsachse (grün, gestrichelt)' },
|
||||
]
|
||||
|
||||
interface LineLabelDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onConfirm: (label: string) => void
|
||||
onSkip: () => void
|
||||
lineType: 'linestring' | 'arrow' | 'polygon'
|
||||
onConfirm: (label: string, kind: LineKind) => void
|
||||
onSkip: (kind: LineKind) => void
|
||||
lineType: 'linestring' | 'arrow' | 'polygon' | 'dangerzone'
|
||||
}
|
||||
|
||||
export function LineLabelDialog({ open, onOpenChange, onConfirm, onSkip, lineType }: LineLabelDialogProps) {
|
||||
const [label, setLabel] = useState('')
|
||||
const [kind, setKind] = useState<LineKind>('normal')
|
||||
|
||||
// Linientyp-Auswahl nur für echte Linien/Pfeile (nicht für Flächen/Gefahrenzonen)
|
||||
const showKindSelector = lineType === 'linestring' || lineType === 'arrow'
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setLabel('')
|
||||
if (open) {
|
||||
setLabel('')
|
||||
setKind('normal')
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm(label.trim())
|
||||
onConfirm(label.trim(), kind)
|
||||
setLabel('')
|
||||
}
|
||||
|
||||
const handleSkip = () => {
|
||||
onSkip()
|
||||
onSkip(kind)
|
||||
setLabel('')
|
||||
}
|
||||
|
||||
@@ -47,8 +64,10 @@ export function LineLabelDialog({ open, onOpenChange, onConfirm, onSkip, lineTyp
|
||||
}
|
||||
}
|
||||
|
||||
const title = lineType === 'polygon' ? 'Fläche beschriften' : 'Leitung beschriften'
|
||||
const placeholder = lineType === 'polygon'
|
||||
const title = lineType === 'polygon' || lineType === 'dangerzone'
|
||||
? 'Fläche beschriften'
|
||||
: showKindSelector ? 'Linie festlegen' : 'Leitung beschriften'
|
||||
const placeholder = lineType === 'polygon' || lineType === 'dangerzone'
|
||||
? 'z.B. Brandzone, Sperrgebiet...'
|
||||
: 'z.B. 1, L2, Zuleitung...'
|
||||
|
||||
@@ -58,7 +77,41 @@ export function LineLabelDialog({ open, onOpenChange, onConfirm, onSkip, lineTyp
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 py-2">
|
||||
<div className="space-y-4 py-2">
|
||||
{showKindSelector && (
|
||||
<div className="space-y-1.5">
|
||||
<Label>Linientyp</Label>
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{LINE_KINDS.map((k) => (
|
||||
<button
|
||||
key={k.value}
|
||||
type="button"
|
||||
onClick={() => setKind(k.value)}
|
||||
className={cn(
|
||||
'flex flex-col items-center gap-1 rounded-lg border-2 px-1.5 py-2 text-xs font-medium transition-all',
|
||||
kind === k.value
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-muted-foreground'
|
||||
)}
|
||||
title={k.hint}
|
||||
>
|
||||
<span
|
||||
className="h-1 w-8 rounded-full"
|
||||
style={{
|
||||
backgroundColor: k.color || '#64748b',
|
||||
...(k.dashed
|
||||
? { background: `repeating-linear-gradient(90deg, ${k.color} 0 5px, transparent 5px 9px)` }
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
{k.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">{LINE_KINDS.find(k => k.value === kind)?.hint}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="line-label">Bezeichnung (optional)</Label>
|
||||
<Input
|
||||
@@ -67,16 +120,16 @@ export function LineLabelDialog({ open, onOpenChange, onConfirm, onSkip, lineTyp
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
autoFocus={!showKindSelector}
|
||||
/>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Wird am Mittelpunkt auf der Karte angezeigt. Leer lassen für keine Beschriftung.
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Wird am Mittelpunkt der Leitung auf der Karte angezeigt. Leer lassen für keine Beschriftung.
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={handleSkip}>
|
||||
Ohne Label
|
||||
{showKindSelector ? 'Übernehmen, ohne Label' : 'Ohne Label'}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={!label.trim()}>
|
||||
Beschriften
|
||||
|
||||
@@ -810,12 +810,12 @@ export function MapView({
|
||||
},
|
||||
})
|
||||
|
||||
// Line layer
|
||||
// Line layer — alle Linien AUSSER Rettungsachse (die bekommt ihren eigenen gestrichelten Layer)
|
||||
m.addLayer({
|
||||
id: 'draw-lines',
|
||||
type: 'line',
|
||||
source: 'draw-features',
|
||||
filter: ['==', ['geometry-type'], 'LineString'],
|
||||
filter: ['all', ['==', ['geometry-type'], 'LineString'], ['!=', ['get', 'lineType'], 'rettungsachse']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
@@ -826,6 +826,23 @@ export function MapView({
|
||||
},
|
||||
})
|
||||
|
||||
// Rettungsachse — gestrichelt, damit sie als freizuhaltende Achse klar erkennbar ist
|
||||
m.addLayer({
|
||||
id: 'draw-lines-rettungsachse',
|
||||
type: 'line',
|
||||
source: 'draw-features',
|
||||
filter: ['all', ['==', ['geometry-type'], 'LineString'], ['==', ['get', 'lineType'], 'rettungsachse']],
|
||||
layout: {
|
||||
'line-cap': 'butt',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': ['get', 'color'],
|
||||
'line-width': ['coalesce', ['get', 'width'], 6],
|
||||
'line-dasharray': [2, 1.5],
|
||||
},
|
||||
})
|
||||
|
||||
// Point layer
|
||||
m.addLayer({
|
||||
id: 'draw-points',
|
||||
@@ -1378,6 +1395,7 @@ export function MapView({
|
||||
color: (f.properties.color as string) || '#000000',
|
||||
width: (f.properties.width as number) || 3,
|
||||
isDangerZone: f.properties.isDangerZone ? 1 : 0,
|
||||
lineType: (f.properties.lineType as string) || 'normal',
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user