Merge pull request #1072 from Pax1601/1054-drawings-control-panel-separate-global-mission-drawings-control-from-navpoints-control

1054 drawings control panel separate global mission drawings control from navpoints control
This commit is contained in:
Pax1601 2025-03-26 16:48:39 +01:00 committed by GitHub
commit 9cbfa2a8aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 231 additions and 143 deletions

View File

@ -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`);
}

View File

@ -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 {

View File

@ -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<string, { lat: number; lng: number }>).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: `
<div style="
@ -527,9 +527,6 @@ export class DCSDrawingsContainer {
initFromData(drawingsData) {
let hasContainers = false;
Object.keys(drawingsData).forEach((layerName: string) => {
if (layerName === 'navpoints') {
return;
}
if (drawingsData[layerName]["name"] === undefined && drawingsData[layerName]["callsignStr"] === undefined) {
const newContainer = new DCSDrawingsContainer(layerName, this);
this.addSubContainer(newContainer);
@ -585,11 +582,11 @@ export class DCSDrawingsContainer {
if (othersContainer.getDrawings().length === 0) this.removeSubContainer(othersContainer); // Remove empty container
}
initNavpoints(drawingsData) {
const newContainer = new DCSDrawingsContainer('Navpoints', this);
this.addSubContainer(newContainer);
newContainer.initFromData(drawingsData);
}
// initNavpoints(drawingsData) {
// const newContainer = new DCSDrawingsContainer('Navpoints', this);
// this.addSubContainer(newContainer);
// newContainer.initFromData(drawingsData);
// }
getLayerGroup() {
return this.#layerGroup;
@ -703,14 +700,17 @@ export class DCSDrawingsContainer {
export class DrawingsManager {
#drawingsContainer: DCSDrawingsContainer;
#navpointsContainer: DCSDrawingsContainer;
#updateEventRequested: boolean = false;
#sessionDataDrawings = {};
#sessionDataNavpoints = {};
#initialized: boolean = false;
constructor() {
const drawingsLayerGroup = new LayerGroup();
drawingsLayerGroup.addTo(getApp().getMap());
this.#drawingsContainer = new DCSDrawingsContainer("Mission drawings", drawingsLayerGroup);
this.#navpointsContainer = new DCSDrawingsContainer("Navpoints", drawingsLayerGroup);
MapOptionsChangedEvent.on((mapOptions: MapOptions) => {
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<string, Record<string, any>> }): 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;

View File

@ -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);

View File

@ -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 (
<div className="ml-2 flex flex-col gap-2" key={container.getGuid()}>
<div className="flex flex-col gap-2">
@ -126,10 +136,12 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
></OlRangeSlider>
</div>
</div>
{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 (
<div className="ml-4 flex justify-start gap-2" key={index}>
<FontAwesomeIcon
@ -149,6 +161,19 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
${getDrawingLabelColor(drawing)}
text-ellipsis text-nowrap
`}>{drawing.getName()}</div>
<FontAwesomeIcon
icon={faMapLocation}
className={`
ml-auto cusor-pointer transition-transform
hover:scale-125
`}
onClick={() => {
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);
}}
/>
</div>
);
})}
@ -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 {""}
<span className="text-blue-500">blue</span> and SA-6 SAMs for{" "}
<span
className={`text-red-500`}
>
red
</span>
<span className="text-blue-500">blue</span> and SA-6 SAMs for <span className={`
text-red-500
`}>red</span>
).
</div>
<div>
@ -212,9 +234,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<div>
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.
</div>
<div>
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.
</div>
<div>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.</div>
</div>
);
}}
@ -292,9 +312,61 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<div>
<div className="flex flex-col gap-2 p-6">
<div className="text-sm text-gray-400">Mission drawings</div>
<OlSearchBar onChange={(search) => setSearchString(search)} text={searchString || ""}></OlSearchBar>
<div className="flex flex-col gap-2">{mainDrawingsContainer.container && renderDrawingsContainerControls(mainDrawingsContainer.container)}</div>
<div
className={`flex flex-row items-center text-sm text-gray-400`}
>
<span
className={`
mr-2 px-1 py-1 text-center font-bold text-olympus-700
text-white
`}
>
<FaPencil />
</span>
Mission drawings
</div>
<OlSearchBar
key="main-search"
onChange={(search) => {
setSearchString(search);
if (search === "") {
setOpenContainers([]);
}
}}
text={searchString || ""}
></OlSearchBar>
<div className="flex flex-col gap-2">
{mainDrawingsContainer.container && renderDrawingsContainerControls(mainDrawingsContainer.container, searchString)}
</div>
</div>
<div className="flex flex-col gap-2 p-6">
<div
className={`flex flex-row items-center text-sm text-gray-400`}
>
<span
className={`
mr-2 px-1 py-1 text-center font-bold text-olympus-700
text-white
`}
>
<FaRegCompass />
</span>
Navpoints
</div>
<OlSearchBar
key="navpoint-search"
onChange={(search) => {
setNavpointSearchString(search);
if (search === "") {
setOpenContainers([]);
}
}}
text={navpointSearchString || ""}
></OlSearchBar>
<div className="flex flex-col gap-2">
{navpointsContainer.container && renderDrawingsContainerControls(navpointsContainer.container, navpointSearchString)}
</div>
</div>
</div>
</div>
@ -362,9 +434,11 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
bg-olympus-600 p-5
`}
>
<div className={`
border-b-2 border-b-olympus-100 pb-4 text-gray-300
`}>Automatic IADS generation</div>
<div
className={`border-b-2 border-b-olympus-100 pb-4 text-gray-300`}
>
Automatic IADS generation
</div>
<OlDropdown className="" label="Units types" disableAutoClose={true}>
{types.map((type, idx) => {
if (!(type in typesSelection)) {