diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts
index d47106cb..6b1eaa18 100644
--- a/frontend/react/src/interfaces.ts
+++ b/frontend/react/src/interfaces.ts
@@ -51,7 +51,8 @@ export interface SessionData {
| { type: 'circle', label: string; latlng: { lat: number; lng: number }; radius: number; coalition: Coalition }
| { type: 'polygon', label: string; latlngs: { lat: number; lng: number }[]; coalition: Coalition }
)[];
- hotgroups?: {[key: string]: number[]}
+ hotgroups?: {[key: string]: number[]},
+ starredSpawns?: { [key: number]: SpawnRequestTable }
}
export interface ProfileOptions {
diff --git a/frontend/react/src/map/coalitionarea/coalitionareamanager.ts b/frontend/react/src/map/coalitionarea/coalitionareamanager.ts
index 25903b84..50756a49 100644
--- a/frontend/react/src/map/coalitionarea/coalitionareamanager.ts
+++ b/frontend/react/src/map/coalitionarea/coalitionareamanager.ts
@@ -2,7 +2,7 @@ import { LatLng, LeafletMouseEvent } from "leaflet";
import { DrawSubState, OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, CoalitionAreaChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent, SessionDataLoadedEvent } from "../../events";
import { getApp } from "../../olympusapp";
-import { areaContains } from "../../other/utils";
+import { areaContains, deepCopyTable } from "../../other/utils";
import { CoalitionCircle } from "./coalitioncircle";
import { CoalitionPolygon } from "./coalitionpolygon";
import { SessionData } from "../../interfaces";
@@ -45,7 +45,7 @@ export class CoalitionAreasManager {
SessionDataLoadedEvent.on((sessionData: SessionData) => {
/* Make a local copy */
- const localSessionData = JSON.parse(JSON.stringify(sessionData)) as SessionData;
+ const localSessionData = deepCopyTable(sessionData) as SessionData;
this.#areas.forEach((area) => this.deleteCoalitionArea(area));
localSessionData.coalitionAreas?.forEach((options) => {
if (options.type === "circle") {
diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts
index 361ac230..19fdb672 100644
--- a/frontend/react/src/map/map.ts
+++ b/frontend/react/src/map/map.ts
@@ -3,7 +3,7 @@ import { getApp } from "../olympusapp";
import { BoxSelect } from "./boxselect";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
-import { areaContains, deg2rad, getGroundElevation } from "../other/utils";
+import { areaContains, deepCopyTable, deg2rad, getGroundElevation } from "../other/utils";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import {
@@ -53,6 +53,7 @@ import {
PasteEnabledChangedEvent,
SelectionClearedEvent,
SelectionEnabledChangedEvent,
+ SessionDataLoadedEvent,
SpawnContextMenuRequestEvent,
StarredSpawnsChangedEvent,
UnitDeselectedEvent,
@@ -291,6 +292,14 @@ export class Map extends L.Map {
ContextActionChangedEvent.on((contextAction) => this.#updateDestinationPreviewMarkers());
MapOptionsChangedEvent.on((mapOptions) => this.#moveDestinationPreviewMarkers());
+ SessionDataLoadedEvent.on((sessionData) => {
+ const localSessionData = deepCopyTable(sessionData);
+ if (localSessionData.starredSpawns) {
+ this.#starredSpawnRequestTables = localSessionData.starredSpawns;
+ StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
+ }
+ });
+
window.addEventListener("blur", () => {
this.setSelectionEnabled(false);
this.setPasteEnabled(false);
diff --git a/frontend/react/src/map/markers/stylesheets/units.css b/frontend/react/src/map/markers/stylesheets/units.css
index 4b7bf004..2e5222e0 100644
--- a/frontend/react/src/map/markers/stylesheets/units.css
+++ b/frontend/react/src/map/markers/stylesheets/units.css
@@ -258,9 +258,9 @@
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
- width: 80px;
+ width: 100px;
height: 45px;
- translate: 60px 2px;
+ translate: 80px 2px;
}
[todo-data-awacs-mode] [data-object|="unit"] .unit-summary.cluster-north {
diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts
index b7120bfe..9e3a6041 100644
--- a/frontend/react/src/other/utils.ts
+++ b/frontend/react/src/other/utils.ts
@@ -436,3 +436,12 @@ export function mode(array) {
}
return maxEl;
}
+
+export function deepCopyTable(table) {
+ try {
+ return JSON.parse(JSON.stringify(table));
+ } catch (error) {
+ console.error(error);
+ return {};
+ }
+}
diff --git a/frontend/react/src/sessiondata.ts b/frontend/react/src/sessiondata.ts
index 53fe30d1..382a99f3 100644
--- a/frontend/react/src/sessiondata.ts
+++ b/frontend/react/src/sessiondata.ts
@@ -12,6 +12,7 @@ import {
SessionDataChangedEvent,
SessionDataLoadedEvent,
SessionDataSavedEvent,
+ StarredSpawnsChangedEvent,
} from "./events";
import { SessionData } from "./interfaces";
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
@@ -125,6 +126,11 @@ export class SessionDataManager {
});
this.#saveSessionData();
});
+
+ StarredSpawnsChangedEvent.on((starredSpawns) => {
+ this.#sessionData.starredSpawns = starredSpawns;
+ this.#saveSessionData();
+ })
}, 200);
});
}
diff --git a/frontend/react/src/ui/panels/drawingmenu.tsx b/frontend/react/src/ui/panels/drawingmenu.tsx
index cf7c8a1f..8bf2ed41 100644
--- a/frontend/react/src/ui/panels/drawingmenu.tsx
+++ b/frontend/react/src/ui/panels/drawingmenu.tsx
@@ -15,6 +15,7 @@ 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 { FaXmark } from "react-icons/fa6";
+import { deepCopyTable } from "../../other/utils";
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
@@ -205,7 +206,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
{types.map((type, idx) => {
if (!(type in typesSelection)) {
typesSelection[type] = true;
- setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
+ setTypesSelection(deepCopyTable(typesSelection));
}
return (
@@ -214,7 +215,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
checked={typesSelection[type]}
onChange={(ev) => {
typesSelection[type] = ev.currentTarget.checked;
- setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
+ setTypesSelection(deepCopyTable(typesSelection));
}}
/>
{type}
@@ -226,7 +227,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
{eras.map((era) => {
if (!(era in erasSelection)) {
erasSelection[era] = true;
- setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
+ setErasSelection(deepCopyTable(erasSelection));
}
return (
@@ -235,7 +236,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
checked={erasSelection[era]}
onChange={(ev) => {
erasSelection[era] = ev.currentTarget.checked;
- setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
+ setErasSelection(deepCopyTable(erasSelection));
}}
/>
{era}
@@ -247,7 +248,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
{["Short range", "Medium range", "Long range"].map((range) => {
if (!(range in rangesSelection)) {
rangesSelection[range] = true;
- setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
+ setRangesSelection(deepCopyTable(rangesSelection));
}
return (
@@ -256,7 +257,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
checked={rangesSelection[range]}
onChange={(ev) => {
rangesSelection[range] = ev.currentTarget.checked;
- setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
+ setErasSelection(deepCopyTable(rangesSelection));
}}
/>
{range}
diff --git a/frontend/react/src/ui/panels/unitcontrolmenu.tsx b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
index 85640c00..f75935ae 100644
--- a/frontend/react/src/ui/panels/unitcontrolmenu.tsx
+++ b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
@@ -46,7 +46,7 @@ import {
olButtonsVisibilityOlympus,
} from "../components/olicons";
import { Coalition } from "../../types/types";
-import { convertROE, ftToM, knotsToMs, mToFt, msToKnots } from "../../other/utils";
+import { convertROE, deepCopyTable, ftToM, knotsToMs, mToFt, msToKnots } from "../../other/utils";
import { FaCog, FaGasPump, FaSignal, FaTag } from "react-icons/fa";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OlSearchBar } from "../components/olsearchbar";
@@ -270,7 +270,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
key={entry[0]}
onClick={() => {
selectionFilter["control"][entry[0]] = !selectionFilter["control"][entry[0]];
- setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
+ setSelectionFilter(deepCopyTable(selectionFilter));
}}
toggled={selectionFilter["control"][entry[0]]}
/>
@@ -319,7 +319,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
disabled={selectionID !== null}
onChange={() => {
selectionFilter[coalition][entry[0]] = !selectionFilter[coalition][entry[0]];
- setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
+ setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
@@ -339,7 +339,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
Object.keys(selectionFilter["blue"]).forEach((key) => {
selectionFilter["blue"][key] = newValue;
});
- setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
+ setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
@@ -351,7 +351,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
Object.keys(selectionFilter["neutral"]).forEach((key) => {
selectionFilter["neutral"][key] = newValue;
});
- setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
+ setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
@@ -363,7 +363,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
Object.keys(selectionFilter["red"]).forEach((key) => {
selectionFilter["red"][key] = newValue;
});
- setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
+ setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
@@ -812,8 +812,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
`}
onClick={() => {
setActiveAdvancedSettings({
- radio: JSON.parse(JSON.stringify(selectedUnits[0].getRadio())),
- TACAN: JSON.parse(JSON.stringify(selectedUnits[0].getTACAN())),
+ radio: deepCopyTable(selectedUnits[0].getRadio()),
+ TACAN: deepCopyTable(selectedUnits[0].getTACAN()),
});
setShowAdvancedSettings(true);
}}
@@ -1104,7 +1104,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
key={idx}
onClick={() => {
if (activeAdvancedSettings) activeAdvancedSettings.radio.callsign = idx + 1;
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
>
{name}
@@ -1123,7 +1123,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
key={idx}
onClick={() => {
if (activeAdvancedSettings) activeAdvancedSettings.radio.callsign = idx + 1;
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
>
{name}
@@ -1141,17 +1141,17 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
max={9}
onChange={(e) => {
if (activeAdvancedSettings) activeAdvancedSettings.radio.callsignNumber = Math.max(Math.min(Number(e.target.value), 9), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
onDecrease={() => {
if (activeAdvancedSettings)
activeAdvancedSettings.radio.callsignNumber = Math.max(Math.min(Number(activeAdvancedSettings.radio.callsignNumber - 1), 9), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
onIncrease={() => {
if (activeAdvancedSettings)
activeAdvancedSettings.radio.callsignNumber = Math.max(Math.min(Number(activeAdvancedSettings.radio.callsignNumber + 1), 9), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
value={activeAdvancedSettings ? activeAdvancedSettings.radio.callsignNumber : 1}
>
@@ -1163,17 +1163,17 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
max={126}
onChange={(e) => {
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.channel = Math.max(Math.min(Number(e.target.value), 126), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
onDecrease={() => {
if (activeAdvancedSettings)
activeAdvancedSettings.TACAN.channel = Math.max(Math.min(Number(activeAdvancedSettings.TACAN.channel - 1), 126), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
onIncrease={() => {
if (activeAdvancedSettings)
activeAdvancedSettings.TACAN.channel = Math.max(Math.min(Number(activeAdvancedSettings.TACAN.channel + 1), 126), 1);
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
value={activeAdvancedSettings ? activeAdvancedSettings.TACAN.channel : 1}
>
@@ -1186,7 +1186,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
key={"X"}
onClick={() => {
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.XY = "X";
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
>
X
@@ -1195,7 +1195,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
key={"Y"}
onClick={() => {
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.XY = "Y";
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
>
Y
@@ -1210,7 +1210,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
if (activeAdvancedSettings.TACAN.callsign.length > 3)
activeAdvancedSettings.TACAN.callsign = activeAdvancedSettings.TACAN.callsign.slice(0, 3);
}
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
/>
@@ -1220,7 +1220,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
toggled={activeAdvancedSettings ? activeAdvancedSettings.TACAN.isOn : false}
onClick={() => {
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.isOn = !activeAdvancedSettings.TACAN.isOn;
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}}
/>
@@ -1232,7 +1232,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
onChange={(value) => {
if (activeAdvancedSettings) {
activeAdvancedSettings.radio.frequency = value;
- setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
+ setActiveAdvancedSettings(deepCopyTable(activeAdvancedSettings));
}
}}
/>
diff --git a/frontend/react/src/ui/panels/unitspawnmenu.tsx b/frontend/react/src/ui/panels/unitspawnmenu.tsx
index 617fa7ee..6aeb67b0 100644
--- a/frontend/react/src/ui/panels/unitspawnmenu.tsx
+++ b/frontend/react/src/ui/panels/unitspawnmenu.tsx
@@ -9,7 +9,7 @@ import { LoadoutBlueprint, SpawnRequestTable, UnitBlueprint } from "../../interf
import { OlStateButton } from "../components/olstatebutton";
import { Coalition } from "../../types/types";
import { getApp } from "../../olympusapp";
-import { ftToM, hash, mode } from "../../other/utils";
+import { deepCopyTable, ftToM, hash, mode } from "../../other/utils";
import { LatLng } from "leaflet";
import { Airbase } from "../../mission/airbase";
import { altitudeIncrements, groupUnitCount, maxAltitudeValues, minAltitudeValues, OlympusState, SpawnSubState } from "../../constants/constants";
@@ -50,7 +50,7 @@ export function UnitSpawnMenu(props: {
const [showLoadout, setShowLoadout] = useState(false);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [showUnitSummary, setShowUnitSummary] = useState(false);
- const [quickAccessName, setQuickAccessName] = useState("No name");
+ const [quickAccessName, setQuickAccessName] = useState("Preset 1");
const [key, setKey] = useState("");
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
@@ -69,7 +69,7 @@ export function UnitSpawnMenu(props: {
const setSpawnRequestTableCallback = useCallback(() => {
if (spawnRequestTable) {
/* Refresh the unique key identified */
- const tempTable = JSON.parse(JSON.stringify(spawnRequestTable));
+ const tempTable = deepCopyTable(spawnRequestTable);
delete tempTable.quickAccessName;
delete tempTable.unit.location;
delete tempTable.unit.altitude;
@@ -93,7 +93,7 @@ export function UnitSpawnMenu(props: {
if (!props.airbase) {
/* If the spawn is starred, set the quick access name */
if (key in props.starredSpawns && props.starredSpawns[key].quickAccessName) setQuickAccessName(props.starredSpawns[key].quickAccessName);
- else setQuickAccessName("No name");
+ else setQuickAccessName(`Preset ${Object.keys(props.starredSpawns).length + 1}`);
}
}, [props.starredSpawns, key]);
useEffect(updateQuickAccessName, [key]);
diff --git a/frontend/react/src/unit/unitsmanager.ts b/frontend/react/src/unit/unitsmanager.ts
index 0ac19cbd..3f06d1be 100644
--- a/frontend/react/src/unit/unitsmanager.ts
+++ b/frontend/react/src/unit/unitsmanager.ts
@@ -1,7 +1,17 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getApp } from "../olympusapp";
import { AirUnit, Unit } from "./unit";
-import { areaContains, bearingAndDistanceToLatLng, deg2rad, getGroundElevation, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
+import {
+ areaContains,
+ bearingAndDistanceToLatLng,
+ deepCopyTable,
+ deg2rad,
+ getGroundElevation,
+ latLngToMercator,
+ mToFt,
+ mercatorToLatLng,
+ msToKnots,
+} from "../other/utils";
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, OlympusState, UnitControlSubState } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
@@ -66,13 +76,15 @@ export class UnitsManager {
SessionDataLoadedEvent.on((sessionData) => {
UnitsRefreshed.on(() => {
- const localSessionData = JSON.parse(JSON.stringify(sessionData));
- Object.keys(localSessionData.hotgroups).forEach((hotgroup) => {
- localSessionData.hotgroups[hotgroup].forEach((ID) => {
- let unit = this.getUnitByID(ID);
- if (unit) this.addToHotgroup(Number(hotgroup), [unit]);
+ const localSessionData = deepCopyTable(sessionData);
+ if (localSessionData.hotgroups) {
+ Object.keys(localSessionData.hotgroups).forEach((hotgroup) => {
+ localSessionData.hotgroups[hotgroup].forEach((ID) => {
+ let unit = this.getUnitByID(ID);
+ if (unit) this.addToHotgroup(Number(hotgroup), [unit]);
+ });
});
- });
+ }
}, true);
});