diff --git a/client/src/api/waypoint.ts b/client/src/api/waypoint.ts
index 9908aef9..150d1c30 100644
--- a/client/src/api/waypoint.ts
+++ b/client/src/api/waypoint.ts
@@ -8,4 +8,5 @@ export interface Waypoint {
is_movable: boolean;
should_mark: boolean;
include_in_path: boolean;
+ timing: string;
}
diff --git a/client/src/components/flightplan/FlightPlan.tsx b/client/src/components/flightplan/FlightPlan.tsx
index 6075437f..ff20eb66 100644
--- a/client/src/components/flightplan/FlightPlan.tsx
+++ b/client/src/components/flightplan/FlightPlan.tsx
@@ -1,5 +1,6 @@
import { Flight } from "../../api/flight";
import WaypointMarker from "../waypointmarker";
+import { ReactElement } from "react";
import { Polyline } from "react-leaflet";
const BLUE_PATH = "#0084ff";
@@ -39,25 +40,23 @@ function FlightPlanPath(props: FlightPlanProps) {
}
const WaypointMarkers = (props: FlightPlanProps) => {
- if (!props.selected || props.flight.waypoints == null) {
+ if (props.selected && props.flight.waypoints == null) {
return <>>;
}
- return (
- <>
- {props.flight.waypoints
- .filter((p) => p.should_mark)
- .map((p, idx) => {
- return (
-
- );
- })}
- >
- );
+ var markers: ReactElement[] = [];
+ props.flight.waypoints?.forEach((p, idx) => {
+ markers.push(
+
+ );
+ });
+
+ return <>{markers}>;
};
export default function FlightPlan(props: FlightPlanProps) {
diff --git a/client/src/components/waypointmarker/WaypointMarker.tsx b/client/src/components/waypointmarker/WaypointMarker.tsx
index 28b30008..c53150c5 100644
--- a/client/src/components/waypointmarker/WaypointMarker.tsx
+++ b/client/src/components/waypointmarker/WaypointMarker.tsx
@@ -1,9 +1,11 @@
+import backend from "../../api/backend";
+import { Flight } from "../../api/flight";
import { Waypoint } from "../../api/waypoint";
import { Icon } from "leaflet";
import { Marker as LMarker } from "leaflet";
import icon from "leaflet/dist/images/marker-icon.png";
import iconShadow from "leaflet/dist/images/marker-shadow.png";
-import { MutableRefObject, useCallback, useRef } from "react";
+import { MutableRefObject, useCallback, useEffect, useRef } from "react";
import { Marker, Tooltip, useMap, useMapEvent } from "react-leaflet";
const WAYPOINT_ICON = new Icon({
@@ -15,6 +17,7 @@ const WAYPOINT_ICON = new Icon({
interface WaypointMarkerProps {
number: number;
waypoint: Waypoint;
+ flight: Flight;
}
const WaypointMarker = (props: WaypointMarkerProps) => {
@@ -50,24 +53,42 @@ const WaypointMarker = (props: WaypointMarkerProps) => {
}, [map]);
useMapEvent("zoomend", rebindTooltip);
+ useEffect(() => {
+ const waypoint = props.waypoint;
+ marker.current?.setTooltipContent(
+ `${props.number} ${waypoint.name}
` +
+ `${waypoint.altitude_ft} ft ${waypoint.altitude_reference}
` +
+ waypoint.timing
+ );
+ });
+
const waypoint = props.waypoint;
return (
{
+ const m: LMarker = e.target;
+ m.setTooltipContent("Waiting to recompute TOT...");
+ },
+ dragend: (e) => {
+ const m: LMarker = e.target;
+ const destination = m.getLatLng();
+ backend.post(
+ `/waypoints/${props.flight.id}/${props.number}/position`,
+ destination
+ );
+ },
+ }}
ref={(ref) => {
if (ref != null) {
marker.current = ref;
}
}}
>
-
- {`${props.number} ${waypoint.name}`}
-
- {`${waypoint.altitude_ft} ft ${waypoint.altitude_reference}`}
-
- TODO: Timing info
-
+
);
};
diff --git a/game/server/waypoints/models.py b/game/server/waypoints/models.py
index d732c248..00b140d3 100644
--- a/game/server/waypoints/models.py
+++ b/game/server/waypoints/models.py
@@ -1,12 +1,29 @@
from __future__ import annotations
+from datetime import timedelta
+
from pydantic import BaseModel
-from game.ato import FlightWaypoint
+from game.ato import Flight, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.server.leaflet import LeafletPoint
+def timing_info(flight: Flight, waypoint_idx: int) -> str:
+ if waypoint_idx == 0:
+ return f"Depart T+{flight.flight_plan.takeoff_time()}"
+
+ waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
+ prefix = "TOT"
+ time = flight.flight_plan.tot_for_waypoint(waypoint)
+ if time is None:
+ prefix = "Depart"
+ time = flight.flight_plan.depart_time_for_waypoint(waypoint)
+ if time is None:
+ return ""
+ return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
+
+
class FlightWaypointJs(BaseModel):
name: str
position: LeafletPoint
@@ -15,9 +32,12 @@ class FlightWaypointJs(BaseModel):
is_movable: bool
should_mark: bool
include_in_path: bool
+ timing: str
@staticmethod
- def for_waypoint(waypoint: FlightWaypoint) -> FlightWaypointJs:
+ def for_waypoint(
+ waypoint: FlightWaypoint, flight: Flight, waypoint_idx: int
+ ) -> FlightWaypointJs:
# Target *points* are the exact location of a unit, whereas the target area is
# only the center of the objective. Allow moving the latter since its exact
# location isn't very important.
@@ -67,4 +87,5 @@ class FlightWaypointJs(BaseModel):
is_movable=is_movable,
should_mark=should_mark,
include_in_path=include_in_path,
+ timing=timing_info(flight, waypoint_idx),
)
diff --git a/game/server/waypoints/routes.py b/game/server/waypoints/routes.py
index ab1ea1af..0f4b6e86 100644
--- a/game/server/waypoints/routes.py
+++ b/game/server/waypoints/routes.py
@@ -1,4 +1,3 @@
-from datetime import timedelta
from uuid import UUID
from dcs.mapping import LatLng, Point
@@ -11,6 +10,7 @@ from game.ato.flightwaypointtype import FlightWaypointType
from game.server import GameContext
from game.server.leaflet import LeafletPoint
from game.server.waypoints.models import FlightWaypointJs
+from game.sim import GameUpdateEvents
from game.utils import meters
router: APIRouter = APIRouter(prefix="/waypoints")
@@ -24,10 +24,13 @@ def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
flight.departure.position,
meters(0),
"RADIO",
- )
+ ),
+ flight,
+ 0,
)
return [departure] + [
- FlightWaypointJs.for_waypoint(w) for w in flight.flight_plan.waypoints
+ FlightWaypointJs.for_waypoint(w, flight, i)
+ for i, w in enumerate(flight.flight_plan.waypoints, 1)
]
@@ -45,6 +48,8 @@ def set_position(
position: LeafletPoint,
game: Game = Depends(GameContext.get),
) -> None:
+ from game.server import EventStream
+
flight = game.db.flights.get(flight_id)
if waypoint_idx == 0:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@@ -64,22 +69,4 @@ def set_position(
detail=f"Could not find PackageModel owning {flight}",
)
package_model.update_tot()
-
-
-@router.get("/{flight_id}/{waypoint_idx}/timing")
-def waypoint_timing(
- flight_id: UUID, waypoint_idx: int, game: Game = Depends(GameContext.get)
-) -> str | None:
- flight = game.db.flights.get(flight_id)
- if waypoint_idx == 0:
- return f"Depart T+{flight.flight_plan.takeoff_time()}"
-
- waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
- prefix = "TOT"
- time = flight.flight_plan.tot_for_waypoint(waypoint)
- if time is None:
- prefix = "Depart"
- time = flight.flight_plan.depart_time_for_waypoint(waypoint)
- if time is None:
- return ""
- return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
+ EventStream.put_nowait(GameUpdateEvents().update_flight(flight))
diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js
index 7abc0fec..9d5aa97d 100644
--- a/resources/ui/map/map.js
+++ b/resources/ui/map/map.js
@@ -301,7 +301,7 @@ function handleStreamedEvents(events) {
}
for (const flightId of events.updated_flights) {
- Flight.withId(flightId).draw();
+ Flight.withId(flightId).update();
}
for (const flightId of events.deleted_flights) {
@@ -775,20 +775,13 @@ class Waypoint {
return this.waypoint.should_mark;
}
- async timing(dragging) {
- if (dragging) {
- return "Waiting to recompute TOT...";
- }
- return await getJson(`/waypoints/${this.flight.id}/${this.number}/timing`);
- }
-
- async description(dragging) {
+ description() {
const alt = this.waypoint.altitude_ft;
const altRef = this.waypoint.altitude_reference;
return (
`${this.number} ${this.waypoint.name}
` +
`${alt} ft ${altRef}
` +
- `${await this.timing(dragging)}`
+ `${this.waypoint.timing}`
);
}
@@ -796,19 +789,13 @@ class Waypoint {
this.marker.setLatLng(this.position());
}
- updateDescription(dragging) {
- this.description(dragging).then((description) => {
- this.marker.setTooltipContent(description);
- });
- }
-
makeMarker() {
const zoom = map.getZoom();
const marker = L.marker(this.position(), {
draggable: this.waypoint.is_movable,
})
.on("dragstart", (e) => {
- this.updateDescription(true);
+ this.marker.setTooltipContent("Waiting to recompute TOT...");
})
.on("drag", (e) => {
const marker = e.target;
@@ -824,7 +811,6 @@ class Waypoint {
)
.then(() => {
this.waypoint.position = destination;
- this.updateDescription(false);
this.flight.drawCommitBoundary();
})
.catch((err) => {
@@ -837,11 +823,9 @@ class Waypoint {
});
if (this.flight.selected) {
- this.description(false).then((description) =>
- marker.bindTooltip(description, {
- permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
- })
- );
+ marker.bindTooltip(this.description(), {
+ permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
+ });
}
return marker;
@@ -961,6 +945,13 @@ class Flight {
}
}
+ update() {
+ getJson(`/flights/${this.id}?with_waypoints=true`).then((flight) => {
+ this.flight = flight;
+ this.draw();
+ });
+ }
+
draw() {
this.drawAircraftLocation();
this.drawFlightPlan();