mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed first iteration of drawings management on v2
This commit is contained in:
parent
1a93ee68d0
commit
b2477112b1
@ -22,6 +22,7 @@ Scheduler* scheduler = nullptr;
|
||||
|
||||
/* Data jsons */
|
||||
json::value missionData = json::value::object();
|
||||
json::value drawingsByLayer = json::value::object();
|
||||
|
||||
mutex mutexLock;
|
||||
string sessionHash;
|
||||
@ -161,3 +162,17 @@ extern "C" DllExport int coreMissionData(lua_State * L)
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
extern "C" DllExport int coreDrawingsData(lua_State* L)
|
||||
{
|
||||
log("Olympus coreDrawingsData called successfully");
|
||||
|
||||
/* Lock for thread safety */
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
|
||||
lua_getglobal(L, "Olympus");
|
||||
lua_getfield(L, -1, "drawingsByLayer");
|
||||
luaTableToJSON(L, -1, drawingsByLayer);
|
||||
|
||||
return(0);
|
||||
}
|
||||
@ -17,6 +17,7 @@ extern UnitsManager* unitsManager;
|
||||
extern WeaponsManager* weaponsManager;
|
||||
extern Scheduler* scheduler;
|
||||
extern json::value missionData;
|
||||
extern json::value drawingsByLayer;
|
||||
extern mutex mutexLock;
|
||||
extern string sessionHash;
|
||||
extern string instancePath;
|
||||
@ -149,6 +150,11 @@ void Server::handle_get(http_request request)
|
||||
else if (URI.compare(COMMANDS_URI) == 0 && query.find(L"commandHash") != query.end()) {
|
||||
answer[L"commandExecuted"] = json::value(scheduler->isCommandExecuted(to_string(query[L"commandHash"])));
|
||||
}
|
||||
/* Drawings data*/
|
||||
else if (URI.compare(DRAWINGS_URI) == 0 && drawingsByLayer.has_object_field(L"drawings")) {
|
||||
log("Trying to answer with drawings...");
|
||||
answer[L"drawings"] = drawingsByLayer[L"drawings"];
|
||||
}
|
||||
|
||||
/* Common data */
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
|
||||
@ -11,12 +11,14 @@ typedef int(__stdcall* f_coreFrame)(lua_State* L);
|
||||
typedef int(__stdcall* f_coreUnitsData)(lua_State* L);
|
||||
typedef int(__stdcall* f_coreWeaponsData)(lua_State* L);
|
||||
typedef int(__stdcall* f_coreMissionData)(lua_State* L);
|
||||
typedef int(__stdcall* f_coreDrawingsData)(lua_State* L);
|
||||
f_coreInit coreInit = nullptr;
|
||||
f_coreDeinit coreDeinit = nullptr;
|
||||
f_coreFrame coreFrame = nullptr;
|
||||
f_coreUnitsData coreUnitsData = nullptr;
|
||||
f_coreWeaponsData coreWeaponsData = nullptr;
|
||||
f_coreMissionData coreMissionData = nullptr;
|
||||
f_coreDrawingsData coreDrawingsData = nullptr;
|
||||
|
||||
string modPath;
|
||||
|
||||
@ -108,6 +110,13 @@ static int onSimulationStart(lua_State* L)
|
||||
goto error;
|
||||
}
|
||||
|
||||
coreDrawingsData = (f_coreDrawingsData)GetProcAddress(hGetProcIDDLL, "coreDrawingsData");
|
||||
if (!coreDrawingsData)
|
||||
{
|
||||
LogError(L, "Error getting coreDrawingsData ProcAddress from DLL");
|
||||
goto error;
|
||||
}
|
||||
|
||||
coreInit(L, modPath.c_str());
|
||||
|
||||
LogInfo(L, "Module loaded and started successfully.");
|
||||
@ -155,6 +164,8 @@ static int onSimulationStop(lua_State* L)
|
||||
coreUnitsData = nullptr;
|
||||
coreWeaponsData = nullptr;
|
||||
coreMissionData = nullptr;
|
||||
|
||||
coreDrawingsData = nullptr;
|
||||
}
|
||||
|
||||
hGetProcIDDLL = NULL;
|
||||
@ -193,6 +204,15 @@ static int setMissionData(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setDrawingsData(lua_State* L)
|
||||
{
|
||||
if (coreDrawingsData)
|
||||
{
|
||||
coreDrawingsData(L);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const luaL_Reg Map[] = {
|
||||
{"onSimulationStart", onSimulationStart},
|
||||
{"onSimulationFrame", onSimulationFrame},
|
||||
@ -200,6 +220,7 @@ static const luaL_Reg Map[] = {
|
||||
{"setUnitsData", setUnitsData },
|
||||
{"setWeaponsData", setWeaponsData },
|
||||
{"setMissionData", setMissionData },
|
||||
{"setDrawingsData", setDrawingsData },
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#define SPOTS_URI "spots"
|
||||
#define MISSION_URI "mission"
|
||||
#define COMMANDS_URI "commands"
|
||||
#define DRAWINGS_URI "drawings"
|
||||
|
||||
#define FRAMERATE_TIME_INTERVAL 0.05
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ export const BULLSEYE_URI = "bullseyes";
|
||||
export const SPOTS_URI = "spots";
|
||||
export const MISSION_URI = "mission";
|
||||
export const COMMANDS_URI = "commands";
|
||||
export const DRAWINGS_URI = "drawings";
|
||||
|
||||
export const NONE = "None";
|
||||
export const GAME_MASTER = "Game master";
|
||||
@ -413,6 +414,7 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
||||
AWACSCoalition: "blue",
|
||||
hideChromeWarning: false,
|
||||
hideSecureWarning: false,
|
||||
showMissionDrawings: false
|
||||
};
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
|
||||
@ -628,6 +628,25 @@ export class AWACSReferenceChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawingsInitEvent {
|
||||
static on(callback: (drawingsData: any /*TODO*/) => void, singleShot = false) {
|
||||
document.addEventListener(
|
||||
this.name,
|
||||
(ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
},
|
||||
{ once: singleShot }
|
||||
);
|
||||
}
|
||||
|
||||
static dispatch(drawingsData: any /*TODO*/) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: drawingsData}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawingsUpdatedEvent extends BaseOlympusEvent {}
|
||||
|
||||
/************** Command mode events ***************/
|
||||
export class CommandModeOptionsChangedEvent {
|
||||
static on(callback: (options: CommandModeOptions) => void, singleShot = false) {
|
||||
|
||||
@ -53,6 +53,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} }
|
||||
}
|
||||
|
||||
export interface ProfileOptions {
|
||||
@ -357,3 +358,41 @@ export interface ServerStatus {
|
||||
connected: boolean;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
export type DrawingPoint = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
||||
|
||||
export type DrawingPrimitiveType = "TextBox" | "Polygon" | "Line" | "Icon";
|
||||
|
||||
export interface Drawing {
|
||||
name: string;
|
||||
visible: boolean;
|
||||
mapX: number;
|
||||
mapY: number;
|
||||
layerName: string;
|
||||
layer: string;
|
||||
primitiveType: DrawingPrimitiveType;
|
||||
colorString: string;
|
||||
fillColorString?: string;
|
||||
borderThickness?: number;
|
||||
fontSize?: number;
|
||||
font?: string;
|
||||
text?: string;
|
||||
angle?: number;
|
||||
radius?: number;
|
||||
points?: PolygonPoints;
|
||||
style?: string;
|
||||
polygonMode?: string;
|
||||
thickness?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
closed?: boolean;
|
||||
lineMode?: string;
|
||||
hiddenOnPlanner?: boolean;
|
||||
file?: string;
|
||||
scale?: number;
|
||||
}
|
||||
650
frontend/react/src/map/drawings/drawingsmanager.ts
Normal file
650
frontend/react/src/map/drawings/drawingsmanager.ts
Normal file
@ -0,0 +1,650 @@
|
||||
import { decimalToRGBA } from "../../other/utils";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { DrawingsInitEvent, DrawingsUpdatedEvent, MapOptionsChangedEvent, SessionDataLoadedEvent } from "../../events";
|
||||
import { MapOptions } from "../../types/types";
|
||||
import { Circle, DivIcon, Layer, LayerGroup, layerGroup, Marker, Polygon, Polyline } from "leaflet";
|
||||
|
||||
export abstract class DCSDrawing {
|
||||
#name: string;
|
||||
#parent: DCSDrawingsContainer;
|
||||
|
||||
constructor(drawingData, parent: DCSDrawingsContainer) {
|
||||
this.#name = drawingData["name"];
|
||||
this.#parent = parent;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
getParent() {
|
||||
return this.#parent;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.#name,
|
||||
opacity: this.getOpacity(),
|
||||
visibility: this.getVisibility(),
|
||||
};
|
||||
}
|
||||
|
||||
abstract getLayer(): Layer;
|
||||
abstract setOpacity(opacity: number): void;
|
||||
abstract getOpacity(): number;
|
||||
abstract setVisibility(visibility: boolean): void;
|
||||
abstract getVisibility(): boolean;
|
||||
}
|
||||
|
||||
export class DCSEmptyLayer extends DCSDrawing {
|
||||
getLayer() {
|
||||
return layerGroup();
|
||||
}
|
||||
|
||||
setOpacity(opacity: number): void {
|
||||
//Do nothing
|
||||
}
|
||||
|
||||
setVisibility(visibility: boolean): void {
|
||||
//Do nothing
|
||||
}
|
||||
|
||||
getOpacity(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getVisibility(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class DCSPolygon extends DCSDrawing {
|
||||
#polygon: Polygon | Circle;
|
||||
|
||||
constructor(drawingData, parent) {
|
||||
super(drawingData, parent);
|
||||
|
||||
const polygonMode = drawingData["polygonMode"];
|
||||
let dashArray: number[] | string = [];
|
||||
|
||||
switch (drawingData.style) {
|
||||
case "dash":
|
||||
dashArray = [5];
|
||||
case "dot":
|
||||
dashArray = [2];
|
||||
case "dotdash":
|
||||
dashArray = "2, 5, 5, 5";
|
||||
}
|
||||
|
||||
switch (polygonMode) {
|
||||
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*/
|
||||
|
||||
this.#polygon = new Circle([drawingData.lat, drawingData.lng], {
|
||||
radius: Math.round(drawingData.radius),
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
fillColor: `${decimalToRGBA(drawingData.fillColorString)}`,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
weight: 1,
|
||||
dashArray: dashArray,
|
||||
});
|
||||
break;
|
||||
|
||||
case "arrow":
|
||||
const arrowBounds = [
|
||||
[drawingData.points["1"].lat, drawingData.points["1"].lng],
|
||||
[drawingData.points["2"].lat, drawingData.points["2"].lng],
|
||||
[drawingData.points["3"].lat, drawingData.points["3"].lng],
|
||||
[drawingData.points["4"].lat, drawingData.points["4"].lng],
|
||||
[drawingData.points["5"].lat, drawingData.points["5"].lng],
|
||||
[drawingData.points["6"].lat, drawingData.points["6"].lng],
|
||||
[drawingData.points["7"].lat, drawingData.points["7"].lng],
|
||||
[drawingData.points["8"].lat, drawingData.points["8"].lng],
|
||||
];
|
||||
|
||||
this.#polygon = new Polygon(arrowBounds, {
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
fillColor: `${decimalToRGBA(drawingData.fillColorString)}`,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
weight: 1,
|
||||
dashArray,
|
||||
});
|
||||
break;
|
||||
|
||||
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
|
||||
}
|
||||
*/
|
||||
const bounds = [
|
||||
[drawingData.points["1"].lat, drawingData.points["1"].lng],
|
||||
[drawingData.points["2"].lat, drawingData.points["2"].lng],
|
||||
[drawingData.points["3"].lat, drawingData.points["3"].lng],
|
||||
[drawingData.points["4"].lat, drawingData.points["4"].lng],
|
||||
];
|
||||
|
||||
this.#polygon = new Polygon(bounds, {
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
fillColor: `${decimalToRGBA(drawingData.fillColorString)}`,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
weight: 1,
|
||||
dashArray: dashArray,
|
||||
});
|
||||
break;
|
||||
|
||||
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
|
||||
}
|
||||
*/
|
||||
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, {
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
fillColor: `${decimalToRGBA(drawingData.fillColorString)}`,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
weight: drawingData.thickness,
|
||||
dashArray: dashArray,
|
||||
});
|
||||
|
||||
case "free":
|
||||
const freePolypoints: [number, number][] = Object.values(drawingData.points as Record<string, { lat: number; lng: number }>).map((p) => [p.lat, p.lng]);
|
||||
|
||||
this.#polygon = new Polygon(freePolypoints, {
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
fillColor: `${decimalToRGBA(drawingData.fillColorString)}`,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
weight: drawingData.thickness,
|
||||
dashArray: dashArray,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setVisibility(true);
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.#polygon;
|
||||
}
|
||||
|
||||
setOpacity(opacity: number): void {
|
||||
if (opacity === this.#polygon.options.fillOpacity && opacity === this.#polygon.options.opacity) return;
|
||||
|
||||
this.#polygon.options.fillOpacity = opacity;
|
||||
this.#polygon.options.opacity = opacity;
|
||||
this.#polygon.redraw();
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
setVisibility(visibility: boolean): void {
|
||||
if (visibility && !this.getParent().getLayerGroup().hasLayer(this.#polygon)) this.#polygon.addTo(this.getParent().getLayerGroup());
|
||||
//@ts-ignore Leaflet typings are wrong
|
||||
if (!visibility && this.getParent().getLayerGroup().hasLayer(this.#polygon)) this.#polygon.removeFrom(this.getParent().getLayerGroup());
|
||||
|
||||
if (visibility && !this.getParent().getVisibility()) this.getParent().setVisibility(true);
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
getOpacity(): number {
|
||||
return this.#polygon.options.opacity ?? 1;
|
||||
}
|
||||
|
||||
getVisibility(): boolean {
|
||||
return this.getParent().getLayerGroup().hasLayer(this.#polygon);
|
||||
}
|
||||
}
|
||||
|
||||
export class DCSLine extends DCSDrawing {
|
||||
#line: Polyline;
|
||||
|
||||
constructor(drawingData, parent) {
|
||||
super(drawingData, parent);
|
||||
|
||||
const points: [number, number][] = Object.values(drawingData.points as Record<string, { lat: number; lng: number }>).map((p) => [p.lat, p.lng]);
|
||||
const dashArray = drawingData.style === "dot" ? "5" : drawingData.style === "dot2" ? "10" : undefined;
|
||||
|
||||
this.#line = new Polyline(points, {
|
||||
color: `${decimalToRGBA(drawingData.colorString)}`,
|
||||
weight: drawingData.thickness,
|
||||
dashArray: dashArray,
|
||||
});
|
||||
|
||||
this.setVisibility(true);
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.#line;
|
||||
}
|
||||
|
||||
setOpacity(opacity: number): void {
|
||||
if (opacity === this.#line.options.opacity) return;
|
||||
|
||||
this.#line.options.opacity = opacity;
|
||||
this.#line.redraw();
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
setVisibility(visibility: boolean): void {
|
||||
if (visibility && !this.getParent().getLayerGroup().hasLayer(this.#line)) this.#line.addTo(this.getParent().getLayerGroup());
|
||||
//@ts-ignore Leaflet typings are wrong
|
||||
if (!visibility && this.getParent().getLayerGroup().hasLayer(this.#line)) this.#line.removeFrom(this.getParent().getLayerGroup());
|
||||
|
||||
if (visibility && !this.getParent().getVisibility()) this.getParent().setVisibility(true);
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
getOpacity(): number {
|
||||
return this.#line.options.opacity ?? 1;
|
||||
}
|
||||
|
||||
getVisibility(): boolean {
|
||||
return this.getParent().getLayerGroup().hasLayer(this.#line);
|
||||
}
|
||||
}
|
||||
|
||||
export class DCSTextBox extends DCSDrawing {
|
||||
#marker: Marker;
|
||||
|
||||
constructor(drawingData, parent) {
|
||||
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
|
||||
*/
|
||||
const customIcon = new DivIcon({
|
||||
html: `
|
||||
<div style="
|
||||
border: ${drawingData.borderThickness}px solid ${decimalToRGBA(drawingData.colorString)};
|
||||
background-color: ${decimalToRGBA(drawingData.fillColorString)};
|
||||
color: ${decimalToRGBA(drawingData.colorString)};
|
||||
padding: 5px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: ${drawingData.fontSize - 1}px;
|
||||
text-align: center;
|
||||
width: max-content;
|
||||
transform: rotate(${drawingData.angle}deg);
|
||||
transform-origin: center;
|
||||
opacity: 100%;
|
||||
">
|
||||
${drawingData.text || drawingData.name}
|
||||
</div>
|
||||
`,
|
||||
// iconSize: [100, 50], // Dimensioni del box
|
||||
iconAnchor: [50, 25], // Punto di ancoraggio al centro
|
||||
className: "",
|
||||
});
|
||||
|
||||
this.#marker = new Marker([drawingData.lat, drawingData.lng], { icon: customIcon });
|
||||
|
||||
this.setVisibility(true);
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.#marker;
|
||||
}
|
||||
|
||||
setOpacity(opacity: number): void {
|
||||
if (opacity === this.#marker.options.opacity) return;
|
||||
|
||||
this.#marker.options.opacity = opacity;
|
||||
|
||||
/* Hack to force marker redraw */
|
||||
const originalVisibility = this.getVisibility();
|
||||
this.setVisibility(false);
|
||||
this.setVisibility(originalVisibility);
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
setVisibility(visibility: boolean): void {
|
||||
if (visibility && !this.getParent().getLayerGroup().hasLayer(this.#marker)) this.#marker.addTo(this.getParent().getLayerGroup());
|
||||
//@ts-ignore Leaflet typings are wrong
|
||||
if (!visibility && this.getParent().getLayerGroup().hasLayer(this.#marker)) this.#marker.removeFrom(this.getParent().getLayerGroup());
|
||||
|
||||
if (visibility && !this.getParent().getVisibility()) this.getParent().setVisibility(true);
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
getOpacity(): number {
|
||||
return this.#marker.options.opacity ?? 1;
|
||||
}
|
||||
|
||||
getVisibility(): boolean {
|
||||
return this.getParent().getLayerGroup().hasLayer(this.#marker);
|
||||
}
|
||||
}
|
||||
|
||||
export class DCSDrawingsContainer {
|
||||
#drawings: DCSDrawing[] = [];
|
||||
#subContainers: DCSDrawingsContainer[] = [];
|
||||
#name: string;
|
||||
#opacity: number = 1;
|
||||
#visibility: boolean = true;
|
||||
#layerGroup: LayerGroup = new LayerGroup();
|
||||
#parent: LayerGroup | DCSDrawingsContainer;
|
||||
#guid: string = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
|
||||
constructor(name: string, parent: LayerGroup | DCSDrawingsContainer) {
|
||||
this.#name = name;
|
||||
this.#parent = parent;
|
||||
this.#layerGroup.addTo(parent instanceof DCSDrawingsContainer ? parent.getLayerGroup() : parent);
|
||||
}
|
||||
|
||||
getGuid() {
|
||||
return this.#guid;
|
||||
}
|
||||
|
||||
initFromData(drawingsData) {
|
||||
let hasContainers = false;
|
||||
Object.keys(drawingsData).forEach((layerName: string) => {
|
||||
if (drawingsData[layerName]["name"] === undefined) {
|
||||
const newContainer = new DCSDrawingsContainer(layerName, this);
|
||||
this.addSubContainer(newContainer);
|
||||
newContainer.initFromData(drawingsData[layerName]);
|
||||
hasContainers = true;
|
||||
}
|
||||
});
|
||||
const othersContainer = new DCSDrawingsContainer("Others", this);
|
||||
if (hasContainers) this.addSubContainer(othersContainer);
|
||||
|
||||
Object.keys(drawingsData).forEach((layerName: string) => {
|
||||
const primitiveType = drawingsData[layerName]["primitiveType"];
|
||||
|
||||
// Possible primitives:
|
||||
// "Line","TextBox","Polygon","Icon"
|
||||
|
||||
// Possible polygon modes:
|
||||
// "arrow","circle","rect","oval","free"
|
||||
|
||||
// Possible Line modes:
|
||||
// 'segments', 'free', 'segment'
|
||||
|
||||
let newDrawing = new DCSEmptyLayer(drawingsData[layerName], othersContainer) as DCSDrawing;
|
||||
|
||||
switch (primitiveType) {
|
||||
case "Polygon":
|
||||
newDrawing = new DCSPolygon(drawingsData[layerName], othersContainer);
|
||||
break;
|
||||
case "TextBox":
|
||||
newDrawing = new DCSTextBox(drawingsData[layerName], othersContainer);
|
||||
break;
|
||||
case "Line":
|
||||
newDrawing = new DCSLine(drawingsData[layerName], othersContainer);
|
||||
break;
|
||||
case "Icon":
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (hasContainers) othersContainer.addDrawing(newDrawing);
|
||||
else this.addDrawing(newDrawing);
|
||||
});
|
||||
|
||||
if (othersContainer.getDrawings().length === 0) this.removeSubContainer(othersContainer); // Remove empty container
|
||||
}
|
||||
|
||||
getLayerGroup() {
|
||||
return this.#layerGroup;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
addDrawing(drawing: DCSDrawing) {
|
||||
this.#drawings.push(drawing);
|
||||
}
|
||||
|
||||
addSubContainer(container: DCSDrawingsContainer) {
|
||||
this.#subContainers.push(container);
|
||||
}
|
||||
|
||||
removeSubContainer(container: DCSDrawingsContainer) {
|
||||
const index = this.#subContainers.indexOf(container);
|
||||
if (index !== -1) this.#subContainers.splice(index, 1);
|
||||
}
|
||||
|
||||
getDrawings() {
|
||||
return this.#drawings;
|
||||
}
|
||||
|
||||
getSubContainers() {
|
||||
return this.#subContainers;
|
||||
}
|
||||
|
||||
setOpacity(opacity: number) {
|
||||
if (opacity === this.#opacity) return;
|
||||
|
||||
this.#opacity = opacity;
|
||||
this.#drawings.forEach((drawing) => drawing.setOpacity(opacity));
|
||||
this.#subContainers.forEach((container) => container.setOpacity(opacity));
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
setVisibility(visibility: boolean, force: boolean = false) {
|
||||
this.#visibility = visibility;
|
||||
|
||||
if (force) {
|
||||
this.#subContainers.forEach((container) => container.setVisibility(visibility, force));
|
||||
this.#drawings.forEach((drawing) => drawing.setVisibility(visibility));
|
||||
}
|
||||
if (visibility && this.#parent instanceof DCSDrawingsContainer && !this.#parent.getVisibility()) this.#parent.setVisibility(true);
|
||||
|
||||
if (this.#parent instanceof DCSDrawingsContainer) {
|
||||
if (visibility && !this.#parent.getLayerGroup().hasLayer(this.#layerGroup)) this.#layerGroup.addTo(this.#parent.getLayerGroup());
|
||||
//@ts-ignore Leaflet typings are wrong
|
||||
if (!visibility && this.#parent.getLayerGroup().hasLayer(this.#layerGroup)) this.#layerGroup.removeFrom(this.#parent.getLayerGroup());
|
||||
} else {
|
||||
if (visibility && !this.#parent.hasLayer(this.#layerGroup)) this.#layerGroup.addTo(this.#parent);
|
||||
//@ts-ignore Leaflet typings are wrong
|
||||
if (!visibility && this.#parent.hasLayer(this.#layerGroup)) this.#layerGroup.removeFrom(this.#parent);
|
||||
}
|
||||
|
||||
getApp().getDrawingsManager().requestUpdateEventDispatch();
|
||||
}
|
||||
|
||||
getOpacity() {
|
||||
return this.#opacity;
|
||||
}
|
||||
|
||||
getVisibility() {
|
||||
return this.#visibility;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
let JSON = { drawings: {}, containers: {}, guid: this.#guid, name: this.#name, opacity: this.#opacity, visibility: this.#visibility };
|
||||
this.#drawings.forEach((drawing) => {
|
||||
JSON["drawings"][drawing.getName()] = drawing.toJSON();
|
||||
});
|
||||
this.#subContainers.forEach((container) => {
|
||||
JSON["containers"][container.getName()] = container.toJSON();
|
||||
});
|
||||
return JSON;
|
||||
}
|
||||
|
||||
fromJSON(JSON) {
|
||||
this.#subContainers.forEach((container) => {
|
||||
if (JSON["containers"][container.getName()]) container.fromJSON(JSON["containers"][container.getName()]);
|
||||
});
|
||||
this.#drawings.forEach((drawing) => {
|
||||
if (JSON["drawings"][drawing.getName()]) {
|
||||
drawing.setOpacity(JSON["drawings"][drawing.getName()].opacity);
|
||||
drawing.setVisibility(JSON["drawings"][drawing.getName()].visibility);
|
||||
}
|
||||
});
|
||||
this.setOpacity(JSON["opacity"]);
|
||||
this.setVisibility(JSON["visibility"]);
|
||||
}
|
||||
|
||||
hasSearchString(searchString) {
|
||||
if (this.getName().toLowerCase().includes(searchString.toLowerCase())) return true;
|
||||
if (
|
||||
this.getDrawings().some((drawing) =>
|
||||
drawing
|
||||
.getName()
|
||||
?.toLowerCase()
|
||||
.includes(searchString.toLowerCase() ?? false)
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (this.getSubContainers().some((container) => container.hasSearchString(searchString))) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawingsManager {
|
||||
#drawingsContainer: DCSDrawingsContainer;
|
||||
#updateEventRequested: boolean = false;
|
||||
#sessionDataDrawings = {};
|
||||
#initialized: boolean = false;
|
||||
|
||||
constructor() {
|
||||
const drawingsLayerGroup = new LayerGroup();
|
||||
drawingsLayerGroup.addTo(getApp().getMap());
|
||||
this.#drawingsContainer = new DCSDrawingsContainer("Mission drawings", drawingsLayerGroup);
|
||||
|
||||
MapOptionsChangedEvent.on((mapOptions: MapOptions) => {
|
||||
this.#drawingsContainer.setVisibility(mapOptions.showMissionDrawings);
|
||||
});
|
||||
|
||||
SessionDataLoadedEvent.on((sessionData) => {
|
||||
this.#sessionDataDrawings = sessionData.drawings ?? {};
|
||||
if (this.#initialized) if (this.#sessionDataDrawings["Mission drawings"]) this.#drawingsContainer.fromJSON(this.#sessionDataDrawings["Mission drawings"]);
|
||||
});
|
||||
}
|
||||
|
||||
initDrawings(data: { drawings: Record<string, Record<string, any>> }): boolean {
|
||||
if (data && data.drawings) {
|
||||
this.#drawingsContainer.initFromData(data.drawings);
|
||||
if (this.#sessionDataDrawings["Mission drawings"]) this.#drawingsContainer.fromJSON(this.#sessionDataDrawings["Mission drawings"]);
|
||||
DrawingsInitEvent.dispatch(this.#drawingsContainer);
|
||||
this.#initialized = true;
|
||||
return true;
|
||||
} else {
|
||||
console.error("Error while initializing drawings, empty data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getDrawingsContainer() {
|
||||
return this.#drawingsContainer;
|
||||
}
|
||||
|
||||
requestUpdateEventDispatch() {
|
||||
if (this.#updateEventRequested) return;
|
||||
this.#updateEventRequested = true;
|
||||
window.setTimeout(() => {
|
||||
this.#updateEventRequested = false;
|
||||
DrawingsUpdatedEvent.dispatch();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,7 @@ import {
|
||||
ConfigLoadedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
DrawingsInitEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapContextMenuRequestEvent,
|
||||
MapOptionsChangedEvent,
|
||||
@ -344,7 +345,8 @@ export class Map extends L.Map {
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
}).addShortcut("clearMeasures", {
|
||||
})
|
||||
.addShortcut("clearMeasures", {
|
||||
label: "Clear measures",
|
||||
keyUpCallback: () => {
|
||||
this.clearMeasures();
|
||||
@ -1329,4 +1331,12 @@ export class Map extends L.Map {
|
||||
this.#measures.forEach((measure) => measure.remove());
|
||||
this.#measures = [];
|
||||
}
|
||||
|
||||
getMapLayer(layerName: string) {
|
||||
return this.#mapLayers[layerName] && this.#mapLayers[layerName];
|
||||
}
|
||||
|
||||
getAllMapLayers() {
|
||||
return this.#mapLayers;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import { SessionDataManager } from "./sessiondata";
|
||||
import { ControllerManager } from "./controllers/controllermanager";
|
||||
import { AWACSController } from "./controllers/awacs";
|
||||
import { CoalitionAreasManager } from "./map/coalitionarea/coalitionareamanager";
|
||||
import { DrawingsManager } from "./map/drawings/drawingsmanager";
|
||||
|
||||
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
export var IP = window.location.toString();
|
||||
@ -53,6 +54,7 @@ export class OlympusApp {
|
||||
#sessionDataManager: SessionDataManager;
|
||||
#controllerManager: ControllerManager;
|
||||
#coalitionAreasManager: CoalitionAreasManager;
|
||||
#drawingsManager: DrawingsManager;
|
||||
//#pluginsManager: // TODO
|
||||
|
||||
constructor() {
|
||||
@ -105,6 +107,10 @@ export class OlympusApp {
|
||||
return this.#coalitionAreasManager;
|
||||
}
|
||||
|
||||
getDrawingsManager() {
|
||||
return this.#drawingsManager;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
getPluginsManager() {
|
||||
return null // this.#pluginsManager as PluginsManager;
|
||||
@ -125,6 +131,7 @@ export class OlympusApp {
|
||||
this.#audioManager = new AudioManager();
|
||||
this.#controllerManager = new ControllerManager();
|
||||
this.#coalitionAreasManager = new CoalitionAreasManager();
|
||||
this.#drawingsManager = new DrawingsManager();
|
||||
|
||||
/* Check if we are running the latest version */
|
||||
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
|
||||
|
||||
@ -645,4 +645,13 @@ export function normalizeAngle(angle: number): number {
|
||||
angle += 360;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
export function decimalToRGBA(decimal: number): string {
|
||||
const r = (decimal >>> 24) & 0xff;
|
||||
const g = (decimal >>> 16) & 0xff;
|
||||
const b = (decimal >>> 8) & 0xff;
|
||||
const a = (decimal & 0xff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {
|
||||
AIRBASES_URI,
|
||||
BULLSEYE_URI,
|
||||
COMMANDS_URI,
|
||||
DRAWINGS_URI,
|
||||
LOGS_URI,
|
||||
MISSION_URI,
|
||||
NONE,
|
||||
@ -220,6 +221,10 @@ export class ServerManager {
|
||||
this.GET(callback, errorCallback, WEAPONS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[WEAPONS_URI] }, "arraybuffer", refresh);
|
||||
}
|
||||
|
||||
getDrawings(callback: CallableFunction, errorCallback: CallableFunction, refresh: boolean = false) {
|
||||
this.GET(callback, errorCallback, DRAWINGS_URI);
|
||||
}
|
||||
|
||||
isCommandExecuted(callback: CallableFunction, commandHash: string, errorCallback: CallableFunction = () => {}) {
|
||||
this.GET(callback, errorCallback, COMMANDS_URI, {
|
||||
commandHash: commandHash,
|
||||
@ -577,6 +582,17 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
loadEnvResources() {
|
||||
/* Load the drawings */
|
||||
this.getDrawings((drawingsData: { drawings: Record<string, Record<string, any>> }) => {
|
||||
if (drawingsData) {
|
||||
getApp().getDrawingsManager()?.initDrawings(drawingsData);
|
||||
}
|
||||
}, () => {});
|
||||
|
||||
// TODO: load navPoints
|
||||
}
|
||||
|
||||
startUpdate() {
|
||||
/* Clear any existing interval */
|
||||
this.#intervals.forEach((interval: number) => {
|
||||
@ -584,6 +600,9 @@ export class ServerManager {
|
||||
});
|
||||
this.#intervals = [];
|
||||
|
||||
// Load mission env resources (one shot)
|
||||
this.loadEnvResources();
|
||||
|
||||
this.#intervals.push(
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused()) {
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
AudioSinksChangedEvent,
|
||||
AudioSourcesChangedEvent,
|
||||
CoalitionAreasChangedEvent,
|
||||
DrawingsUpdatedEvent,
|
||||
HotgroupsChangedEvent,
|
||||
SessionDataChangedEvent,
|
||||
SessionDataLoadedEvent,
|
||||
@ -130,7 +131,13 @@ export class SessionDataManager {
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => {
|
||||
this.#sessionData.starredSpawns = starredSpawns;
|
||||
this.#saveSessionData();
|
||||
})
|
||||
});
|
||||
|
||||
DrawingsUpdatedEvent.on(() => {
|
||||
let container = getApp().getDrawingsManager().getDrawingsContainer();
|
||||
this.#sessionData.drawings = {"Mission drawings": container.toJSON()};
|
||||
this.#saveSessionData();
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ export type MapOptions = {
|
||||
AWACSCoalition: Coalition;
|
||||
hideChromeWarning: boolean;
|
||||
hideSecureWarning: boolean;
|
||||
showMissionDrawings: boolean;
|
||||
};
|
||||
|
||||
export type MapHiddenTypes = {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { FaArrowDown, FaArrowUp, FaTrash } from "react-icons/fa";
|
||||
import { FaArrowDown, FaArrowUp, FaChevronRight, FaTrash } from "react-icons/fa";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faDrawPolygon, faEye, faEyeSlash } 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";
|
||||
@ -13,9 +13,12 @@ import { Coalition } from "../../types/types";
|
||||
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 } from "../../events";
|
||||
import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent, DrawingsInitEvent, DrawingsUpdatedEvent } from "../../events";
|
||||
import { FaXmark } from "react-icons/fa6";
|
||||
import { deepCopyTable } from "../../other/utils";
|
||||
import { DCSDrawingsContainer, DCSEmptyLayer } from "../../map/drawings/drawingsmanager";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
|
||||
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
@ -30,11 +33,21 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [erasSelection, setErasSelection] = useState({});
|
||||
const [rangesSelection, setRangesSelection] = useState({});
|
||||
|
||||
const [openContainers, setOpenContainers] = useState([] as DCSDrawingsContainer[]);
|
||||
const [mainDrawingsContainer, setDrawingsContainer] = useState({ container: null } as { container: null | DCSDrawingsContainer });
|
||||
const [searchString, setSearchString] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
DrawingsInitEvent.on((drawingContainer) => {
|
||||
setDrawingsContainer({ container: drawingContainer });
|
||||
});
|
||||
DrawingsUpdatedEvent.on(() => {
|
||||
setDrawingsContainer({ container: getApp().getDrawingsManager().getDrawingsContainer() });
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Get all the unique types and eras for groundunits */
|
||||
@ -50,6 +63,85 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
CoalitionAreasChangedEvent.on((coalitionAreas) => setCoalitionAreas([...coalitionAreas]));
|
||||
}, []);
|
||||
|
||||
function renderDrawingsContainerControls(container: DCSDrawingsContainer) {
|
||||
if (container.hasSearchString(searchString)) {
|
||||
return (
|
||||
<div className="ml-2 flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between gap-2">
|
||||
<FaChevronRight
|
||||
className={`
|
||||
my-auto
|
||||
${openContainers.includes(container) && `rotate-90`}
|
||||
cursor-pointer text-gray-400 transition-transform
|
||||
`}
|
||||
onClick={() => {
|
||||
if (openContainers.includes(container)) {
|
||||
let index = openContainers.indexOf(container);
|
||||
openContainers.splice(index, 1);
|
||||
} else {
|
||||
openContainers.push(container);
|
||||
}
|
||||
setOpenContainers([...openContainers]);
|
||||
}}
|
||||
></FaChevronRight>
|
||||
<FontAwesomeIcon
|
||||
icon={container.getVisibility() ? faEye : faEyeSlash}
|
||||
className={`
|
||||
my-auto w-6 cursor-pointer text-gray-400 transition-transform
|
||||
hover:scale-125 hover:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
container.setVisibility(!container.getVisibility(), true);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
w-40 w-max-40 overflow-hidden text-ellipsis text-nowrap bg-
|
||||
`}
|
||||
>
|
||||
{container.getName()}
|
||||
</div>
|
||||
|
||||
<OlRangeSlider
|
||||
value={container.getOpacity() * 100}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={(ev) => {
|
||||
container.setOpacity(Number(ev.currentTarget.value) / 100);
|
||||
}}
|
||||
className={`my-auto ml-auto max-w-32`}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
</div>
|
||||
{openContainers.includes(container) && container.getSubContainers().map((container) => renderDrawingsContainerControls(container))}
|
||||
{openContainers.includes(container) &&
|
||||
container.getDrawings().map((drawing) => {
|
||||
if (drawing instanceof DCSEmptyLayer) return <></>;
|
||||
return (
|
||||
<div className="ml-4 flex justify-start gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={drawing.getVisibility() ? faEye : faEyeSlash}
|
||||
className={`
|
||||
my-auto w-6 cursor-pointer text-gray-400
|
||||
transition-transform
|
||||
hover:scale-125 hover:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
drawing.setVisibility(!drawing.getVisibility());
|
||||
}}
|
||||
/>
|
||||
<div className={`overflow-hidden text-ellipsis text-nowrap`}>{drawing.getName()}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
@ -132,6 +224,14 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
|
||||
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload, faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP } from "../../olympusapp";
|
||||
@ -16,8 +16,24 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight, FaFloppyDisk } from "react-icons/fa6";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent, SessionDataChangedEvent, SessionDataSavedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ImportExportSubstate, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, OlympusState, RED_COMMANDER } from "../../constants/constants";
|
||||
import {
|
||||
CommandModeOptionsChangedEvent,
|
||||
ConfigLoadedEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
SessionDataChangedEvent,
|
||||
SessionDataSavedEvent,
|
||||
} from "../../events";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
COMMAND_MODE_OPTIONS_DEFAULTS,
|
||||
ImportExportSubstate,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
OlympusState,
|
||||
RED_COMMANDER,
|
||||
} from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
import { FaCheck, FaSave, FaSpinner } from "react-icons/fa";
|
||||
|
||||
@ -116,15 +132,40 @@ export function Header() {
|
||||
{IP}
|
||||
</div>
|
||||
</div>
|
||||
{savingSessionData ? <div className="text-white"><FaSpinner className={`
|
||||
animate-spin text-2xl
|
||||
`}/></div> : <div className={`relative text-white`}><FaFloppyDisk className={`
|
||||
absolute -top-3 text-2xl
|
||||
`}/><FaCheck className={`
|
||||
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
|
||||
`}/><FaCheck className={`absolute left-3 top-0 text-green-500`}/></div>}
|
||||
<OlStateButton className="ml-8" icon={faDownload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.EXPORT)}} checked={false}/>
|
||||
<OlStateButton icon={faUpload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.IMPORT)}} checked={false}/>
|
||||
{savingSessionData ? (
|
||||
<div className="text-white">
|
||||
<FaSpinner
|
||||
className={`animate-spin text-2xl`}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`relative text-white`}>
|
||||
<FaFloppyDisk
|
||||
className={`absolute -top-3 text-2xl`}
|
||||
/>
|
||||
<FaCheck
|
||||
className={`
|
||||
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
|
||||
`}
|
||||
/>
|
||||
<FaCheck className={`absolute left-3 top-0 text-green-500`} />
|
||||
</div>
|
||||
)}
|
||||
<OlStateButton
|
||||
className="ml-8"
|
||||
icon={faDownload}
|
||||
onClick={() => {
|
||||
getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.EXPORT);
|
||||
}}
|
||||
checked={false}
|
||||
/>
|
||||
<OlStateButton
|
||||
icon={faUpload}
|
||||
onClick={() => {
|
||||
getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.IMPORT);
|
||||
}}
|
||||
checked={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && (
|
||||
@ -138,6 +179,14 @@ export function Header() {
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
|
||||
<OlRoundStateButton
|
||||
icon={faDrawPolygon}
|
||||
checked={mapOptions.showMissionDrawings}
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showMissionDrawings", !mapOptions.showMissionDrawings);
|
||||
}}
|
||||
tooltip="Show/Hide mission drawings"
|
||||
/>
|
||||
<OlLockStateButton
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
onClick={() => {
|
||||
|
||||
@ -13,6 +13,7 @@ Olympus.log = mist.Logger:new("Olympus", 'info')
|
||||
Olympus.missionData = {}
|
||||
Olympus.unitsData = {}
|
||||
Olympus.weaponsData = {}
|
||||
Olympus.drawingsByLayer = {}
|
||||
|
||||
-- Units data structures
|
||||
Olympus.unitCounter = 1 -- Counter to generate unique names
|
||||
@ -1075,6 +1076,71 @@ function getUnitDescription(unit)
|
||||
return unit:getDescr()
|
||||
end
|
||||
|
||||
-- This function is periodically called to collect the data of all the existing drawings in the mission to be transmitted to the olympus.dll
|
||||
function Olympus.initializeDrawings()
|
||||
local drawings = {}
|
||||
if mist.DBs.drawingByName ~= nil then
|
||||
for drawingName, drawingData in pairs(mist.DBs.drawingByName) do
|
||||
local customLayer = drawingData.name:match("^%[LYR:(.-)%]")
|
||||
|
||||
-- Let's convert DCS coords to lat lon
|
||||
local vec3 = { x = drawingData['mapX'], y = 0, z = drawingData['mapY'] }
|
||||
local lat, lng = coord.LOtoLL(vec3)
|
||||
drawingData['lat'] = lat
|
||||
drawingData['lng'] = lng
|
||||
|
||||
-- If the drawing has points, we have to convert those too
|
||||
if drawingData['points'] ~= nil then
|
||||
if drawingData['points']['x'] ~= nil then
|
||||
-- In this case we have only one point
|
||||
local point = { x = drawingData['points']['x'], y = 0, z = drawingData['points']['y'] }
|
||||
local pointLat, pointLng = coord.LOtoLL(point)
|
||||
drawingData['points'][0] = { lat = pointLat, lng = pointLng }
|
||||
else
|
||||
-- In this case we have multiple points indexed by number
|
||||
for pointNumber, pointLOCoords in pairs(drawingData['points']) do
|
||||
local point = { x = pointLOCoords['x'], y = 0, z = pointLOCoords['y'] }
|
||||
local pointLat, pointLng = coord.LOtoLL(point)
|
||||
|
||||
drawingData['points'][pointNumber] = { lat = pointLat, lng = pointLng }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Let's initialize the layers
|
||||
if drawings[drawingData.layerName] == nil then
|
||||
drawings[drawingData.layerName] = {}
|
||||
end
|
||||
|
||||
if customLayer and drawings[drawingData.layerName][customLayer] == nil then
|
||||
drawings[drawingData.layerName][customLayer] = {}
|
||||
end
|
||||
|
||||
-- Let's put the drawing in the correct layer
|
||||
if customLayer then
|
||||
-- Let's remove the tag from the drawing name
|
||||
local cleanDrawingName = string.match(drawingName, "%] (.+)")
|
||||
drawingData.name = cleanDrawingName
|
||||
-- The drawing has the custom layer tag
|
||||
drawings[drawingData.layerName][customLayer][cleanDrawingName] = drawingData
|
||||
else
|
||||
-- The drawing is a standard drawing
|
||||
drawings[drawingData.layerName][drawingName] = drawingData
|
||||
end
|
||||
end
|
||||
|
||||
Olympus.drawingsByLayer["drawings"] = drawings
|
||||
|
||||
-- Send the drawings to the DLL
|
||||
Olympus.OlympusDLL.setDrawingsData()
|
||||
|
||||
Olympus.notify("Olympus drawings initialized", 2)
|
||||
else
|
||||
Olympus.debug("MIST DBs not ready", 2)
|
||||
timer.scheduleFunction(Olympus.initializeDrawings, {}, timer.getTime() + 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function is periodically called to collect the data of all the existing units in the mission to be transmitted to the olympus.dll
|
||||
function Olympus.setUnitsData(arg, time)
|
||||
-- Units data
|
||||
@ -1560,5 +1626,8 @@ timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
|
||||
-- Initialize the ME units
|
||||
Olympus.initializeUnits()
|
||||
|
||||
-- Initialize the Drawings
|
||||
Olympus.initializeDrawings()
|
||||
|
||||
Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user