mirror of
https://github.com/azaion/ui.git
synced 2026-04-25 10:26: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
151 lines
6.3 KiB
TypeScript
151 lines
6.3 KiB
TypeScript
import { useTranslation } from 'react-i18next'
|
|
import WaypointList from './WaypointList'
|
|
import AltitudeChart from './AltitudeChart'
|
|
import WindEffect from './WindEffect'
|
|
import type { FlightPoint, CalculatedPointInfo, ActionMode, WindParams } from './types'
|
|
import type { Aircraft } from '../../types'
|
|
|
|
interface Props {
|
|
points: FlightPoint[]
|
|
calculatedPointInfo: CalculatedPointInfo[]
|
|
aircrafts: Aircraft[]
|
|
initialAltitude: number
|
|
actionMode: ActionMode
|
|
wind: WindParams
|
|
locationInput: string
|
|
currentPosition: { lat: number; lng: number }
|
|
totalDistance: string
|
|
totalTime: string
|
|
batteryStatus: { label: string; color: string }
|
|
onInitialAltitudeChange: (v: number) => void
|
|
onActionModeChange: (mode: ActionMode) => void
|
|
onWindChange: (w: WindParams) => void
|
|
onLocationInputChange: (v: string) => void
|
|
onLocationSearch: () => void
|
|
onReorderPoints: (points: FlightPoint[]) => void
|
|
onEditPoint: (point: FlightPoint) => void
|
|
onRemovePoint: (id: string) => void
|
|
onSave: () => void
|
|
onUpload: () => void
|
|
onEditJson: () => void
|
|
onExport: () => void
|
|
}
|
|
|
|
export default function FlightParamsPanel({
|
|
points, calculatedPointInfo, aircrafts, initialAltitude, actionMode, wind,
|
|
locationInput, currentPosition, totalDistance, totalTime, batteryStatus,
|
|
onInitialAltitudeChange, onActionModeChange, onWindChange,
|
|
onLocationInputChange, onLocationSearch, onReorderPoints, onEditPoint, onRemovePoint,
|
|
onSave, onUpload, onEditJson, onExport,
|
|
}: Props) {
|
|
const { t } = useTranslation()
|
|
|
|
const modeBtn = (mode: ActionMode, label: string, color: 'orange' | 'green' | 'red') => {
|
|
const active = actionMode === mode
|
|
const colorMap = {
|
|
orange: { border: 'border-az-orange', text: 'text-az-orange', bg: 'bg-az-orange/20', hover: 'hover:bg-az-orange/10' },
|
|
green: { border: 'border-az-green', text: 'text-az-green', bg: 'bg-az-green/20', hover: 'hover:bg-az-green/10' },
|
|
red: { border: 'border-az-red', text: 'text-az-red', bg: 'bg-az-red/20', hover: 'hover:bg-az-red/10' },
|
|
}[color]
|
|
return (
|
|
<button
|
|
onClick={() => onActionModeChange(mode)}
|
|
className={`flex-1 px-2.5 py-1 rounded border text-[11px] ${colorMap.border} ${colorMap.text} ${active ? colorMap.bg : colorMap.hover}`}
|
|
>{label}</button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="p-2 space-y-2 text-xs overflow-y-auto flex-1">
|
|
<div className="flex gap-1">
|
|
{modeBtn('points', t('flights.planner.addPoints'), 'orange')}
|
|
{modeBtn('workArea', t('flights.planner.workArea'), 'green')}
|
|
{modeBtn('prohibitedArea', t('flights.planner.prohibitedArea'), 'red')}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.location')}</label>
|
|
<input
|
|
value={locationInput}
|
|
onChange={e => onLocationInputChange(e.target.value)}
|
|
onKeyDown={e => e.key === 'Enter' && onLocationSearch()}
|
|
placeholder="47.242, 35.024"
|
|
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
|
/>
|
|
<div className="text-az-muted text-[9px] mt-0.5">
|
|
{t('flights.planner.currentLocation')}: {currentPosition.lat.toFixed(6)}, {currentPosition.lng.toFixed(6)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.aircraft')}</label>
|
|
<select className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text">
|
|
{aircrafts.map(a => <option key={a.id} value={a.id}>{a.model}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.initialAltitude')}</label>
|
|
<input type="number" value={initialAltitude}
|
|
onChange={e => onInitialAltitudeChange(Number(e.target.value))}
|
|
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.cameraFov')}</label>
|
|
<input type="text" placeholder={t('flights.planner.cameraFovPlaceholder')}
|
|
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-0.5 text-[9px]">{t('flights.planner.commAddr')}</label>
|
|
<input type="text" placeholder={t('flights.planner.commAddrPlaceholder')}
|
|
className="w-full bg-az-bg border border-az-border rounded px-2 py-1 text-az-text outline-none focus:border-az-orange"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-az-muted block mb-1 text-[9px]">{t('flights.waypoints')}</label>
|
|
<WaypointList
|
|
points={points}
|
|
calculatedPointInfo={calculatedPointInfo}
|
|
onReorder={onReorderPoints}
|
|
onEdit={onEditPoint}
|
|
onRemove={onRemovePoint}
|
|
/>
|
|
</div>
|
|
|
|
{points.length > 1 && (
|
|
<div className="bg-az-header rounded px-2 py-1 flex gap-2 text-[10px]">
|
|
<span>{totalDistance}</span>
|
|
<span>{totalTime}</span>
|
|
<span style={{ color: batteryStatus.color }}>{batteryStatus.label}</span>
|
|
</div>
|
|
)}
|
|
|
|
<AltitudeChart points={points} />
|
|
|
|
<WindEffect wind={wind} onChange={onWindChange} />
|
|
|
|
<div className="flex gap-1">
|
|
<button onClick={onSave} className="flex-1 px-2.5 py-1 rounded border border-az-green text-az-green text-[11px] hover:bg-az-green/10">
|
|
{t('flights.planner.save')}
|
|
</button>
|
|
<button onClick={onUpload} className="flex-1 px-2.5 py-1 rounded border border-az-blue text-az-blue text-[11px] hover:bg-az-blue/10">
|
|
{t('flights.planner.upload')}
|
|
</button>
|
|
</div>
|
|
<div className="flex gap-1">
|
|
<button onClick={onEditJson} className="flex-1 px-2.5 py-1 rounded border border-az-muted text-az-text text-[11px] hover:border-az-text hover:text-white">
|
|
{t('flights.planner.editAsJson')}
|
|
</button>
|
|
<button onClick={onExport} className="flex-1 px-2.5 py-1 rounded border border-az-muted text-az-text text-[11px] hover:border-az-text hover:text-white">
|
|
{t('flights.planner.exportMapData')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|