refactor(wind): Wind nur noch in der Kopfzeile (Karte = nur Nordpfeil) (v1.8.9)
- Wind-Anzeige von der Karte entfernt (war doppelt und nicht als "Wind" erkennbar); MapCompass zeigt jetzt nur den Nordpfeil - Kopfzeile: Wind-Badge ist klickbar → Einsteller (Live vom Standort / Richtung wählen / entfernen), klar mit "Wind" beschriftet - Topbar erhält onWindChange/onFetchLiveWind (nur bei Bearbeitungsrecht) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,8 @@ import {
|
||||
ChevronDown,
|
||||
Loader2,
|
||||
Share2,
|
||||
Wind,
|
||||
Satellite,
|
||||
} from 'lucide-react'
|
||||
import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog'
|
||||
import { ShareDialog } from '@/components/dialogs/share-dialog'
|
||||
@@ -56,6 +58,12 @@ import type { Project, DrawFeature, ProjectMode } from '@/types'
|
||||
import { formatDateTime } from '@/lib/utils'
|
||||
import { Logo } from '@/components/ui/logo'
|
||||
|
||||
// 8-Richtungs-Auswahl (Grad, meteorologisch „Wind aus")
|
||||
const WIND_OPTIONS: { label: string; deg: number }[] = [
|
||||
{ label: 'N', deg: 0 }, { label: 'NO', deg: 45 }, { label: 'O', deg: 90 }, { label: 'SO', deg: 135 },
|
||||
{ label: 'S', deg: 180 }, { label: 'SW', deg: 225 }, { label: 'W', deg: 270 }, { label: 'NW', deg: 315 },
|
||||
]
|
||||
|
||||
interface TopbarProps {
|
||||
project: Project | null
|
||||
onNewProject: (mode?: ProjectMode) => void
|
||||
@@ -82,6 +90,10 @@ interface TopbarProps {
|
||||
onTogglePresentationLock?: () => void
|
||||
/** Windrichtung wird live aktualisiert (für die Live-Anzeige oben) */
|
||||
windLive?: boolean
|
||||
/** Windrichtung setzen/entfernen (Grad, meteorologisch „Wind aus"; null = entfernen) */
|
||||
onWindChange?: (dir: number | null) => void
|
||||
/** Live-Wind (Open-Meteo) vom Standort holen */
|
||||
onFetchLiveWind?: () => Promise<void> | void
|
||||
/** true, wenn der aktuelle Nutzer den Bearbeitungs-Lock hält */
|
||||
isEditingByMe?: boolean
|
||||
/** Karte speichern & für andere freigeben (Lock lösen) */
|
||||
@@ -115,6 +127,8 @@ export function Topbar({
|
||||
presentationLocked,
|
||||
onTogglePresentationLock,
|
||||
windLive,
|
||||
onWindChange,
|
||||
onFetchLiveWind,
|
||||
isEditingByMe,
|
||||
onStopEditing,
|
||||
editingLoading,
|
||||
@@ -138,6 +152,8 @@ export function Topbar({
|
||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
|
||||
const [windLoading, setWindLoading] = useState(false)
|
||||
|
||||
// Live clock
|
||||
const [now, setNow] = useState(new Date())
|
||||
useEffect(() => {
|
||||
@@ -214,20 +230,70 @@ export function Topbar({
|
||||
|
||||
{/* Zone 3 — Uhr + Aktionen */}
|
||||
<div className="flex items-center gap-1 md:gap-1.5 shrink-0">
|
||||
{/* Wind — taktische Live-Anzeige (Pfeil zeigt Ausbreitungsrichtung, N oben) */}
|
||||
{typeof project?.windDirection === 'number' && (
|
||||
<div
|
||||
className="hidden sm:flex items-center gap-1.5 rounded-md bg-blue-600 text-white pl-1.5 pr-2 py-1 shrink-0"
|
||||
title={`Wind aus ${degToCompass(project.windDirection)} (${project.windDirection}°) — treibt nach ${degToCompass(project.windDirection + 180)}${windLive ? ' · live' : ''}`}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0" style={{ transform: `rotate(${project.windDirection + 180}deg)` }} aria-hidden>
|
||||
<path d="M12 3 L18 16 L12 12.5 L6 16 Z" fill="currentColor" />
|
||||
</svg>
|
||||
<span className="text-xs font-bold leading-none whitespace-nowrap">
|
||||
<span className="hidden lg:inline opacity-80 font-medium">Wind </span>aus {degToCompass(project.windDirection)}
|
||||
</span>
|
||||
{windLive && <span className="w-1.5 h-1.5 rounded-full bg-green-300 animate-pulse shrink-0" title="Live" />}
|
||||
</div>
|
||||
{/* Wind — klare, einzige Wind-Anzeige. Klick öffnet den Einsteller (Live vom Standort
|
||||
oder Richtung wählen). Der Pfeil zeigt die Ausbreitungsrichtung, N oben. */}
|
||||
{project && onWindChange && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
{typeof project.windDirection === 'number' ? (
|
||||
<button
|
||||
className="hidden sm:flex items-center gap-1.5 rounded-md bg-blue-600 hover:bg-blue-700 text-white pl-1.5 pr-2 py-1 shrink-0"
|
||||
title={`Wind aus ${degToCompass(project.windDirection)} (${project.windDirection}°) — treibt nach ${degToCompass(project.windDirection + 180)}. Klicken zum Ändern.`}
|
||||
>
|
||||
<Wind className="w-3.5 h-3.5 shrink-0 opacity-90" />
|
||||
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0" style={{ transform: `rotate(${project.windDirection + 180}deg)` }} aria-hidden>
|
||||
<path d="M12 3 L18 16 L12 12.5 L6 16 Z" fill="currentColor" />
|
||||
</svg>
|
||||
<span className="text-xs font-bold leading-none whitespace-nowrap">
|
||||
Wind aus {degToCompass(project.windDirection)}
|
||||
</span>
|
||||
{windLive && <span className="w-1.5 h-1.5 rounded-full bg-green-300 animate-pulse shrink-0" title="Live" />}
|
||||
</button>
|
||||
) : (
|
||||
<Button variant="outline" size="sm" className="hidden sm:flex h-9 md:h-10 px-2 shrink-0" title="Windrichtung setzen">
|
||||
<Wind className="w-4 h-4 lg:mr-1.5" />
|
||||
<span className="hidden lg:inline">Wind</span>
|
||||
</Button>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-52 p-2">
|
||||
<div className="flex items-center gap-1.5 px-1 pb-1.5 text-xs font-semibold text-muted-foreground">
|
||||
<Wind className="w-3.5 h-3.5" /> Windrichtung (Wind aus)
|
||||
</div>
|
||||
{onFetchLiveWind && (
|
||||
<button
|
||||
onClick={async () => { setWindLoading(true); try { await onFetchLiveWind() } finally { setWindLoading(false) } }}
|
||||
disabled={windLoading}
|
||||
className="w-full mb-2 flex items-center justify-center gap-1.5 rounded-md bg-blue-600 hover:bg-blue-700 text-white text-xs font-semibold py-1.5 disabled:opacity-60"
|
||||
>
|
||||
{windLoading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Satellite className="w-3.5 h-3.5" />}
|
||||
Live vom Standort
|
||||
</button>
|
||||
)}
|
||||
<div className="text-[10px] text-muted-foreground mb-1 px-0.5">oder Richtung wählen:</div>
|
||||
<div className="grid grid-cols-4 gap-1">
|
||||
{WIND_OPTIONS.map((w) => (
|
||||
<button
|
||||
key={w.deg}
|
||||
onClick={() => onWindChange(w.deg)}
|
||||
className={`h-8 rounded-md text-xs font-semibold transition-colors ${
|
||||
project.windDirection === w.deg ? 'bg-blue-600 text-white' : 'bg-muted hover:bg-accent text-foreground'
|
||||
}`}
|
||||
>
|
||||
{w.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{typeof project.windDirection === 'number' && (
|
||||
<button
|
||||
onClick={() => onWindChange(null)}
|
||||
className="mt-2 w-full text-xs text-muted-foreground hover:text-destructive py-1"
|
||||
>
|
||||
Wind entfernen
|
||||
</button>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
{/* Uhr — Ops-Rooms leben nach der Uhr */}
|
||||
|
||||
Reference in New Issue
Block a user