From 5797b9d2093b45381972ec9768e2089a13a1abcb Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Fri, 20 Dec 2024 15:43:54 +0100 Subject: [PATCH] feat: added starred spawns to session data --- frontend/react/src/interfaces.ts | 3 +- .../map/coalitionarea/coalitionareamanager.ts | 4 +- frontend/react/src/map/map.ts | 11 ++++- .../src/map/markers/stylesheets/units.css | 4 +- frontend/react/src/other/utils.ts | 9 ++++ frontend/react/src/sessiondata.ts | 6 +++ frontend/react/src/ui/panels/drawingmenu.tsx | 13 +++--- .../react/src/ui/panels/unitcontrolmenu.tsx | 42 +++++++++---------- .../react/src/ui/panels/unitspawnmenu.tsx | 8 ++-- frontend/react/src/unit/unitsmanager.ts | 26 ++++++++---- 10 files changed, 82 insertions(+), 44 deletions(-) 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); });