diff --git a/frontend/react/src/eventscontext.tsx b/frontend/react/src/eventscontext.tsx
index 52453b28..fdf399d8 100644
--- a/frontend/react/src/eventscontext.tsx
+++ b/frontend/react/src/eventscontext.tsx
@@ -7,12 +7,14 @@ export const EventsContext = createContext({
setMeasureMenuVisible: (e: boolean) => {},
setDrawingMenuVisible: (e: boolean) => {},
setOptionsMenuVisible: (e: boolean) => {},
+ setAirbaseMenuVisible: (e: boolean) => {},
toggleMainMenuVisible: () => {},
toggleSpawnMenuVisible: () => {},
toggleUnitControlMenuVisible: () => {},
toggleMeasureMenuVisible: () => {},
toggleDrawingMenuVisible: () => {},
toggleOptionsMenuVisible: () => {},
+ toggleAirbaseMenuVisible: () => {},
});
export const EventsProvider = EventsContext.Provider;
diff --git a/frontend/react/src/mission/airbase.ts b/frontend/react/src/mission/airbase.ts
index 5894558f..0c214126 100644
--- a/frontend/react/src/mission/airbase.ts
+++ b/frontend/react/src/mission/airbase.ts
@@ -12,7 +12,6 @@ export class Airbase extends CustomMarker {
runways: [],
};
#coalition: string = "";
- #hasChartDataBeenSet: boolean = false;
#properties: string[] = [];
#parkings: string[] = [];
@@ -22,10 +21,6 @@ export class Airbase extends CustomMarker {
this.#name = options.name;
}
- chartDataHasBeenSet() {
- return this.#hasChartDataBeenSet;
- }
-
createIcon() {
var icon = new DivIcon({
className: "leaflet-airbase-marker",
@@ -43,10 +38,10 @@ export class Airbase extends CustomMarker {
el.appendChild(img);
this.getElement()?.appendChild(el);
el.addEventListener("mouseover", (ev) => {
- document.dispatchEvent(new CustomEvent("airbaseMouseover", { detail: this }));
+ document.dispatchEvent(new CustomEvent("airbasemouseover", { detail: this }));
});
el.addEventListener("mouseout", (ev) => {
- document.dispatchEvent(new CustomEvent("airbaseMouseout", { detail: this }));
+ document.dispatchEvent(new CustomEvent("airbasemouseout", { detail: this }));
});
el.dataset.coalition = this.#coalition;
}
@@ -73,7 +68,6 @@ export class Airbase extends CustomMarker {
}
setChartData(chartData: AirbaseChartData) {
- this.#hasChartDataBeenSet = true;
this.#chartData = chartData;
}
diff --git a/frontend/react/src/mission/missionmanager.ts b/frontend/react/src/mission/missionmanager.ts
index 68653367..f36b23d6 100644
--- a/frontend/react/src/mission/missionmanager.ts
+++ b/frontend/react/src/mission/missionmanager.ts
@@ -87,7 +87,7 @@ export class MissionManager {
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign,
}).addTo(getApp().getMap());
- this.#airbases[airbase.callsign].on("contextmenu", (e) => this.#onAirbaseClick(e));
+ this.#airbases[airbase.callsign].on("click", (e) => this.#onAirbaseClick(e));
this.#loadAirbaseChartData(airbase.callsign);
}
@@ -316,7 +316,9 @@ export class MissionManager {
if (requestRefresh) getApp().getServerManager().refreshAll();
}
- #onAirbaseClick(e: any) {}
+ #onAirbaseClick(ev: any) {
+ document.dispatchEvent(new CustomEvent("airbaseclick", { detail: ev.target }));
+ }
#loadAirbaseChartData(callsign: string) {
if (!this.#theatre) {
@@ -324,7 +326,7 @@ export class MissionManager {
}
var xhr = new XMLHttpRequest();
- xhr.open("GET", `api/airbases/${this.#theatre.toLowerCase()}/${callsign}`, true);
+ xhr.open("GET", window.location.href.split("?")[0].replace("vite/", "") + `api/airbases/${this.#theatre.toLowerCase()}/${callsign}`, true);
xhr.responseType = "json";
xhr.onload = () => {
var status = xhr.status;
diff --git a/frontend/react/src/server/servermanager.ts b/frontend/react/src/server/servermanager.ts
index d6779888..108a52ea 100644
--- a/frontend/react/src/server/servermanager.ts
+++ b/frontend/react/src/server/servermanager.ts
@@ -142,8 +142,6 @@ export class ServerManager {
setAddress(address: string) {
this.#REST_ADDRESS = `${address.replace("vite/", "").replace("vite", "")}olympus`;
- // TODO: TEMPORARY FOR DEBUGGING
- // this.#REST_ADDRESS = `https://refugees.dcsolympus.com/olympus`;
console.log(`Setting REST address to ${this.#REST_ADDRESS}`);
}
diff --git a/frontend/react/src/statecontext.tsx b/frontend/react/src/statecontext.tsx
index 9281bf2f..5c2d9577 100644
--- a/frontend/react/src/statecontext.tsx
+++ b/frontend/react/src/statecontext.tsx
@@ -8,6 +8,7 @@ export const StateContext = createContext({
measureMenuVisible: false,
drawingMenuVisible: false,
optionsMenuVisible: false,
+ airbaseMenuVisible: false,
mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS,
mapOptions: MAP_OPTIONS_DEFAULTS,
mapSources: [] as string[],
diff --git a/frontend/react/src/ui/components/olaccordion.tsx b/frontend/react/src/ui/components/olaccordion.tsx
index eda36fb6..b855ce90 100644
--- a/frontend/react/src/ui/components/olaccordion.tsx
+++ b/frontend/react/src/ui/components/olaccordion.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
-export function OlAccordion(props: { title: string; children?: JSX.Element | JSX.Element[]; showArrows?: boolean }) {
+export function OlAccordion(props: { title: string; children?: JSX.Element | JSX.Element[]; showArrows?: boolean; className?: string }) {
const [open, setOpen] = useState(false);
const [scrolledUp, setScrolledUp] = useState(true);
const [scrolledDown, setScrolledDown] = useState(false);
@@ -31,6 +31,7 @@ export function OlAccordion(props: { title: string; children?: JSX.Element | JSX
type="button"
onClick={() => setOpen(!open)}
className={`
+ ${props.className ?? ""}
flex w-full items-center justify-between gap-3 border-gray-200 py-2
text-gray-700
dark:border-gray-700 dark:text-white
diff --git a/frontend/react/src/ui/panels/airbasemenu.tsx b/frontend/react/src/ui/panels/airbasemenu.tsx
new file mode 100644
index 00000000..05224ebf
--- /dev/null
+++ b/frontend/react/src/ui/panels/airbasemenu.tsx
@@ -0,0 +1,141 @@
+import React, { useState } from "react";
+import { Menu } from "./components/menu";
+import { OlCheckbox } from "../components/olcheckbox";
+import { OlRangeSlider } from "../components/olrangeslider";
+import { OlNumberInput } from "../components/olnumberinput";
+import { Coalition, MapOptions } from "../../types/types";
+import { getApp } from "../../olympusapp";
+import { Airbase } from "../../mission/airbase";
+import { FaArrowLeft, FaCompass } from "react-icons/fa6";
+import { getUnitsByLabel } from "../../other/utils";
+import { UnitBlueprint } from "../../interfaces";
+import { IDLE } from "../../constants/constants";
+import { OlSearchBar } from "../components/olsearchbar";
+import { OlAccordion } from "../components/olaccordion";
+import { OlUnitEntryList } from "../components/olunitlistentry";
+import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
+import { UnitSpawnMenu } from "./unitspawnmenu";
+
+export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase: Airbase | null; children?: JSX.Element | JSX.Element[] }) {
+ const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
+ const [filterString, setFilterString] = useState("");
+
+ const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = getUnitsByLabel(filterString);
+
+ return (
+
+ );
+}
diff --git a/frontend/react/src/ui/panels/controls.tsx b/frontend/react/src/ui/panels/controlspanel.tsx
similarity index 100%
rename from frontend/react/src/ui/panels/controls.tsx
rename to frontend/react/src/ui/panels/controlspanel.tsx
diff --git a/frontend/react/src/ui/panels/sidebar.tsx b/frontend/react/src/ui/panels/sidebar.tsx
index 58e856ca..a4642cf7 100644
--- a/frontend/react/src/ui/panels/sidebar.tsx
+++ b/frontend/react/src/ui/panels/sidebar.tsx
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { OlStateButton } from "../components/olstatebutton";
-import { faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
+import { faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faMagnifyingGlass, faPlaneDeparture } from "@fortawesome/free-solid-svg-icons";
import { EventsConsumer } from "../../eventscontext";
import { StateConsumer } from "../../statecontext";
import { IDLE } from "../../constants/constants";
@@ -52,6 +52,12 @@ export function SideBar() {
icon={faPencil}
tooltip="Hide/show drawing menu"
>
+
diff --git a/frontend/react/src/ui/panels/spawnmenu.tsx b/frontend/react/src/ui/panels/spawnmenu.tsx
index a08cd847..7df3b1ca 100644
--- a/frontend/react/src/ui/panels/spawnmenu.tsx
+++ b/frontend/react/src/ui/panels/spawnmenu.tsx
@@ -1,7 +1,5 @@
import React, { useState, useEffect } from "react";
import { Menu } from "./components/menu";
-import { faPlus } from "@fortawesome/free-solid-svg-icons";
-import { library } from "@fortawesome/fontawesome-svg-core";
import { OlSearchBar } from "../components/olsearchbar";
import { OlAccordion } from "../components/olaccordion";
import { getApp } from "../../olympusapp";
@@ -18,8 +16,6 @@ import {
import { IDLE, SPAWN_UNIT } from "../../constants/constants";
import { getUnitsByLabel } from "../../other/utils";
-library.add(faPlus);
-
export function SpawnMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
const [filterString, setFilterString] = useState("");
@@ -107,7 +103,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
)}
- {!(blueprint === null) && }
+ {!(blueprint === null) && }
>
);
diff --git a/frontend/react/src/ui/panels/unitspawnmenu.tsx b/frontend/react/src/ui/panels/unitspawnmenu.tsx
index 318d47ad..65c91273 100644
--- a/frontend/react/src/ui/panels/unitspawnmenu.tsx
+++ b/frontend/react/src/ui/panels/unitspawnmenu.tsx
@@ -11,8 +11,9 @@ import { getApp } from "../../olympusapp";
import { IDLE, SPAWN_UNIT } from "../../constants/constants";
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
import { LatLng } from "leaflet";
+import { Airbase } from "../../mission/airbase";
-export function UnitSpawnMenu(props: { blueprint: UnitBlueprint }) {
+export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation: boolean; airbase?: Airbase | null; coalition?: Coalition }) {
/* Compute the min and max values depending on the unit type */
const minNumber = 1;
const maxNumber = 4;
@@ -30,31 +31,60 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint }) {
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
- if (props.blueprint !== null) {
- getApp()
- ?.getMap()
- ?.setState(SPAWN_UNIT, {
- spawnRequestTable: {
- category: getUnitCategoryByBlueprint(props.blueprint),
- unit: {
- unitType: props.blueprint.name,
- location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
- skill: "High",
- liveryID: "",
- altitude: ftToM(spawnAltitude),
- loadout:
- props.blueprint.loadouts?.find((loadout) => {
- return loadout.name === spawnLoadoutName;
- })?.code ?? "",
+ if (props.coalition && props.coalition !== spawnCoalition) {
+ setSpawnCoalition(props.coalition);
+ }
+ if (props.spawnAtLocation) {
+ if (props.blueprint !== null) {
+ getApp()
+ ?.getMap()
+ ?.setState(SPAWN_UNIT, {
+ spawnRequestTable: {
+ category: getUnitCategoryByBlueprint(props.blueprint),
+ unit: {
+ unitType: props.blueprint.name,
+ location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
+ skill: "High",
+ liveryID: "",
+ altitude: ftToM(spawnAltitude),
+ loadout:
+ props.blueprint.loadouts?.find((loadout) => {
+ return loadout.name === spawnLoadoutName;
+ })?.code ?? "",
+ },
+ coalition: spawnCoalition,
},
- coalition: spawnCoalition,
- },
- });
- } else {
- if (getApp()?.getMap()?.getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
+ });
+ } else {
+ if (getApp()?.getMap()?.getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
+ }
}
});
+ function spawnAtAirbase() {
+ getApp()
+ .getUnitsManager()
+ .spawnUnits(
+ getUnitCategoryByBlueprint(props.blueprint),
+ [
+ {
+ unitType: props.blueprint.name,
+ location: new LatLng(0, 0), // Not relevant spawning at airbase
+ skill: "High",
+ liveryID: "",
+ altitude: 0,
+ loadout:
+ props.blueprint.loadouts?.find((loadout) => {
+ return loadout.name === spawnLoadoutName;
+ })?.code ?? "",
+ },
+ ],
+ props.coalition,
+ false,
+ props.airbase?.getName()
+ );
+ }
+
/* Get a list of all the roles */
const roles: string[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
@@ -87,14 +117,16 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint }) {
inline-flex w-full flex-row content-center justify-between
`}
>
- {
- spawnCoalition === "blue" && setSpawnCoalition("neutral");
- spawnCoalition === "neutral" && setSpawnCoalition("red");
- spawnCoalition === "red" && setSpawnCoalition("blue");
- }}
- />
+ {!props.coalition && (
+ {
+ spawnCoalition === "blue" && setSpawnCoalition("neutral");
+ spawnCoalition === "neutral" && setSpawnCoalition("red");
+ spawnCoalition === "red" && setSpawnCoalition("blue");
+ }}
+ />
+ )}
-
- {spawnLoadout &&
- spawnLoadout.items.map((item) => {
+ {spawnLoadout && spawnLoadout.items.length > 0 && (
+
+ {spawnLoadout.items.map((item) => {
return (
+ )}
+ {!props.spawnAtLocation && (
+
+ )}
);
}
diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx
index c8af5f23..df013bc2 100644
--- a/frontend/react/src/ui/ui.tsx
+++ b/frontend/react/src/ui/ui.tsx
@@ -18,16 +18,19 @@ import { sha256 } from "js-sha256";
import { MiniMapPanel } from "./panels/minimappanel";
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
import { DrawingMenu } from "./panels/drawingmenu";
-import { ControlsPanel } from "./panels/controls";
+import { ControlsPanel } from "./panels/controlspanel";
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
+import { AirbaseMenu } from "./panels/airbasemenu";
+import { Airbase } from "../mission/airbase";
-export type OlympusState = {
+export type OlympusUIState = {
mainMenuVisible: boolean;
spawnMenuVisible: boolean;
unitControlMenuVisible: boolean;
measureMenuVisible: boolean;
drawingMenuVisible: boolean;
optionsMenuVisible: boolean;
+ airbaseMenuVisible: boolean;
mapHiddenTypes: MapHiddenTypes;
mapOptions: MapOptions;
};
@@ -40,6 +43,7 @@ export function UI() {
const [measureMenuVisible, setMeasureMenuVisible] = useState(false);
const [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
const [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
+ const [airbaseMenuVisible, setAirbaseMenuVisible] = useState(false);
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
const [checkingPassword, setCheckingPassword] = useState(false);
@@ -48,6 +52,7 @@ export function UI() {
const [mapSources, setMapSources] = useState([] as string[]);
const [activeMapSource, setActiveMapSource] = useState("");
const [mapState, setMapState] = useState(IDLE);
+ const [airbase, setAirbase] = useState(null as null | Airbase);
useEffect(() => {
document.addEventListener("hiddenTypesChanged", (ev) => {
@@ -75,6 +80,13 @@ export function UI() {
setMapSources(sources);
setActiveMapSource(sources[0]);
});
+
+ document.addEventListener("airbaseclick", (ev) => {
+ hideAllMenus();
+ getApp().getMap().setState(IDLE);
+ setAirbase((ev as CustomEvent).detail);
+ setAirbaseMenuVisible(true);
+ });
}, []);
function hideAllMenus() {
@@ -84,6 +96,7 @@ export function UI() {
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
setOptionsMenuVisible(false);
+ setAirbaseMenuVisible(false);
}
function checkPassword(password: string) {
@@ -139,6 +152,7 @@ export function UI() {
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible,
optionsMenuVisible: optionsMenuVisible,
+ airbaseMenuVisible: airbaseMenuVisible,
mapOptions: mapOptions,
mapHiddenTypes: mapHiddenTypes,
mapSources: mapSources,
@@ -154,6 +168,7 @@ export function UI() {
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
setOptionsMenuVisible: setOptionsMenuVisible,
+ setAirbaseMenuVisible: setAirbaseMenuVisible,
toggleMainMenuVisible: () => {
hideAllMenus();
setMainMenuVisible(!mainMenuVisible);
@@ -178,6 +193,10 @@ export function UI() {
hideAllMenus();
setOptionsMenuVisible(!optionsMenuVisible);
},
+ toggleAirbaseMenuVisible: () => {
+ hideAllMenus();
+ setAirbaseMenuVisible(!airbaseMenuVisible);
+ },
}}
>
@@ -211,6 +230,7 @@ export function UI() {
setOptionsMenuVisible(false)} options={mapOptions} />
setUnitControlMenuVisible(false)} />
setDrawingMenuVisible(false)} />
+ setAirbaseMenuVisible(false)} airbase={airbase}/>