diff --git a/client/demo.js b/client/demo.js index 0860c08a..f344e58f 100644 --- a/client/demo.js +++ b/client/demo.js @@ -535,24 +535,49 @@ class DemoDataGenerator { }; airbases(req, res){ - var ret = {airbases: {}}; + var ret = {airbases: { + ["0"]: { + callsign: "Neutral", + lat: 37.3, + lng: -115.8, + coalition: "neutral" + }, + ["1"]: { + callsign: "Red", + lat: 37.3, + lng: -115.75, + coalition: "red" + }, + ["2"]: { + callsign: "Blue", + lat: 37.3, + lng: -115.7, + coalition: "blue" + } + }}; res.send(JSON.stringify(ret)); }; bullseyes(req, res){ - var ret = {bullseyes: {}}; + var ret = {bullseyes: { + "0": { + lat: 37.25, + lng: -115.8 + }, + "1": { + lat: 37.25, + lng: -115.75 + }, + "2": { + lat: 37.25, + lng: -115.7 + } + }}; res.send(JSON.stringify(ret)); }; generateRandomUnitsDemoData(unitsNumber) { - //var units = {}; - //for (let i = 0; i < unitsNumber; i++) - //{ - // units[String(i)] = JSON.parse(JSON.stringify(DEMO_UNIT_DATA)); - // units[String(i)].flightData.latitude += (Math.random() - 0.5) * 0.3; - // units[String(i)].flightData.longitude += (Math.random() - 0.5) * 0.3; - //} return {"units": DEMO_UNIT_DATA}; } } diff --git a/client/public/stylesheets/airbases.css b/client/public/stylesheets/airbases.css index eedff840..5ded6a48 100644 --- a/client/public/stylesheets/airbases.css +++ b/client/public/stylesheets/airbases.css @@ -1,41 +1,44 @@ -.airbase-marker-container { - height: 60px; - width: 60px; - left: -30px; - top: -30px; - border: 1px transparent solid; +:root { + --airbase-marker-height: 63px; + --airbase-marker-width: 63px; +} + +[data-object|="airbase"] { + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + position: relative; +} + +[data-hide-airbase] #map-container [data-object|="airbase"] { + display: none; +} + +/****************************** +Marker +******************************/ + +[data-object|="airbase"] .airbase { + background-color: transparent; + background-repeat: no-repeat; + background-size: cover; position: absolute; + transform-origin: center; + z-index: 3; } -.airbase-marker-image { - height: 60px; - width: 60px; - left: 0px; - top: 0px; - display: block; - position: absolute; - filter: drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white); - opacity: 0.8; +/* Airbase */ +[data-object|="airbase"] .airbase-marker { + background-image: var(--airbase-marker-neutral-url); + height: var(--airbase-marker-height); + width: var(--airbase-marker-width); } -.blue.airbase-marker-image { - filter: invert(40%) sepia(94%) saturate(2477%) hue-rotate(197deg) brightness(92%) contrast(91%) drop-shadow(1px 1px #FFFA) drop-shadow(1px -1px #FFFA) drop-shadow(-1px 1px 0px #FFFA) drop-shadow(-1px -1px #FFFA); +[data-object|="airbase"][data-coalition="red"] .airbase-marker { + background-image: var(--airbase-marker-red-url); } -.red.airbase-marker-image { - filter:invert(32%) sepia(91%) saturate(5128%) hue-rotate(349deg) brightness(97%) contrast(97%) drop-shadow(1px 1px #FFFA) drop-shadow(1px -1px #FFFA) drop-shadow(-1px 1px 0px #FFFA) drop-shadow(-1px -1px #FFFA); -} - -.neutral.airbase-marker-image { - filter: invert(71%) sepia(12%) saturate(9%) hue-rotate(319deg) brightness(92%) contrast(96%) drop-shadow(1px 1px #000A) drop-shadow(1px -1px #000A) drop-shadow(-1px 1px 0px #000A) drop-shadow(-1px -1px #000A); -} - -.airbase-marker-name { - bottom: -20px; - position: absolute; - text-align: center; - font: 800 14px Arial; - white-space: nowrap; - -webkit-text-fill-color: white; - -webkit-text-stroke: 1px; -} +[data-object|="airbase"][data-coalition="blue"] .airbase-marker { + background-image: var(--airbase-marker-blue-url); +} \ No newline at end of file diff --git a/client/public/stylesheets/contextmenu.css b/client/public/stylesheets/contextmenus.css similarity index 87% rename from client/public/stylesheets/contextmenu.css rename to client/public/stylesheets/contextmenus.css index e29a39f9..5c41b05c 100644 --- a/client/public/stylesheets/contextmenu.css +++ b/client/public/stylesheets/contextmenus.css @@ -1,8 +1,5 @@ -#contextmenu { +#map-contextmenu { position: absolute; -} - -#contextmenu { display: flex; flex-direction: column; row-gap: 5px; @@ -38,7 +35,7 @@ margin-right: 10px; } -#contextmenu>div:nth-child(2){ +#map-contextmenu>div:nth-child(2){ display: flex; flex-direction: row; justify-content: space-between; @@ -46,22 +43,22 @@ padding-right: 0px; } -#contextmenu>ul{ +#map-contextmenu>ul{ max-height: 200px; overflow-x: hidden; overflow-y: auto; } -#contextmenu .ol-panel { +#map-contextmenu .ol-panel { width: 100%; border-radius: var(--border-radius-sm); } -#contextmenu ul { +#map-contextmenu ul { margin: 0px; } -#contextmenu>div:nth-child(n+3){ +#map-contextmenu>div:nth-child(n+3){ display: flex; flex-direction: column; justify-content: space-between; @@ -69,7 +66,7 @@ row-gap: 5px; } -#contextmenu .ol-select-container{ +#map-contextmenu .ol-select-container{ width: 100%; flex:0 0 auto; align-self: stretch; @@ -205,4 +202,24 @@ [data-smoke-color="green"]::before{ background-color: green; } [data-smoke-color="orange"]::before{ background-color: orange; } +/* Unit context menu */ +#unit-contextmenu { + position: absolute; + display: flex; + flex-direction: column; + row-gap: 5px; + width: 150px; + height: fit-content; + z-index: 1000; +} +/* Airbase context menu */ +#airbase-contextmenu { + position: absolute; + display: flex; + flex-direction: column; + row-gap: 5px; + width: 230px; + height: fit-content; + z-index: 1000; +} diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index e22b7d82..597d8ab6 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -1,6 +1,6 @@ @import url("layout.css"); @import url("airbases.css"); -@import url("contextmenu.css"); +@import url("contextmenus.css"); @import url("units.css"); /* Variables definitions */ diff --git a/client/public/themes/olympus/images/icon_airbase_blue.svg b/client/public/themes/olympus/images/icon_airbase_blue.svg new file mode 100644 index 00000000..0800974c --- /dev/null +++ b/client/public/themes/olympus/images/icon_airbase_blue.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/public/themes/olympus/images/icon_airbase_neutral.svg b/client/public/themes/olympus/images/icon_airbase_neutral.svg new file mode 100644 index 00000000..69713bd5 --- /dev/null +++ b/client/public/themes/olympus/images/icon_airbase_neutral.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/public/themes/olympus/images/icon_airbase_red.svg b/client/public/themes/olympus/images/icon_airbase_red.svg new file mode 100644 index 00000000..45d55abd --- /dev/null +++ b/client/public/themes/olympus/images/icon_airbase_red.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css index 741a00bb..48d9ca93 100644 --- a/client/public/themes/olympus/olympus.css +++ b/client/public/themes/olympus/olympus.css @@ -206,4 +206,12 @@ --spawn-aircraft-url: url( "/themes/olympus/images/spawn_aircraft.svg" ); --spawn-ground-url: url( "/themes/olympus/images/spawn_ground.svg" ); --spawn-smoke-url: url( "/themes/olympus/images/spawn_smoke.svg" ); + + /*** Airbase ***/ + --airbase-marker-height: 63px; + --airbase-marker-width: 63px; + + --airbase-marker-blue-url: url( "/themes/olympus/images/icon_airbase_blue.svg" ); + --airbase-marker-neutral-url: url( "/themes/olympus/images/icon_airbase_neutral.svg" ); + --airbase-marker-red-url: url( "/themes/olympus/images/icon_airbase_red.svg" ); } \ No newline at end of file diff --git a/client/src/controls/airbasecontextmenu.ts b/client/src/controls/airbasecontextmenu.ts new file mode 100644 index 00000000..f5dae337 --- /dev/null +++ b/client/src/controls/airbasecontextmenu.ts @@ -0,0 +1,9 @@ +import { ContextMenu } from "./contextmenu"; + +export class AirbaseContextMenu extends ContextMenu { + constructor(id: string) + { + super(id); + + } +} \ No newline at end of file diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index 0eca9462..e0cbf644 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -1,74 +1,16 @@ import { LatLng } from "leaflet"; -import { getActiveCoalition, setActiveCoalition } from ".."; -import { ContextMenuOption } from "../@types/dom"; -import { ClickEvent } from "../map/map"; -import { spawnAircraft, spawnGroundUnit } from "../server/server"; -import { aircraftDatabase } from "../units/aircraftdatabase"; -import { groundUnitsDatabase } from "../units/groundunitsdatabase"; -import { Dropdown } from "./dropdown"; - -export interface SpawnOptions { - role: string; - type: string; - latlng: LatLng; - coalition: string; - loadout: string | null; - airbaseName: string | null; -} export class ContextMenu { #container: HTMLElement | null; #latlng: LatLng = new LatLng(0, 0); - #aircraftRoleDropdown: Dropdown; - #aircraftTypeDropdown: Dropdown; - #aircraftLoadoutDropdown: Dropdown; - #groundUnitRoleDropdown: Dropdown; - #groundUnitTypeDropdown: Dropdown; - #spawnOptions: SpawnOptions = {role: "", type: "", latlng: this.#latlng, loadout: null, coalition: "blue", airbaseName: null}; - constructor(id: string,) { + constructor(id: string) { this.#container = document.getElementById(id); - this.#container?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e)); - - this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role)); - this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type)); - this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout)); - this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role)); - this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type)); - - document.addEventListener("contextMenuShow", (e: any) => { - this.#container?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", e.detail.type !== "aircraft"); - this.#container?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", e.detail.type === "aircraft"); - this.#container?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", e.detail.type !== "ground-unit"); - this.#container?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", e.detail.type === "ground-unit"); - this.#container?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", e.detail.type !== "smoke"); - this.#container?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", e.detail.type === "smoke"); - - this.#resetAircraftRole(); - this.#resetAircraftType(); - this.#resetGroundUnitRole(); - this.#resetGroundUnitType(); - }) - - document.addEventListener("contextMenuDeployAircraft", () => { - this.hide(); - this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - spawnAircraft(this.#spawnOptions); - }) - - document.addEventListener("contextMenuDeployGroundUnit", () => { - this.hide(); - this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - spawnGroundUnit(this.#spawnOptions); - }) - this.hide(); } show(x: number, y: number, latlng: LatLng) { - this.#spawnOptions.latlng = latlng; + this.#latlng = latlng; this.#container?.classList.toggle("hide", false); if (this.#container != null) { if (x + this.#container.offsetWidth < window.innerWidth) @@ -87,117 +29,13 @@ export class ContextMenu { this.#container?.classList.toggle("hide", true); } - #onSwitch(e: any) { - if (this.#container != null) { - if (e.srcElement.checked) - setActiveCoalition("red"); - else - setActiveCoalition("blue"); - } - } - - /********* Aircraft spawn menu *********/ - #setAircraftRole(role: string) + getContainer() { - if (this.#spawnOptions != null) - { - this.#spawnOptions.role = role; - this.#resetAircraftRole(); - this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role)); - this.#aircraftTypeDropdown.selectValue(0); - } + return this.#container; } - #resetAircraftRole() { - (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; - (this.#container?.querySelector("#loadout-list")).replaceChildren(); - this.#aircraftRoleDropdown.reset(); - this.#aircraftTypeDropdown.reset(); - this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles()); - } - - #setAircraftType(label: string) + getLatLng() { - if (this.#spawnOptions != null) - { - this.#resetAircraftType(); - var type = aircraftDatabase.getNameByLabel(label); - if (type != null) - { - this.#spawnOptions.type = type; - this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role)); - this.#aircraftLoadoutDropdown.selectValue(0); - var image = (this.#container?.querySelector("#unit-image")); - image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`; - image.classList.toggle("hide", false); - } - } - } - - #resetAircraftType() { - (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; - (this.#container?.querySelector("#loadout-list")).replaceChildren(); - this.#aircraftLoadoutDropdown.reset(); - (this.#container?.querySelector("#unit-image")).classList.toggle("hide", true); - } - - #setAircraftLoadout(loadoutName: string) - { - if (this.#spawnOptions != null) - { - var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName); - if (loadout) - { - this.#spawnOptions.loadout = loadout.code; - (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; - var items = loadout.items.map((item: any) => {return `${item.quantity}x ${item.name}`;}); - items.length == 0? items.push("Empty loadout"): ""; - (this.#container?.querySelector("#loadout-list")).replaceChildren( - ...items.map((item: any) => { - var div = document.createElement('div'); - div.innerText = item; - return div; - }) - ) - } - } - } - - /********* Ground unit spawn menu *********/ - #setGroundUnitRole(role: string) - { - if (this.#spawnOptions != null) - { - this.#spawnOptions.role = role; - this.#resetGroundUnitRole(); - this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getLabelsByRole(role)); - this.#groundUnitTypeDropdown.selectValue(0); - } - } - - #resetGroundUnitRole() { - (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; - (this.#container?.querySelector("#loadout-list")).replaceChildren(); - this.#groundUnitRoleDropdown.reset(); - this.#groundUnitTypeDropdown.reset(); - this.#groundUnitRoleDropdown.setOptions(groundUnitsDatabase.getRoles()); - } - - #setGroundUnitType(label: string) - { - if (this.#spawnOptions != null) - { - this.#resetGroundUnitType(); - var type = groundUnitsDatabase.getNameByLabel(label); - if (type != null) - { - this.#spawnOptions.type = type; - (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; - } - } - } - - #resetGroundUnitType() { - (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; + return this.#latlng; } } \ No newline at end of file diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts new file mode 100644 index 00000000..fea0bc64 --- /dev/null +++ b/client/src/controls/mapcontextmenu.ts @@ -0,0 +1,185 @@ +import { LatLng } from "leaflet"; +import { getActiveCoalition, setActiveCoalition } from ".."; +import { spawnAircraft, spawnGroundUnit } from "../server/server"; +import { aircraftDatabase } from "../units/aircraftdatabase"; +import { groundUnitsDatabase } from "../units/groundunitsdatabase"; +import { ContextMenu } from "./contextmenu"; +import { Dropdown } from "./dropdown"; + +export interface SpawnOptions { + role: string; + type: string; + latlng: LatLng; + coalition: string; + loadout: string | null; + airbaseName: string | null; +} + +export class MapContextMenu extends ContextMenu { + #aircraftRoleDropdown: Dropdown; + #aircraftTypeDropdown: Dropdown; + #aircraftLoadoutDropdown: Dropdown; + #groundUnitRoleDropdown: Dropdown; + #groundUnitTypeDropdown: Dropdown; + #spawnOptions: SpawnOptions = {role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null}; + + constructor(id: string) { + super(id); + this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e)); + + this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role)); + this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type)); + this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout)); + this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role)); + this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type)); + + document.addEventListener("contextMenuShow", (e: any) => { + this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", e.detail.type !== "aircraft"); + this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", e.detail.type === "aircraft"); + this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", e.detail.type !== "ground-unit"); + this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", e.detail.type === "ground-unit"); + this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", e.detail.type !== "smoke"); + this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", e.detail.type === "smoke"); + + this.#resetAircraftRole(); + this.#resetAircraftType(); + this.#resetGroundUnitRole(); + this.#resetGroundUnitType(); + }) + + document.addEventListener("contextMenuDeployAircraft", () => { + this.hide(); + this.#spawnOptions.coalition = getActiveCoalition(); + if (this.#spawnOptions) + spawnAircraft(this.#spawnOptions); + }) + + document.addEventListener("contextMenuDeployGroundUnit", () => { + this.hide(); + this.#spawnOptions.coalition = getActiveCoalition(); + if (this.#spawnOptions) + spawnGroundUnit(this.#spawnOptions); + }) + + this.hide(); + } + + show(x: number, y: number, latlng: LatLng) { + super.show(x, y, latlng); + this.#spawnOptions.latlng = latlng; + } + + #onSwitch(e: any) { + if (this.getContainer() != null) { + if (e.srcElement.checked) + setActiveCoalition("red"); + else + setActiveCoalition("blue"); + } + } + + /********* Aircraft spawn menu *********/ + #setAircraftRole(role: string) + { + if (this.#spawnOptions != null) + { + this.#spawnOptions.role = role; + this.#resetAircraftRole(); + this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role)); + this.#aircraftTypeDropdown.selectValue(0); + } + } + + #resetAircraftRole() { + (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; + (this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); + this.#aircraftRoleDropdown.reset(); + this.#aircraftTypeDropdown.reset(); + this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles()); + } + + #setAircraftType(label: string) + { + if (this.#spawnOptions != null) + { + this.#resetAircraftType(); + var type = aircraftDatabase.getNameByLabel(label); + if (type != null) + { + this.#spawnOptions.type = type; + this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role)); + this.#aircraftLoadoutDropdown.selectValue(0); + var image = (this.getContainer()?.querySelector("#unit-image")); + image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`; + image.classList.toggle("hide", false); + } + } + } + + #resetAircraftType() { + (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; + (this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); + this.#aircraftLoadoutDropdown.reset(); + (this.getContainer()?.querySelector("#unit-image")).classList.toggle("hide", true); + } + + #setAircraftLoadout(loadoutName: string) + { + if (this.#spawnOptions != null) + { + var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName); + if (loadout) + { + this.#spawnOptions.loadout = loadout.code; + (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; + var items = loadout.items.map((item: any) => {return `${item.quantity}x ${item.name}`;}); + items.length == 0? items.push("Empty loadout"): ""; + (this.getContainer()?.querySelector("#loadout-list")).replaceChildren( + ...items.map((item: any) => { + var div = document.createElement('div'); + div.innerText = item; + return div; + }) + ) + } + } + } + + /********* Ground unit spawn menu *********/ + #setGroundUnitRole(role: string) + { + if (this.#spawnOptions != null) + { + this.#spawnOptions.role = role; + this.#resetGroundUnitRole(); + this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getLabelsByRole(role)); + this.#groundUnitTypeDropdown.selectValue(0); + } + } + + #resetGroundUnitRole() { + (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; + (this.getContainer()?.querySelector("#loadout-list")).replaceChildren(); + this.#groundUnitRoleDropdown.reset(); + this.#groundUnitTypeDropdown.reset(); + this.#groundUnitRoleDropdown.setOptions(groundUnitsDatabase.getRoles()); + } + + #setGroundUnitType(label: string) + { + if (this.#spawnOptions != null) + { + this.#resetGroundUnitType(); + var type = groundUnitsDatabase.getNameByLabel(label); + if (type != null) + { + this.#spawnOptions.type = type; + (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; + } + } + } + + #resetGroundUnitType() { + (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true; + } +} \ No newline at end of file diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts new file mode 100644 index 00000000..2579c3f0 --- /dev/null +++ b/client/src/controls/unitcontextmenu.ts @@ -0,0 +1,18 @@ +import { ContextMenu } from "./contextmenu"; + +export class UnitContextMenu extends ContextMenu { + constructor(id: string) { + super(id); + } + + setOptions(options: string[], callback: CallableFunction) + { + this.getContainer()?.replaceChildren(...options.map((option: string) => + { + var button = document.createElement("button"); + button.innerText = option; + button.addEventListener("click", () => callback(option)); + return (button); + })); + } +} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index 46893fe3..c0084e7f 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -1,7 +1,6 @@ import { Map } from "./map/map" import { UnitsManager } from "./units/unitsmanager"; import { UnitInfoPanel } from "./panels/unitinfopanel"; -import { ContextMenu } from "./controls/contextmenu"; import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; import { MissionHandler } from "./missionhandler/missionhandler"; import { UnitControlPanel } from "./panels/unitcontrolpanel"; @@ -13,7 +12,6 @@ import { LogPanel } from "./panels/logpanel"; import { getAirbases, getBulllseye as getBulllseyes, getUnits, toggleDemoEnabled } from "./server/server"; var map: Map; -var contextMenu: ContextMenu; var unitsManager: UnitsManager; var missionHandler: MissionHandler; @@ -43,9 +41,6 @@ function setup() { unitsManager = new UnitsManager(); missionHandler = new MissionHandler(); - /* Context menus */ - contextMenu = new ContextMenu("contextmenu"); - /* Panels */ unitInfoPanel = new UnitInfoPanel("unit-info-panel"); unitControlPanel = new UnitControlPanel("unit-control-panel"); @@ -204,10 +199,6 @@ export function getMissionData() { return missionHandler; } -export function getContextMenu() { - return contextMenu; -} - export function getUnitsManager() { return unitsManager; } diff --git a/client/src/map/map.ts b/client/src/map/map.ts index c7199ef7..0ebd8526 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -1,24 +1,15 @@ import * as L from "leaflet" -import { getContextMenu, getUnitsManager } from ".."; +import { getUnitsManager } from ".."; import { BoxSelect } from "./boxselect"; -import { SpawnOptions } from "../controls/contextmenu"; +import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu"; +import { UnitContextMenu } from "../controls/unitcontextmenu"; +import { AirbaseContextMenu } from "../controls/airbasecontextmenu"; export const IDLE = "IDLE"; export const MOVE_UNIT = "MOVE_UNIT"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); -export interface ClickEvent { - x: number; - y: number; - latlng: L.LatLng; -} - -export interface SpawnEvent extends ClickEvent { - airbaseName: string | null; - coalitionID: number | null; -} - export class Map extends L.Map { #state: string; #layer: L.TileLayer | null = null; @@ -26,6 +17,10 @@ export class Map extends L.Map { #leftClickTimer: number = 0; #lastMousePosition: L.Point = new L.Point(0, 0); + #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); + #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); + #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); + constructor(ID: string) { /* Init the leaflet map */ //@ts-ignore @@ -36,7 +31,6 @@ export class Map extends L.Map { /* Init the state machine */ this.#state = IDLE; - /* Register event handles */ this.on("click", (e: any) => this.#onClick(e)); @@ -112,19 +106,62 @@ export class Map extends L.Map { return this.#state; } - /* Context Menu */ - showContextMenu(e: any, spawnOptions: SpawnOptions | null = null) { + /* Context Menus */ + hideAllContextMenus() + { + this.hideMapContextMenu(); + this.hideUnitContextMenu(); + this.hideAirbaseContextMenu(); + } + + showMapContextMenu(e: any) { + this.hideAllContextMenus(); var x = e.originalEvent.x; var y = e.originalEvent.y; - getContextMenu()?.show(x, y, e.latlng); + this.#mapContextMenu.show(x, y, e.latlng); document.dispatchEvent(new CustomEvent("mapContextMenu")); } - hideContextMenu() { - getContextMenu()?.hide(); + hideMapContextMenu() { + this.#mapContextMenu.hide(); document.dispatchEvent(new CustomEvent("mapContextMenu")); } + getMapContextMenu(){ + return this.#mapContextMenu; + } + + showUnitContextMenu(e: any) { + this.hideAllContextMenus(); + var x = e.originalEvent.x; + var y = e.originalEvent.y; + this.#unitContextMenu.show(x, y, e.latlng); + } + + getUnitContextMenu(){ + return this.#unitContextMenu; + } + + hideUnitContextMenu() { + this.#unitContextMenu.hide(); + } + + showAirbaseContextMenu(e: any) { + this.hideAllContextMenus(); + var x = e.originalEvent.x; + var y = e.originalEvent.y; + this.#airbaseContextMenu.show(x, y, e.latlng); + } + + getAirbaseContextMenu(){ + return this.#airbaseContextMenu; + } + + hideAirbaseContextMenu() { + this.#airbaseContextMenu.hide(); + } + + /* Mouse coordinates */ getMousePosition() { return this.#lastMousePosition; } @@ -134,7 +171,7 @@ export class Map extends L.Map { } /* Spawn from air base */ - spawnFromAirbase(e: SpawnEvent) + spawnFromAirbase(e: any) { //this.#aircraftSpawnMenu(e); } @@ -142,14 +179,13 @@ export class Map extends L.Map { /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { - this.hideContextMenu(); + this.hideAllContextMenus(); if (this.#state === IDLE) { } else if (this.#state === MOVE_UNIT) { this.setState(IDLE); getUnitsManager().deselectAllUnits(); - this.hideContextMenu(); } } } @@ -159,10 +195,10 @@ export class Map extends L.Map { } #onContextMenu(e: any) { - this.hideContextMenu(); + this.hideMapContextMenu(); if (this.#state === IDLE) { if (this.#state == IDLE) { - this.showContextMenu(e); + this.showMapContextMenu(e); } } else if (this.#state === MOVE_UNIT) { diff --git a/client/src/missionhandler/airbase.ts b/client/src/missionhandler/airbase.ts index 593312df..6bbd88dd 100644 --- a/client/src/missionhandler/airbase.ts +++ b/client/src/missionhandler/airbase.ts @@ -10,41 +10,28 @@ export interface AirbaseOptions export class Airbase extends L.Marker { #name: string = ""; - #coalitionID: number = -1; + #coalition: string = ""; constructor(options: AirbaseOptions) { super(options.position, { riseOnHover: true }); this.#name = options.name; - var icon = new L.DivIcon({ - html: ` - - - -
- -
${options.name}
-
`, - className: 'airbase-marker'}); // Set the marker, className must be set to avoid white square + html: `
+
+
`, + className: 'leaflet-airbase-marker', + iconSize: [63, 63] + }); // Set the marker, className must be set to avoid white square this.setIcon(icon); + } - setCoalitionID(coalitionID: number) + setCoalition(coalition: string) { - this.#coalitionID = coalitionID; - var element = this.getElement(); - if (element != null) - { - var img = element.querySelector("#icon"); - if (img != null) - { - img.classList.toggle("blue", this.#coalitionID == 2); - img.classList.toggle("red", this.#coalitionID == 1); - img.classList.toggle("neutral", this.#coalitionID == 0); - } - } + this.#coalition = coalition; + this.getElement()?.setAttribute("data-coalition", this.#coalition); } getName() @@ -52,8 +39,8 @@ export class Airbase extends L.Marker return this.#name; } - getCoalitionID() + getCoalition() { - return this.#coalitionID; + return this.#coalition; } } diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 193b69a9..a655b4ce 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -1,6 +1,5 @@ import { Marker, LatLng, Icon } from "leaflet"; import { getMap, getUnitsManager } from ".."; -import { SpawnEvent } from "../map/map"; import { Airbase } from "./airbase"; var bullseyeIcons = [ @@ -71,7 +70,8 @@ export class MissionHandler } else { - this.#airbasesMarkers[idx].setCoalitionID(airbase.coalition); + this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.lat, airbase.lng)); + this.#airbasesMarkers[idx].setCoalition(airbase.coalition); } } } @@ -84,20 +84,21 @@ export class MissionHandler else options = ["Spawn unit"]; + getMap().showAirbaseContextMenu(e); //getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(), // options.map((option) => {return {tooltip: option, src: "", callback: (label: string) => {this.#onAirbaseOptionSelection(e, label)}}}, false) //) } #onAirbaseOptionSelection(e: any, option: string) { - if (option === "Spawn unit") { - var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()}; - getMap().spawnFromAirbase(spawnEvent); - } - else if (option === "Land here") - { - getMap().hideContextMenu(); - getUnitsManager().selectedUnitsLandAt(e.latlng); - } + //if (option === "Spawn unit") { + // var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()}; + // getMap().spawnFromAirbase(spawnEvent); + //} + //else if (option === "Land here") + //{ + // getMap().hideContextMenu(); + // getUnitsManager().selectedUnitsLandAt(e.latlng); + //} } } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 2d7f83b0..e591ea72 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,6 +1,6 @@ import * as L from 'leaflet' import { setConnected } from '..'; -import { SpawnOptions } from '../controls/contextmenu'; +import { SpawnOptions } from '../controls/mapcontextmenu'; /* Edit here to change server address */ const REST_ADDRESS = "http://localhost:30000/olympus"; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index bfe59e14..7ec28d2e 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -91,7 +91,7 @@ export class Unit extends Marker { var icon = new DivIcon({ html: html, - className: 'ol-unit-marker', + className: 'leaflet-unit-marker', iconAnchor: [0, 0] }); this.setIcon(icon); @@ -338,14 +338,16 @@ export class Unit extends Marker { #onContextMenu(e: any) { var options = [ - 'Attack', - 'Follow' + 'Attack' ] - //getMap().showContextMenu(e.originalEvent, "Action: " + this.getData().unitName, options.map((option: string) => {return {tooltip: option, src: "", callback: (action: string) => this.#executeAction(action)}})); + getMap().showUnitContextMenu(e); + getMap().getUnitContextMenu().setOptions(options, (option: string) => { + getMap().hideUnitContextMenu(); + this.#executeAction(option); + }); } #executeAction(action: string) { - getMap().hideContextMenu(); if (action === "Attack") getUnitsManager().selectedUnitsAttackUnit(this.ID); } diff --git a/client/views/contextmenu.ejs b/client/views/contextmenus.ejs similarity index 95% rename from client/views/contextmenu.ejs rename to client/views/contextmenus.ejs index 09115f68..08ce62aa 100644 --- a/client/views/contextmenu.ejs +++ b/client/views/contextmenus.ejs @@ -1,4 +1,4 @@ -
+
+
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/client/views/index.ejs b/client/views/index.ejs index c951c67f..cecce721 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -25,7 +25,7 @@ <%- include('aic.ejs') %> <%- include('atc.ejs') %> - <%- include('contextmenu.ejs') %> + <%- include('contextmenus.ejs') %> <%- include('unitcontrolpanel.ejs') %> <%- include('unitinfopanel.ejs') %> <%- include('mouseinfopanel.ejs') %>