diff --git a/frontend/react/src/events.ts b/frontend/react/src/events.ts index a6f4f9f3..c0909f52 100644 --- a/frontend/react/src/events.ts +++ b/frontend/react/src/events.ts @@ -648,17 +648,17 @@ export class AWACSReferenceChangedEvent { } export class DrawingsInitEvent { - static on(callback: (drawingsData: any /*TODO*/) => void, singleShot = false) { + static on(callback: (drawingsData: any, navpointData: any /*TODO*/) => void, singleShot = false) { document.addEventListener( this.name, (ev: CustomEventInit) => { - callback(ev.detail); + callback(ev.detail.drawingsData, ev.detail.navpointData); }, { once: singleShot } ); } - static dispatch(drawingsData: any /*TODO*/) { + static dispatch(drawingsData: any, navpointData?: any /*TODO*/) { document.dispatchEvent(new CustomEvent(this.name, {detail: drawingsData})); if (DEBUG) console.log(`Event ${this.name} dispatched`); } diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index b9ae4680..af34f7dd 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -60,6 +60,7 @@ export interface SessionData { hotgroups?: { [key: string]: number[] }; starredSpawns?: { [key: number]: SpawnRequestTable }; drawings?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } }; + navpoints?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } }; } export interface ProfileOptions { diff --git a/frontend/react/src/map/drawings/drawingsmanager.ts b/frontend/react/src/map/drawings/drawingsmanager.ts index 7c823bfa..4de83577 100644 --- a/frontend/react/src/map/drawings/drawingsmanager.ts +++ b/frontend/react/src/map/drawings/drawingsmanager.ts @@ -103,20 +103,20 @@ export class DCSPolygon extends DCSDrawing { case "circle": // Example circle: /* - colorString: 4278190335 - fillColorString: 4278190127 - lat: 27.65469131156049 - layer: "Blue" - layerName: "Blue" - lng: 54.33075915954884 - name: "SA11-2 + SA6-3" - points: {0: {…}, x: 166867.07767244, y: -187576.93134045} - polygonMode: "circle" - primitiveType: "Polygon" - radius: 36651.296128911 - style: "dash" - thickness: 16 - visible: true*/ + colorString: 4278190335 + fillColorString: 4278190127 + lat: 27.65469131156049 + layer: "Blue" + layerName: "Blue" + lng: 54.33075915954884 + name: "SA11-2 + SA6-3" + points: {0: {…}, x: 166867.07767244, y: -187576.93134045} + polygonMode: "circle" + primitiveType: "Polygon" + radius: 36651.296128911 + style: "dash" + thickness: 16 + visible: true*/ this.#polygon = new Circle([drawingData.lat, drawingData.lng], { radius: Math.round(drawingData.radius), @@ -131,7 +131,7 @@ export class DCSPolygon extends DCSDrawing { case "arrow": let weight = this.getWeight(); - + if (!weight || weight < 1) { weight = 1; } @@ -160,44 +160,44 @@ export class DCSPolygon extends DCSDrawing { case "rect": /** Rectangle Example: - * { - "angle": 68.579040048342, - "colorString": 255, - "fillColorString": 4294901888, - "height": 11100, - "lat": 27.5547706075188, - "layer": "Author", - "layerName": "Author", - "lng": 57.22438242806247, - "mapX": 152970.68262179, - "mapY": 97907.892121675, - "name": "FLOT BUFFER EAST", - "points": { - "1": { - "lat": 27.417344649833286, - "lng": 57.34472624501578 - }, - "2": { - "lat": 27.38096510320196, - "lng": 57.24010993680159 - }, - "3": { - "lat": 27.69209116201148, - "lng": 57.1037392116416 - }, - "4": { - "lat": 27.728570135811577, - "lng": 57.20860735951096 - } - }, - "polygonMode": "rect", - "primitiveType": "Polygon", - "style": "dot", - "thickness": 16, - "visible": true, - "width": 37000 - } - */ + * { + "angle": 68.579040048342, + "colorString": 255, + "fillColorString": 4294901888, + "height": 11100, + "lat": 27.5547706075188, + "layer": "Author", + "layerName": "Author", + "lng": 57.22438242806247, + "mapX": 152970.68262179, + "mapY": 97907.892121675, + "name": "FLOT BUFFER EAST", + "points": { + "1": { + "lat": 27.417344649833286, + "lng": 57.34472624501578 + }, + "2": { + "lat": 27.38096510320196, + "lng": 57.24010993680159 + }, + "3": { + "lat": 27.69209116201148, + "lng": 57.1037392116416 + }, + "4": { + "lat": 27.728570135811577, + "lng": 57.20860735951096 + } + }, + "polygonMode": "rect", + "primitiveType": "Polygon", + "style": "dot", + "thickness": 16, + "visible": true, + "width": 37000 + } + */ const bounds = [ [drawingData.points["1"].lat, drawingData.points["1"].lng], [drawingData.points["2"].lat, drawingData.points["2"].lng], @@ -217,42 +217,42 @@ export class DCSPolygon extends DCSDrawing { case "oval": /** - * Example: - * { - "angle": 270, - "colorString": 255, - "fillColorString": 4278190080, - "lat": 25.032272009407105, - "layer": "Blue", - "layerName": "Blue", - "lng": 55.36597899137401, - "mapX": -125416.92956726, - "mapY": -89103.936896595, - "name": "AM OTP", - "points": { - "1": { - "lat": 25.03332743167039, - "lng": 55.34689257576858 - }, - "2": { - "lat": 25.034529398092356, - "lng": 55.348849087588164 - }, - ... - "24": { - "lat": 25.032053589358366, - "lng": 55.34623694782629 - } - }, - "polygonMode": "oval", - "primitiveType": "Polygon", - "r1": 1992.4714734497, - "r2": 541.99904672895, - "style": "dot", - "thickness": 10, - "visible": true - } - */ + * Example: + * { + "angle": 270, + "colorString": 255, + "fillColorString": 4278190080, + "lat": 25.032272009407105, + "layer": "Blue", + "layerName": "Blue", + "lng": 55.36597899137401, + "mapX": -125416.92956726, + "mapY": -89103.936896595, + "name": "AM OTP", + "points": { + "1": { + "lat": 25.03332743167039, + "lng": 55.34689257576858 + }, + "2": { + "lat": 25.034529398092356, + "lng": 55.348849087588164 + }, + ... + "24": { + "lat": 25.032053589358366, + "lng": 55.34623694782629 + } + }, + "polygonMode": "oval", + "primitiveType": "Polygon", + "r1": 1992.4714734497, + "r2": 541.99904672895, + "style": "dot", + "thickness": 10, + "visible": true + } + */ const points: [number, number][] = Object.values(drawingData.points as Record).map((p) => [p.lat, p.lng]); this.#polygon = new Polygon(points, { @@ -276,8 +276,8 @@ export class DCSPolygon extends DCSDrawing { dashArray: dashArray, }); break; - - default: + + default: break; } @@ -374,21 +374,21 @@ export class DCSTextBox extends DCSDrawing { super(drawingData, parent); /* Example textbox "ABC625": - angle: 0 - borderThickness: 1 - colorString: 4294967295 - fillColorString: 8421504 - font: "DejaVuLGCSansCondensed.ttf" - fontSize: 10 - layer: "Common" - layerName: "Common" - mapX: -261708.68309463 - mapY: -217863.03743212 - name: "ABC625" - primitiveType: "TextBox" - text: "ABC625" - visible: true - */ + angle: 0 + borderThickness: 1 + colorString: 4294967295 + fillColorString: 8421504 + font: "DejaVuLGCSansCondensed.ttf" + fontSize: 10 + layer: "Common" + layerName: "Common" + mapX: -261708.68309463 + mapY: -217863.03743212 + name: "ABC625" + primitiveType: "TextBox" + text: "ABC625" + visible: true + */ const customIcon = new DivIcon({ html: `
{ this.#drawingsContainer.setVisibility(mapOptions.showMissionDrawings); @@ -718,18 +718,25 @@ export class DrawingsManager { SessionDataLoadedEvent.on((sessionData) => { this.#sessionDataDrawings = sessionData.drawings ?? {}; - if (this.#initialized) if (this.#sessionDataDrawings["Mission drawings"]) this.#drawingsContainer.fromJSON(this.#sessionDataDrawings["Mission drawings"]); + this.#sessionDataNavpoints = sessionData.navpoints ?? {}; + if (this.#initialized) { + if (this.#sessionDataDrawings["Mission drawings"]) this.#drawingsContainer.fromJSON(this.#sessionDataDrawings["Mission drawings"]); + if (this.#sessionDataNavpoints["Navpoints"]) this.#navpointsContainer.fromJSON(this.#sessionDataNavpoints["Navpoints"]); + } this.#drawingsContainer.setVisibility(getApp().getMap().getOptions().showMissionDrawings); }); } initDrawings(data: { drawings: Record> }): boolean { if (data && data.drawings) { + if (data.drawings.navpoints) { + this.#navpointsContainer.initFromData(data.drawings.navpoints); + delete data.drawings.navpoints; + } this.#drawingsContainer.initFromData(data.drawings); - if (data.drawings.navpoints) this.#drawingsContainer.initNavpoints(data.drawings.navpoints); if (this.#sessionDataDrawings["Mission drawings"]) this.#drawingsContainer.fromJSON(this.#sessionDataDrawings["Mission drawings"]); this.#drawingsContainer.setVisibility(getApp().getMap().getOptions().showMissionDrawings); - DrawingsInitEvent.dispatch(this.#drawingsContainer); + DrawingsInitEvent.dispatch(this.#drawingsContainer, this.#navpointsContainer); this.#initialized = true; return true; } else { @@ -742,6 +749,10 @@ export class DrawingsManager { return this.#drawingsContainer; } + getNavpointsContainer() { + return this.#navpointsContainer; + } + requestUpdateEventDispatch() { if (this.#updateEventRequested) return; this.#updateEventRequested = true; diff --git a/frontend/react/src/sessiondata.ts b/frontend/react/src/sessiondata.ts index 33301fb4..4eadc9e0 100644 --- a/frontend/react/src/sessiondata.ts +++ b/frontend/react/src/sessiondata.ts @@ -134,8 +134,10 @@ export class SessionDataManager { }); DrawingsUpdatedEvent.on(() => { - let container = getApp().getDrawingsManager().getDrawingsContainer(); - this.#sessionData.drawings = {"Mission drawings": container.toJSON()}; + let mainDrawingsContainer = getApp().getDrawingsManager().getDrawingsContainer(); + let navpointsContainer = getApp().getDrawingsManager().getNavpointsContainer(); + this.#sessionData.drawings = {"Mission drawings": mainDrawingsContainer.toJSON()}; + this.#sessionData.navpoints = {"Navpoints": navpointsContainer.toJSON()}; this.#saveSessionData(); }); }, 200); diff --git a/frontend/react/src/ui/panels/drawingmenu.tsx b/frontend/react/src/ui/panels/drawingmenu.tsx index f106b63a..c7c6dfa6 100644 --- a/frontend/react/src/ui/panels/drawingmenu.tsx +++ b/frontend/react/src/ui/panels/drawingmenu.tsx @@ -3,7 +3,7 @@ import { Menu } from "./components/menu"; import { FaArrowDown, FaArrowUp, FaChevronRight, FaTrash } from "react-icons/fa"; import { getApp } from "../../olympusapp"; import { OlStateButton } from "../components/olstatebutton"; -import { faDrawPolygon, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; +import { faDrawPolygon, faEye, faEyeSlash, faMapLocation } from "@fortawesome/free-solid-svg-icons"; import { faCircle } from "@fortawesome/free-regular-svg-icons"; import { CoalitionPolygon } from "../../map/coalitionarea/coalitionpolygon"; import { OlCoalitionToggle } from "../components/olcoalitiontoggle"; @@ -14,7 +14,7 @@ import { OlRangeSlider } from "../components/olrangeslider"; import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle"; import { DrawSubState, ERAS_ORDER, IADSTypes, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants"; import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent, DrawingsInitEvent, DrawingsUpdatedEvent } from "../../events"; -import { FaXmark } from "react-icons/fa6"; +import { FaCopy, FaPencil, FaRegCompass, FaXmark } from "react-icons/fa6"; import { deepCopyTable } from "../../other/utils"; import { DCSDrawing, DCSDrawingsContainer, DCSEmptyLayer } from "../../map/drawings/drawingsmanager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -35,18 +35,22 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { const [openContainers, setOpenContainers] = useState([] as DCSDrawingsContainer[]); const [mainDrawingsContainer, setDrawingsContainer] = useState({ container: null } as { container: null | DCSDrawingsContainer }); + const [navpointsContainer, setNavpointsContainer] = useState({ container: null } as { container: null | DCSDrawingsContainer }); const [searchString, setSearchString] = useState(""); + const [navpointSearchString, setNavpointSearchString] = useState(""); useEffect(() => { AppStateChangedEvent.on((state, subState) => { setAppState(state); setAppSubState(subState); }); - DrawingsInitEvent.on((drawingContainer) => { + DrawingsInitEvent.on((drawingContainer, navpointsContainer) => { setDrawingsContainer({ container: drawingContainer }); + setNavpointsContainer({ container: navpointsContainer }); }); DrawingsUpdatedEvent.on(() => { setDrawingsContainer({ container: getApp().getDrawingsManager().getDrawingsContainer() }); + setNavpointsContainer({ container: getApp().getDrawingsManager().getNavpointsContainer() }); }); }, []); @@ -67,8 +71,14 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { return drawing.getVisibility() ? `text-gray-200` : `text-gray-600`; } - function renderDrawingsContainerControls(container: DCSDrawingsContainer) { - if (container.hasSearchString(searchString)) { + function renderDrawingsContainerControls(container: DCSDrawingsContainer, containerSearchString: string) { + if (container.hasSearchString(containerSearchString)) { + /* The following snippet automatically open containers that contains searched drawings */ + if (!openContainers.includes(container) && containerSearchString != "") { + openContainers.push(container); + setOpenContainers([...openContainers]); + } + return (
@@ -126,10 +136,12 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { >
- {openContainers.includes(container) && container.getSubContainers().map((container) => renderDrawingsContainerControls(container))} + {openContainers.includes(container) && + container.getSubContainers().map((container) => renderDrawingsContainerControls(container, containerSearchString))} {openContainers.includes(container) && container.getDrawings().map((drawing, index) => { if (drawing instanceof DCSEmptyLayer) return <>; + if (!drawing.getName().toLowerCase().includes(containerSearchString.toLowerCase())) return <>; return (
void }) { ${getDrawingLabelColor(drawing)} text-ellipsis text-nowrap `}>{drawing.getName()}
+ { + const latLng = drawing.getLayer()["getLatLng"] && drawing.getLayer()["getLatLng"](); + const bounds = drawing.getLayer()["getBounds"] && drawing.getLayer()["getBounds"](); + latLng && getApp().getMap().setView(latLng, 14); + bounds && getApp().getMap().fitBounds(bounds); + }} + />
); })} @@ -192,12 +217,9 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { You can change the name and the coalition of the area. You can also generate an IADS area by selecting the types, eras, and ranges of units you want to include in the area. You can also set the density and distribution of the IADS. If you check the 'Force coalition appropriate units' box, the IADS will only include units that are appropriate for the coalition of the area (e.g. Hawk SAMs for {""} - blue and SA-6 SAMs for{" "} - - red - + blue and SA-6 SAMs for red ).
@@ -212,9 +234,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
You can search for a specific drawing by typing in the search bar. The search is case-insensitive and will match any part of the drawing name.
-
- Any change you make is persistent and will be saved for the next time you reload Olympus, as long as the DCS mission was not restarted. -
+
Any change you make is persistent and will be saved for the next time you reload Olympus, as long as the DCS mission was not restarted.
); }} @@ -292,9 +312,61 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
-
Mission drawings
- setSearchString(search)} text={searchString || ""}> -
{mainDrawingsContainer.container && renderDrawingsContainerControls(mainDrawingsContainer.container)}
+
+ + + + Mission drawings +
+ { + setSearchString(search); + if (search === "") { + setOpenContainers([]); + } + }} + text={searchString || ""} + > +
+ {mainDrawingsContainer.container && renderDrawingsContainerControls(mainDrawingsContainer.container, searchString)} +
+
+ +
+
+ + + + Navpoints +
+ { + setNavpointSearchString(search); + if (search === "") { + setOpenContainers([]); + } + }} + text={navpointSearchString || ""} + > +
+ {navpointsContainer.container && renderDrawingsContainerControls(navpointsContainer.container, navpointSearchString)} +
@@ -362,9 +434,11 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { bg-olympus-600 p-5 `} > -
Automatic IADS generation
+
+ Automatic IADS generation +
{types.map((type, idx) => { if (!(type in typesSelection)) {