mirror of
https://github.com/azaion/ui.git
synced 2026-04-23 04:26:34 +00:00
feat(flights): integrate mission-planner into Flights page
- 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
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user