mirror of
https://github.com/azaion/ui.git
synced 2026-04-22 20:06:34 +00:00
274800e508
- Port mission-planner flight planning to main app (Tailwind, react-leaflet v5, react-i18next) - Add FlightMap with click-to-add waypoints, draggable markers, polyline with arrows - Add FlightParamsPanel with action modes, waypoint list (drag-reorder), altitude chart, wind, JSON import/export - Add FlightListSidebar with create/delete and telemetry date - Add collapsible left panel with quick action mode shortcuts - Add work area / no-go zone drawing via manual mouse events (L.rectangle) - Add AltitudeDialog and JsonEditorDialog (Tailwind modals) - Add battery/time/distance calculations per waypoint segment - Add satellite/classic map toggle and mini-map on point drag - Add Camera FOV and Communication Addr fields - Add current position display under location search - Merge mission-planner translations under flights.planner.* - Gitignore .superpowers session data
88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
import { useRef } from 'react'
|
|
import { Marker, Popup } from 'react-leaflet'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { pointIconGreen, pointIconBlue, pointIconRed } from './mapIcons'
|
|
import { PURPOSES } from './types'
|
|
import type { FlightPoint, MovingPointInfo } from './types'
|
|
import type L from 'leaflet'
|
|
|
|
interface Props {
|
|
point: FlightPoint
|
|
points: FlightPoint[]
|
|
index: number
|
|
mapElement: HTMLElement | null
|
|
onDrag: (index: number, position: { lat: number; lng: number }) => void
|
|
onDragEnd: (index: number, position: { lat: number; lng: number }) => void
|
|
onAltitudeChange: (index: number, altitude: number) => void
|
|
onMetaChange: (index: number, meta: string[]) => void
|
|
onRemove: (id: string) => void
|
|
onMoving: (info: MovingPointInfo | null) => void
|
|
}
|
|
|
|
export default function MapPoint({
|
|
point, points, index, mapElement,
|
|
onDrag, onDragEnd, onAltitudeChange, onMetaChange, onRemove, onMoving,
|
|
}: Props) {
|
|
const { t } = useTranslation()
|
|
const markerRef = useRef<L.Marker>(null)
|
|
|
|
const icon = index === 0 ? pointIconGreen : index === points.length - 1 ? pointIconRed : pointIconBlue
|
|
|
|
const handleMove = (e: L.LeafletEvent) => {
|
|
const marker = markerRef.current
|
|
if (!marker || !mapElement) return
|
|
const markerEl = (marker as unknown as { _icon: HTMLElement })._icon
|
|
if (!markerEl) return
|
|
const mapRect = mapElement.getBoundingClientRect()
|
|
const mRect = markerEl.getBoundingClientRect()
|
|
const dx = mRect.left - mapRect.left + mRect.width > mapRect.width / 2 ? -150 : 200
|
|
const dy = mRect.top + mRect.height > mapRect.height / 2 ? -150 : 150
|
|
onMoving({ x: mRect.left - mapRect.left + dx, y: mRect.top - mapRect.top + dy, latlng: (e.target as L.Marker).getLatLng() })
|
|
}
|
|
|
|
const toggleMeta = (value: string) => {
|
|
const newMeta = point.meta.includes(value) ? point.meta.filter(m => m !== value) : [...point.meta, value]
|
|
onMetaChange(index, newMeta)
|
|
}
|
|
|
|
return (
|
|
<Marker
|
|
position={point.position}
|
|
icon={icon}
|
|
draggable
|
|
ref={markerRef}
|
|
eventHandlers={{
|
|
drag: (e) => onDrag(index, (e.target as L.Marker).getLatLng()),
|
|
dragend: (e) => { onDragEnd(index, (e.target as L.Marker).getLatLng()); onMoving(null) },
|
|
move: handleMove,
|
|
}}
|
|
>
|
|
<Popup>
|
|
<div className="text-xs space-y-1.5 min-w-[140px]">
|
|
<div className="font-semibold">{t('flights.planner.point')} {index + 1}</div>
|
|
<div>
|
|
<label className="text-az-muted text-[10px]">{t('flights.planner.altitude')}</label>
|
|
<input type="range" min={0} max={3000} value={point.altitude}
|
|
onChange={e => onAltitudeChange(index, Number(e.target.value))}
|
|
className="w-full accent-az-orange" />
|
|
<span className="text-[10px] text-az-muted">{point.altitude}m</span>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
{PURPOSES.map(p => (
|
|
<label key={p.value} className="flex items-center gap-1 text-[10px] cursor-pointer">
|
|
<input type="checkbox" checked={point.meta.includes(p.value)}
|
|
onChange={() => toggleMeta(p.value)} className="accent-az-orange" />
|
|
{t(`flights.planner.${p.label}`)}
|
|
</label>
|
|
))}
|
|
</div>
|
|
<button onClick={() => onRemove(point.id)}
|
|
className="text-az-red text-[10px] hover:underline">
|
|
{t('flights.planner.removePoint')}
|
|
</button>
|
|
</div>
|
|
</Popup>
|
|
</Marker>
|
|
)
|
|
}
|