From aad0fd22ad0732cf6a0cd9ee63ab20dc2fe2f84b Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 5 Nov 2023 10:57:53 +0000 Subject: [PATCH 01/10] Air defence has its own spawn icon --- client/@types/olympus/index.d.ts | 2 +- client/plugins/databasemanager/index.js | 26 +++-------- .../databasemanager/src/loadouteditor.ts | 8 ++-- client/plugins/databasemanager/src/utils.ts | 2 +- client/src/contextmenus/mapcontextmenu.ts | 21 ++++++++- client/src/controls/unitspawnmenu.ts | 46 ++++++++++++++++++- client/src/unit/databases/unitdatabase.ts | 4 +- client/views/contextmenus/map.ejs | 9 +++- 8 files changed, 86 insertions(+), 32 deletions(-) diff --git a/client/@types/olympus/index.d.ts b/client/@types/olympus/index.d.ts index faa3f853..063b3ddf 100644 --- a/client/@types/olympus/index.d.ts +++ b/client/@types/olympus/index.d.ts @@ -869,7 +869,7 @@ declare module "contextmenus/mapcontextmenu" { * @param y Y screen coordinate of the top left corner of the context menu * @param latlng Leaflet latlng object of the mouse click */ - show(x: number, y: number, latlng: LatLng): void; + show(x: number, y: number, latlng: LatLng): false | undefined; /** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it. * * @param coalitionArea The CoalitionArea the user can edit diff --git a/client/plugins/databasemanager/index.js b/client/plugins/databasemanager/index.js index 82738524..a43d566e 100644 --- a/client/plugins/databasemanager/index.js +++ b/client/plugins/databasemanager/index.js @@ -613,10 +613,10 @@ class LoadoutEditor { title.innerText = "Loadout properties"; __classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f").appendChild(title); if (__classPrivateFieldGet(this, _LoadoutEditor_loadout, "f")) { - var laodout = __classPrivateFieldGet(this, _LoadoutEditor_loadout, "f"); - (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Name", laodout.name, "text", (value) => { laodout.name = value; __classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f").dispatchEvent(new Event("refresh")); }); - (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Code", laodout.code, "text", (value) => { laodout.code = value; }); - (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Roles", (0, utils_1.arrayToString)(laodout.roles), "text", (value) => { laodout.roles = (0, utils_1.stringToArray)(value); }); + var loadout = __classPrivateFieldGet(this, _LoadoutEditor_loadout, "f"); + (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Name", loadout.name, "text", (value) => { loadout.name = value; __classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f").dispatchEvent(new Event("refresh")); }); + (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Code", loadout.code, "text", (value) => { loadout.code = value; }); + (0, utils_1.addStringInput)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), "Roles", (0, utils_1.arrayToString)(loadout.roles), "text", (value) => { loadout.roles = (0, utils_1.stringToArray)(value); }); (0, utils_1.addLoadoutItemsEditor)(__classPrivateFieldGet(this, _LoadoutEditor_contentDiv, "f"), __classPrivateFieldGet(this, _LoadoutEditor_loadout, "f")); } } @@ -1034,25 +1034,11 @@ exports.addLoadoutsScroll = addLoadoutsScroll; * @returns The string */ function arrayToString(array) { - var value = "["; - var firstRole = true; - array.forEach((role) => { - value += firstRole ? "" : ", "; - firstRole = false; - value += role; - }); - value += "]"; - return value; + return "[" + array.join(", ") + "]"; } exports.arrayToString = arrayToString; function stringToArray(input) { - input = input.replace("[", "").replace("]", ""); - var values = input.split(","); - var result = []; - values.forEach((value) => { - result.push(value.trim()); - }); - return result; + return input.match(/(\w)+/g) || []; } exports.stringToArray = stringToArray; diff --git a/client/plugins/databasemanager/src/loadouteditor.ts b/client/plugins/databasemanager/src/loadouteditor.ts index 73fe32e1..5f07c605 100644 --- a/client/plugins/databasemanager/src/loadouteditor.ts +++ b/client/plugins/databasemanager/src/loadouteditor.ts @@ -37,10 +37,10 @@ export class LoadoutEditor { this.#contentDiv.appendChild(title); if (this.#loadout) { - var laodout = this.#loadout; - addStringInput(this.#contentDiv, "Name", laodout.name, "text", (value: string) => {laodout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));}); - addStringInput(this.#contentDiv, "Code", laodout.code, "text", (value: string) => {laodout.code = value; }); - addStringInput(this.#contentDiv, "Roles", arrayToString(laodout.roles), "text", (value: string) => {laodout.roles = stringToArray(value);}); + var loadout = this.#loadout; + addStringInput(this.#contentDiv, "Name", loadout.name, "text", (value: string) => {loadout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));}); + addStringInput(this.#contentDiv, "Code", loadout.code, "text", (value: string) => {loadout.code = value; }); + addStringInput(this.#contentDiv, "Roles", arrayToString(loadout.roles), "text", (value: string) => {loadout.roles = stringToArray(value);}); addLoadoutItemsEditor(this.#contentDiv, this.#loadout); } } diff --git a/client/plugins/databasemanager/src/utils.ts b/client/plugins/databasemanager/src/utils.ts index 19cec041..59594c83 100644 --- a/client/plugins/databasemanager/src/utils.ts +++ b/client/plugins/databasemanager/src/utils.ts @@ -269,5 +269,5 @@ export function arrayToString(array: string[]) { export function stringToArray(input: string) { - return input.match( /(\w)+/g ); + return input.match( /(\w)+/g ) || []; } \ No newline at end of file diff --git a/client/src/contextmenus/mapcontextmenu.ts b/client/src/contextmenus/mapcontextmenu.ts index f7924fcd..30006313 100644 --- a/client/src/contextmenus/mapcontextmenu.ts +++ b/client/src/contextmenus/mapcontextmenu.ts @@ -4,7 +4,7 @@ import { ContextMenu } from "./contextmenu"; import { Switch } from "../controls/switch"; import { GAME_MASTER } from "../constants/constants"; import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; -import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu"; +import { AirDefenceUnitSpawnMenu, AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu"; import { Airbase } from "../mission/airbase"; import { SmokeMarker } from "../map/markers/smokemarker"; @@ -15,6 +15,7 @@ export class MapContextMenu extends ContextMenu { #coalitionSwitch: Switch; #aircraftSpawnMenu: AircraftSpawnMenu; #helicopterSpawnMenu: HelicopterSpawnMenu; + #airDefenceUnitSpawnMenu: AirDefenceUnitSpawnMenu; #groundUnitSpawnMenu: GroundUnitSpawnMenu; #navyUnitSpawnMenu: NavyUnitSpawnMenu; #coalitionArea: CoalitionArea | null = null; @@ -35,6 +36,7 @@ export class MapContextMenu extends ContextMenu { /* Create the spawn menus for the different unit types */ this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu"); this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu"); + this.#airDefenceUnitSpawnMenu = new AirDefenceUnitSpawnMenu("air-defence-spawn-menu"); this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu"); this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu"); @@ -73,21 +75,25 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); + this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); + this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.getContainer()?.addEventListener("show", () => this.#aircraftSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#helicopterSpawnMenu.showCirclesPreviews()); + this.getContainer()?.addEventListener("show", () => this.#airDefenceUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#groundUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#navyUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#aircraftSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#helicopterSpawnMenu.clearCirclesPreviews()); + this.getContainer()?.addEventListener("hide", () => this.#airDefenceUnitSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#groundUnitSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#navyUnitSpawnMenu.clearCirclesPreviews()); } @@ -103,11 +109,13 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.setLatLng(latlng); this.#helicopterSpawnMenu.setLatLng(latlng); + this.#airDefenceUnitSpawnMenu.setLatLng(latlng); this.#groundUnitSpawnMenu.setLatLng(latlng); this.#navyUnitSpawnMenu.setLatLng(latlng); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); @@ -143,7 +151,7 @@ export class MapContextMenu extends ContextMenu { #showSubMenu(type: string) { if (type === "more") this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide"); - else if (["aircraft", "helicopter", "groundunit"].includes(type)) + else if (["aircraft", "helicopter", "air-defence", "groundunit"].includes(type)) this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true); this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); @@ -152,6 +160,8 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter"); this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit"); this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit"); + this.getContainer()?.querySelector("#air-defence-spawn-menu")?.classList.toggle("hide", type !== "air-defence"); + this.getContainer()?.querySelector("#air-defence-spawn-button")?.classList.toggle("is-open", type === "air-defence"); this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit"); this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit"); this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke"); @@ -167,6 +177,9 @@ export class MapContextMenu extends ContextMenu { this.#helicopterSpawnMenu.reset(); this.#helicopterSpawnMenu.setCountries(); this.#helicopterSpawnMenu.clearCirclesPreviews(); + this.#airDefenceUnitSpawnMenu.reset(); + this.#airDefenceUnitSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); this.#groundUnitSpawnMenu.reset(); this.#groundUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.clearCirclesPreviews(); @@ -200,11 +213,13 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.reset(); this.#helicopterSpawnMenu.reset(); + this.#airDefenceUnitSpawnMenu.reset(); this.#groundUnitSpawnMenu.reset(); this.#navyUnitSpawnMenu.reset(); this.#aircraftSpawnMenu.clearCirclesPreviews(); this.#helicopterSpawnMenu.clearCirclesPreviews(); + this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); this.#groundUnitSpawnMenu.clearCirclesPreviews(); this.#navyUnitSpawnMenu.clearCirclesPreviews(); @@ -221,6 +236,7 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); } @@ -234,6 +250,7 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); } diff --git a/client/src/controls/unitspawnmenu.ts b/client/src/controls/unitspawnmenu.ts index 0ed023d1..adfbd68e 100644 --- a/client/src/controls/unitspawnmenu.ts +++ b/client/src/controls/unitspawnmenu.ts @@ -31,6 +31,7 @@ export class UnitSpawnMenu { #unitDatabase: UnitDatabase; #countryCodes: any; #orderByRole: boolean; + protected unitTypeFilter = (unit:any) => { return true; }; /* Controls */ #unitRoleTypeDropdown: Dropdown; @@ -258,7 +259,7 @@ export class UnitSpawnMenu { if (this.#orderByRole) this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles()); else - this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes()); + this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter)); this.#unitLoadoutListEl.replaceChildren(); this.#unitLoadoutDropdown.reset(); @@ -582,9 +583,52 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { } } +export class AirDefenceUnitSpawnMenu extends UnitSpawnMenu { + + protected showRangeCircles: boolean = true; + protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)}; + + /** + * + * @param ID - the ID of the HTML element which will contain the context menu + */ + constructor(ID: string){ + super(ID, groundUnitDatabase, false); + this.setMaxUnitCount(4); + this.getAltitudeSlider().hide(); + this.getLoadoutDropdown().hide(); + this.getLoadoutPreview().classList.add("hide"); + } + + deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { + spawnOptions.coalition = getApp().getActiveCoalition(); + if (spawnOptions) { + var unitTable: UnitSpawnTable = { + unitType: spawnOptions.name, + location: spawnOptions.latlng, + liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "" + }; + + var units = []; + for (let i = 0; i < unitsCount; i++) { + units.push(JSON.parse(JSON.stringify(unitTable))); + unitTable.location.lat += i > 0? 0.0001: 0; + } + + getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { + if (res.commandHash !== undefined) + getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); + }); + + this.getContainer().dispatchEvent(new Event("hide")); + } + } +} + export class GroundUnitSpawnMenu extends UnitSpawnMenu { protected showRangeCircles: boolean = true; + protected unitTypeFilter = (unit:any) => {return !(/\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type))}; /** * diff --git a/client/src/unit/databases/unitdatabase.ts b/client/src/unit/databases/unitdatabase.ts index ba7c41cc..94c8acca 100644 --- a/client/src/unit/databases/unitdatabase.ts +++ b/client/src/unit/databases/unitdatabase.ts @@ -94,10 +94,12 @@ export class UnitDatabase { } /* Returns a list of all possible types in a database */ - getTypes() { + getTypes(unitFilter?:CallableFunction) { var filteredBlueprints = this.getBlueprints(); var types: string[] = []; for (let unit in filteredBlueprints) { + if ( typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit])) + continue; var type = filteredBlueprints[unit].type; if (type && type !== "" && !types.includes(type)) types.push(type); diff --git a/client/views/contextmenus/map.ejs b/client/views/contextmenus/map.ejs index 75533346..ae4de75d 100644 --- a/client/views/contextmenus/map.ejs +++ b/client/views/contextmenus/map.ejs @@ -6,6 +6,8 @@ data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"> + From dd811def07a37c29c48d414a0f905c36cdc2f80f Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 5 Nov 2023 19:11:42 +0000 Subject: [PATCH 02/10] Added health bar --- client/public/stylesheets/markers/units.css | 36 +++++++++++++++++-- .../themes/olympus/images/icons/health.svg | 1 + client/public/themes/olympus/theme.css | 6 ++++ client/src/interfaces.ts | 1 + client/src/unit/unit.ts | 21 +++++++++++ client/src/weapon/weapon.ts | 3 ++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 client/public/themes/olympus/images/icons/health.svg diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 7f9ae8e9..0fc7848d 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -97,6 +97,18 @@ position: absolute; } +/*** Health indicator ***/ +[data-object|="unit"] .unit-health { + background: white; + border: var(--unit-health-border-width) solid var(--secondary-dark-steel); + border-radius: var(--border-radius-sm); + display: none; + height: var(--unit-health-height); + position: absolute; + translate: var(--unit-health-x) var(--unit-health-y); + width: var(--unit-health-width); +} + /*** Fuel indicator ***/ [data-object|="unit"] .unit-fuel { background: white; @@ -109,7 +121,8 @@ width: var(--unit-fuel-width); } -[data-object|="unit"] .unit-fuel-level { +[data-object|="unit"] .unit-fuel-level, +[data-object|="unit"] .unit-health-level { background-color: var(--secondary-light-grey); height: 100%; width: 100%; @@ -178,6 +191,7 @@ /*** Common ***/ [data-object|="unit"]:hover .unit-ammo, +[data-object|="unit"]:hover .unit-health , [data-object|="unit"]:hover .unit-fuel { display: flex; } @@ -188,13 +202,14 @@ } } -[data-object|="unit"][data-has-low-fuel] .unit-fuel { +[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health { animation: pulse 1.5s linear infinite; } [data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup, [data-object|="unit"][data-is-selected] .unit-ammo, [data-object|="unit"][data-is-selected] .unit-fuel, +[data-object|="unit"][data-is-selected] .unit-health, [data-object|="unit"][data-is-selected] .unit-selected-spotlight { display: flex; } @@ -211,6 +226,7 @@ } [data-object|="unit"][data-coalition="blue"] .unit-fuel-level, +[data-object|="unit"][data-coalition="blue"] .unit-health-level, [data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo>div:nth-child(1), [data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo>div:nth-child(2), [data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo>div:nth-child(3), @@ -227,6 +243,7 @@ } [data-object|="unit"][data-coalition="red"] .unit-fuel-level, +[data-object|="unit"][data-coalition="red"] .unit-health-level, [data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo>div:nth-child(1), [data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo>div:nth-child(2), [data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo>div:nth-child(3), @@ -307,6 +324,20 @@ background-image: url("/resources/theme/images/states/awacs.svg"); } +[data-object|="unit"] .unit-health::before { + background-image: url("/resources/theme/images/icons/health.svg"); + background-repeat: no-repeat; + background-size: contain; + content: " "; + filter: invert(100%); + height: 10px; + left: 0; + position: absolute; + top: 0; + translate: -11px -4px; + width: 10px; +} + /*** Dead unit ***/ [data-object|="unit"][data-is-dead] .unit-selected-spotlight, @@ -316,6 +347,7 @@ [data-object|="unit"][data-is-dead] .unit-hotgroup-id, [data-object|="unit"][data-is-dead] .unit-state, [data-object|="unit"][data-is-dead] .unit-fuel, +[data-object|="unit"][data-is-dead] .unit-health, [data-object|="unit"][data-is-dead] .unit-ammo, [data-object|="unit"][data-is-dead]:hover .unit-fuel, [data-object|="unit"][data-is-dead]:hover .unit-ammo { diff --git a/client/public/themes/olympus/images/icons/health.svg b/client/public/themes/olympus/images/icons/health.svg new file mode 100644 index 00000000..cc4a6622 --- /dev/null +++ b/client/public/themes/olympus/images/icons/health.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index df1c6244..8cd6f904 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -66,6 +66,12 @@ --unit-height: 50px; --unit-width: 50px; + --unit-health-border-width: 2px; + --unit-health-height: 6px; + --unit-health-width: 36px; + --unit-health-x: 0px; + --unit-health-y: 26px; + /*** Air units ***/ --unit-ammo-gap: calc(2px + var(--unit-stroke-width)); --unit-ammo-border-radius: 50%; diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index dc4931a0..4208e448 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -86,6 +86,7 @@ export interface UnitSpawnTable { export interface ObjectIconOptions { showState: boolean, showVvi: boolean, + showHealth: boolean, showHotgroup: boolean, showUnitIcon: boolean, showShortLabel: boolean, diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index c79c0c18..983d4d27 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -40,6 +40,7 @@ export class Unit extends CustomMarker { #onOff: boolean = true; #followRoads: boolean = false; #fuel: number = 0; + #health: number = Math.round(Math.random()*100); #desiredSpeed: number = 0; #desiredSpeedType: string = "CAS"; #desiredAltitude: number = 0; @@ -116,6 +117,7 @@ export class Unit extends CustomMarker { getGroupName() { return this.#groupName }; getHasTask() { return this.#hasTask }; getHeading() { return this.#heading }; + getHealth() { return this.#health }; getHuman() { return this.#human }; getIsActiveAWACS() { return this.#isActiveAWACS }; getIsActiveTanker() { return this.#isActiveTanker }; @@ -228,6 +230,7 @@ export class Unit extends CustomMarker { case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; + // case DataIndexes.health: this.#health = dataExtractor.extractUInt16(); break; // To be dynamic case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; @@ -327,6 +330,7 @@ export class Unit extends CustomMarker { return { showState: false, showVvi: false, + showHealth: true, showHotgroup: false, showUnitIcon: true, showShortLabel: false, @@ -519,6 +523,16 @@ export class Unit extends CustomMarker { el.append(fuelIndicator); } + // Health indicator + if (iconOptions.showHealth) { + var healthIndicator = document.createElement("div"); + healthIndicator.classList.add("unit-health"); + var healthLevel = document.createElement("div"); + healthLevel.classList.add("unit-health-level"); + healthIndicator.appendChild(healthLevel); + el.append(healthIndicator); + } + // Ammo indicator if (iconOptions.showAmmo) { var ammoIndicator = document.createElement("div"); @@ -1038,6 +1052,10 @@ export class Unit extends CustomMarker { element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`); element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20); + /* Set health data */ + element.querySelector(".unit-health-level")?.setAttribute("style", `width: ${this.#health}%`); + element.querySelector(".unit")?.toggleAttribute("data-has-low-health", this.#health < 20); + /* Set dead/alive flag */ element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); @@ -1341,6 +1359,7 @@ export class AirUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: belongsToCommandedCoalition, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))), @@ -1414,6 +1433,7 @@ export class GroundUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: false, + showHealth: true, showHotgroup: belongsToCommandedCoalition, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, @@ -1478,6 +1498,7 @@ export class NavyUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: false, + showHealth: false, showHotgroup: true, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, diff --git a/client/src/weapon/weapon.ts b/client/src/weapon/weapon.ts index f673c07e..57496337 100644 --- a/client/src/weapon/weapon.ts +++ b/client/src/weapon/weapon.ts @@ -100,6 +100,7 @@ export class Weapon extends CustomMarker { return { showState: false, showVvi: false, + showHealth: false, showHotgroup: false, showUnitIcon: true, showShortLabel: false, @@ -276,6 +277,7 @@ export class Missile extends Weapon { return { showState: false, showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: false, showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, @@ -308,6 +310,7 @@ export class Bomb extends Weapon { return { showState: false, showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: false, showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, From 5db90e589651c93852961642449096c1c4f793df Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 5 Nov 2023 19:24:23 +0000 Subject: [PATCH 03/10] Changed air defence form to extend ground units' form --- client/src/controls/unitspawnmenu.ts | 56 +++++++--------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/client/src/controls/unitspawnmenu.ts b/client/src/controls/unitspawnmenu.ts index adfbd68e..b0ee3fd4 100644 --- a/client/src/controls/unitspawnmenu.ts +++ b/client/src/controls/unitspawnmenu.ts @@ -583,48 +583,6 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { } } -export class AirDefenceUnitSpawnMenu extends UnitSpawnMenu { - - protected showRangeCircles: boolean = true; - protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)}; - - /** - * - * @param ID - the ID of the HTML element which will contain the context menu - */ - constructor(ID: string){ - super(ID, groundUnitDatabase, false); - this.setMaxUnitCount(4); - this.getAltitudeSlider().hide(); - this.getLoadoutDropdown().hide(); - this.getLoadoutPreview().classList.add("hide"); - } - - deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) { - spawnOptions.coalition = getApp().getActiveCoalition(); - if (spawnOptions) { - var unitTable: UnitSpawnTable = { - unitType: spawnOptions.name, - location: spawnOptions.latlng, - liveryID: spawnOptions.liveryID? spawnOptions.liveryID: "" - }; - - var units = []; - for (let i = 0; i < unitsCount; i++) { - units.push(JSON.parse(JSON.stringify(unitTable))); - unitTable.location.lat += i > 0? 0.0001: 0; - } - - getApp().getUnitsManager().spawnUnits("GroundUnit", units, getApp().getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => { - if (res.commandHash !== undefined) - getApp().getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getApp().getActiveCoalition(), res.commandHash); - }); - - this.getContainer().dispatchEvent(new Event("hide")); - } - } -} - export class GroundUnitSpawnMenu extends UnitSpawnMenu { protected showRangeCircles: boolean = true; @@ -667,6 +625,20 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu { } } +export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { + + protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)}; + + /** + * + * @param ID - the ID of the HTML element which will contain the context menu + */ + constructor(ID: string){ + super(ID); + this.setMaxUnitCount(4); + } +} + export class NavyUnitSpawnMenu extends UnitSpawnMenu { /** * From f72717404482c3cae4b9d40f3ec3e7c98b4fd94a Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 6 Nov 2023 18:30:59 +0100 Subject: [PATCH 04/10] Testing of dynamic resizing --- .gitignore | 1 + client/plugins/databasemanager/index.js | 134 +++++++---- client/plugins/databasemanager/src/utils.ts | 6 +- client/public/stylesheets/layout/layout.css | 81 +------ client/public/stylesheets/olympus.css | 2 + client/public/stylesheets/other/toolbar.css | 73 ++++++ .../public/stylesheets/panels/unitcontrol.css | 40 ++++ client/public/stylesheets/panels/unitinfo.css | 57 ++--- client/public/stylesheets/style/style.css | 3 - .../olympus/images/icons/gamepad-solid.svg | 1 + .../icons/person-military-pointing-solid.svg | 1 + client/src/olympusapp.ts | 10 +- client/src/panels/unitcontrolpanel.ts | 10 + client/views/panels/unitcontrol.ejs | 212 +++++++++--------- client/views/toolbars/commandmode.ejs | 1 + client/views/toolbars/primary.ejs | 6 +- 16 files changed, 363 insertions(+), 275 deletions(-) create mode 100644 client/public/stylesheets/other/toolbar.css create mode 100644 client/public/themes/olympus/images/icons/gamepad-solid.svg create mode 100644 client/public/themes/olympus/images/icons/person-military-pointing-solid.svg diff --git a/.gitignore b/.gitignore index 7a2f4b1f..2f1bffe6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ node_modules /client/plugins/controltips/index.js hgt /client/public/databases/units/old +/client/plugins/databasemanager/index.js diff --git a/client/plugins/databasemanager/index.js b/client/plugins/databasemanager/index.js index db4029ad..d434ec3d 100644 --- a/client/plugins/databasemanager/index.js +++ b/client/plugins/databasemanager/index.js @@ -508,7 +508,7 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor { * @param blueprint The blueprint to edit */ setBlueprint(blueprint) { - var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s; + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u; __classPrivateFieldSet(this, _GroundUnitEditor_blueprint, blueprint, "f"); if (__classPrivateFieldGet(this, _GroundUnitEditor_blueprint, "f") !== null) { this.contentDiv2.replaceChildren(); @@ -526,18 +526,20 @@ class GroundUnitEditor extends uniteditor_1.UnitEditor { (0, utils_1.addStringInput)(this.contentDiv2, "Acquisition range [m]", (_c = String(blueprint.acquisitionRange)) !== null && _c !== void 0 ? _c : "", "number", (value) => { blueprint.acquisitionRange = parseFloat(value); }); (0, utils_1.addStringInput)(this.contentDiv2, "Engagement range [m]", (_d = String(blueprint.engagementRange)) !== null && _d !== void 0 ? _d : "", "number", (value) => { blueprint.engagementRange = parseFloat(value); }); (0, utils_1.addStringInput)(this.contentDiv2, "Targeting range [m]", (_e = String(blueprint.targetingRange)) !== null && _e !== void 0 ? _e : "", "number", (value) => { blueprint.targetingRange = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_f = String(blueprint.barrelHeight)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_g = String(blueprint.muzzleVelocity)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_h = String(blueprint.aimTime)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.aimTime = parseFloat(value); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Burst quantity", (_j = String(blueprint.shotsToFire)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Burst base interval [s]", (_k = String(blueprint.shotsBaseInterval)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); }); - (0, utils_1.addStringInput)(this.contentDiv2, "Base scatter [°]", (_l = String(blueprint.shotsBaseScatter)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_m = blueprint.canTargetPoint) !== null && _m !== void 0 ? _m : false, (value) => { blueprint.canTargetPoint = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_o = blueprint.canRearm) !== null && _o !== void 0 ? _o : false, (value) => { blueprint.canRearm = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_p = blueprint.canAAA) !== null && _p !== void 0 ? _p : false, (value) => { blueprint.canAAA = value; }); - (0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_q = blueprint.indirectFire) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.indirectFire = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Description", (_r = blueprint.description) !== null && _r !== void 0 ? _r : "", "text", (value) => { blueprint.description = value; }); - (0, utils_1.addStringInput)(this.contentDiv2, "Abilities", (_s = blueprint.abilities) !== null && _s !== void 0 ? _s : "", "text", (value) => { blueprint.abilities = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Aim method range [m]", (_f = String(blueprint.aimMethodRange)) !== null && _f !== void 0 ? _f : "", "number", (value) => { blueprint.aimMethodRange = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Barrel height [m]", (_g = String(blueprint.barrelHeight)) !== null && _g !== void 0 ? _g : "", "number", (value) => { blueprint.barrelHeight = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Muzzle velocity [m/s]", (_h = String(blueprint.muzzleVelocity)) !== null && _h !== void 0 ? _h : "", "number", (value) => { blueprint.muzzleVelocity = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Aim time [s]", (_j = String(blueprint.aimTime)) !== null && _j !== void 0 ? _j : "", "number", (value) => { blueprint.aimTime = parseFloat(value); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots to fire", (_k = String(blueprint.shotsToFire)) !== null && _k !== void 0 ? _k : "", "number", (value) => { blueprint.shotsToFire = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots base interval [s]", (_l = String(blueprint.shotsBaseInterval)) !== null && _l !== void 0 ? _l : "", "number", (value) => { blueprint.shotsBaseInterval = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Shots base scatter [°]", (_m = String(blueprint.shotsBaseScatter)) !== null && _m !== void 0 ? _m : "", "number", (value) => { blueprint.shotsBaseScatter = Math.round(parseFloat(value)); }); + (0, utils_1.addStringInput)(this.contentDiv2, "Alertness time constant [s]", (_o = String(blueprint.alertnessTimeConstant)) !== null && _o !== void 0 ? _o : "", "number", (value) => { blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can target point", (_p = blueprint.canTargetPoint) !== null && _p !== void 0 ? _p : false, (value) => { blueprint.canTargetPoint = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can rearm", (_q = blueprint.canRearm) !== null && _q !== void 0 ? _q : false, (value) => { blueprint.canRearm = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Can operate as AAA", (_r = blueprint.canAAA) !== null && _r !== void 0 ? _r : false, (value) => { blueprint.canAAA = value; }); + (0, utils_1.addCheckboxInput)(this.contentDiv2, "Indirect fire (e.g. mortar)", (_s = blueprint.indirectFire) !== null && _s !== void 0 ? _s : false, (value) => { blueprint.indirectFire = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Description", (_t = blueprint.description) !== null && _t !== void 0 ? _t : "", "text", (value) => { blueprint.description = value; }); + (0, utils_1.addStringInput)(this.contentDiv2, "Abilities", (_u = blueprint.abilities) !== null && _u !== void 0 ? _u : "", "text", (value) => { blueprint.abilities = value; }); } } /** Add a new empty blueprint @@ -751,9 +753,9 @@ class UnitEditor { this.database = JSON.parse(JSON.stringify({ blueprints: database.getBlueprints(true) })); } /** Show the editor - * + * @param filter String filter */ - show() { + show(filter = "") { this.visible = true; this.contentDiv1.replaceChildren(); this.contentDiv2.replaceChildren(); @@ -763,17 +765,16 @@ class UnitEditor { var title = document.createElement("label"); title.innerText = "Units list"; this.contentDiv1.appendChild(title); - (0, utils_1.addBlueprintsScroll)(this.contentDiv1, this.database, (key) => { - if (this.database != null) - this.setBlueprint(this.database.blueprints[key]); - }); - (0, utils_1.addNewElementInput)(this.contentDiv1, (ev, input) => { - if (input.value != "") - this.addBlueprint((input).value); - }); + var filterInput = document.createElement("input"); + filterInput.value = filter; + this.contentDiv1.appendChild(filterInput); + filterInput.onchange = (e) => { + this.show(e.target.value); + }; + this.addBlueprints(filter); } } - /** Hid the editor + /** Hide the editor * */ hide() { @@ -789,6 +790,22 @@ class UnitEditor { getDatabase() { return this.database; } + /** + * + * @param filter String filter + */ + addBlueprints(filter = "") { + if (this.database) { + (0, utils_1.addBlueprintsScroll)(this.contentDiv1, this.database, filter, (key) => { + if (this.database != null) + this.setBlueprint(this.database.blueprints[key]); + }); + (0, utils_1.addNewElementInput)(this.contentDiv1, (ev, input) => { + if (input.value != "") + this.addBlueprint((input).value); + }); + } + } } exports.UnitEditor = UnitEditor; @@ -958,36 +975,56 @@ exports.addNewElementInput = addNewElementInput; * * @param div The HTMLElement that will contain the list * @param database The database that will be used to fill the list of blueprints + * @param filter A string filter that will be executed to filter the blueprints to add * @param callback Callback called when the user clicks on one of the elements */ -function addBlueprintsScroll(div, database, callback) { +function addBlueprintsScroll(div, database, filter, callback) { var scrollDiv = document.createElement("div"); scrollDiv.classList.add("dm-scroll-container"); if (database !== null) { var blueprints = database.blueprints; for (let key of Object.keys(blueprints).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))) { - var rowDiv = document.createElement("div"); - scrollDiv.appendChild(rowDiv); - var text = document.createElement("label"); - text.textContent = key; - text.onclick = () => callback(key); - rowDiv.appendChild(text); - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.checked = blueprints[key].enabled; - checkbox.onclick = () => { - console.log(checkbox.checked); - blueprints[key].enabled = checkbox.checked; - }; - rowDiv.appendChild(checkbox); - /* This button allows to remove an element from the list. It requires a refresh. */ - var button = document.createElement("button"); - button.innerText = "X"; - button.onclick = () => { - delete blueprints[key]; - div.dispatchEvent(new Event("refresh")); - }; - rowDiv.appendChild(button); + var addKey = true; + if (filter !== "") { + try { + var blueprint = blueprints[key]; + addKey = eval(filter); + } + catch (_a) { + console.error("An error has occurred evaluating the blueprint filter"); + } + } + if (addKey) { + var rowDiv = document.createElement("div"); + scrollDiv.appendChild(rowDiv); + let text = document.createElement("label"); + text.textContent = key; + text.onclick = () => { + callback(key); + const collection = document.getElementsByClassName("blueprint-selected"); + for (let i = 0; i < collection.length; i++) { + collection[i].classList.remove("blueprint-selected"); + } + text.classList.add("blueprint-selected"); + }; + rowDiv.appendChild(text); + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = blueprints[key].enabled; + checkbox.onclick = () => { + console.log(checkbox.checked); + blueprints[key].enabled = checkbox.checked; + }; + rowDiv.appendChild(checkbox); + /* This button allows to remove an element from the list. It requires a refresh. */ + var button = document.createElement("button"); + button.innerText = "X"; + button.onclick = () => { + delete blueprints[key]; + div.dispatchEvent(new Event("refresh")); + }; + rowDiv.appendChild(button); + } } } div.appendChild(scrollDiv); @@ -1043,7 +1080,8 @@ function arrayToString(array) { } exports.arrayToString = arrayToString; function stringToArray(input) { - return input.match(/(\w)+/g) || []; + var _a; + return (_a = input.match(/(\w)+/g)) !== null && _a !== void 0 ? _a : []; } exports.stringToArray = stringToArray; diff --git a/client/plugins/databasemanager/src/utils.ts b/client/plugins/databasemanager/src/utils.ts index 004a8b84..18639612 100644 --- a/client/plugins/databasemanager/src/utils.ts +++ b/client/plugins/databasemanager/src/utils.ts @@ -287,7 +287,11 @@ export function arrayToString(array: string[]) { return "[" + array.join( ", " ) + "]"; } - +/** Converts an a single string like [val1, val2, val3] into an array + * + * @param input The input string + * @returns The array + */ export function stringToArray(input: string) { return input.match( /(\w)+/g ) ?? []; } \ No newline at end of file diff --git a/client/public/stylesheets/layout/layout.css b/client/public/stylesheets/layout/layout.css index 8dc6e68d..3a85782b 100644 --- a/client/public/stylesheets/layout/layout.css +++ b/client/public/stylesheets/layout/layout.css @@ -1,7 +1,3 @@ -:root { - --right-panel-width:190px; -} - /* Page style */ #map-container { height: 100%; @@ -17,54 +13,10 @@ top: 10px; z-index: 99999; column-gap: 10px; + row-gap: 10px; margin-right: 320px; height: fit-content; -} - -@media (max-width: 1820px) { - #toolbar-container { - flex-direction: column; - align-items: start; - row-gap: 10px; - } -} - -#primary-toolbar { - align-items: center; - display: flex; - height: fit-content; - min-width: 650px; -} - -@media (max-width: 1820px) { - #primary-toolbar { - row-gap: 10px; - flex-wrap: wrap; - } -} - -#command-mode-toolbar { - align-items: center; - display: flex; -} - -#app-icon>.ol-select-options { - width: fit-content; -} - -#toolbar-summary { - background-image: url("/images/icon-round.png"); - background-position: 20px 22px; - background-repeat: no-repeat; - background-size: 45px 45px; - display: flex; - flex-direction: column; - padding: 20px; - text-indent: 60px; -} - -#toolbar-summary { - white-space: nowrap; + flex-wrap: wrap; } #connection-status-panel { @@ -72,7 +24,7 @@ font-size: 12px; position: absolute; right: 10px; - width: var( --right-panel-width ); + width: 190px; z-index: 9999; } @@ -84,7 +36,7 @@ position: absolute; right: 10px; row-gap: 10px; - width: var( --right-panel-width ); + width: 190px; z-index: 9999; } @@ -92,40 +44,21 @@ height: fit-content; left: 10px; position: absolute; - top: 80px; - width: 320px; z-index: 9999; } -@media (max-width: 1820px) { - #unit-control-panel { - top: 150px; - } -} - -@media (max-width: 1350px) { - #unit-control-panel { - top: 190px; - } -} - #unit-info-panel { bottom: 20px; font-size: 12px; - left: 10px; position: absolute; - width: fit-content; + width: 600px; z-index: 9999; padding: 24px 30px; display: flex; flex-direction: row; justify-content: space-evenly; -} - -@media (max-width: 1525px) { - #unit-info-panel { - flex-direction: column; - } + right: 210px; + height: 180px; } #info-popup { diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 9edb8f83..aa3c4808 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -11,6 +11,8 @@ @import url("other/contextmenus.css"); @import url("other/popup.css"); +@import url("other/toolbar.css"); + @import url("markers/airbase.css"); @import url("markers/bullseye.css"); diff --git a/client/public/stylesheets/other/toolbar.css b/client/public/stylesheets/other/toolbar.css new file mode 100644 index 00000000..69fe943c --- /dev/null +++ b/client/public/stylesheets/other/toolbar.css @@ -0,0 +1,73 @@ + +#primary-toolbar { + align-items: center; + display: flex; + height: fit-content; +} + +#command-mode-toolbar { + align-items: center; + display: flex; +} + +#app-icon>.ol-select-options { + width: fit-content; +} + +#toolbar-summary { + background-image: url("/images/icon-round.png"); + background-position: 20px 22px; + background-repeat: no-repeat; + background-size: 45px 45px; + display: flex; + flex-direction: column; + padding: 20px; + text-indent: 60px; +} + +#toolbar-summary { + white-space: nowrap; +} + +#toolbar-container>*:nth-child(2)>svg { + display: none; + width: 0px; + height: 0px; +} + +#toolbar-container>*:nth-child(3)>svg { + display: none; +} + +@media (max-width: 1145px) { + #toolbar-container { + flex-direction: column; + align-items: start; + } + + #toolbar-container>*:nth-child(1):not(:hover) { + width: fit-content; + height: fit-content; + } + + #toolbar-container>*:nth-child(1):not(:hover)>*:not(:first-child) { + display: none; + } + + #toolbar-container>*:not(:first-child):not(:hover) { + align-items: center; + justify-content: center; + aspect-ratio: 1/1; + } + + #toolbar-container>*:not(:first-child):not(:hover)>svg { + display: block; + width: 30px; + height: 30px; + filter: invert(); + } + + #toolbar-container>*:not(:first-child):not(:hover)>*:not(:first-child) { + display: none; + } +} diff --git a/client/public/stylesheets/panels/unitcontrol.css b/client/public/stylesheets/panels/unitcontrol.css index 99509503..2888c6ac 100644 --- a/client/public/stylesheets/panels/unitcontrol.css +++ b/client/public/stylesheets/panels/unitcontrol.css @@ -3,9 +3,49 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } #unit-control-panel { + display: flex; + flex-direction: row; + column-gap: 10px; + row-gap: 10px; + width: 350px; +} + +#unit-control-panel>div:nth-child(2) { display: flex; flex-direction: column; row-gap: 10px; + width: 100%; +} + +#unit-control-panel>*:nth-child(1) { + display: none; + padding: 14px; +} + +@media (max-width: 1145px) { + #unit-control-panel>*:nth-child(1) { + display: flex; + } + + #unit-control-panel>*:nth-child(1) svg { + display: flex; + width: 30px; + height: 30px; + filter: invert(100%); + } + + #unit-control-panel:hover>*:nth-child(1) { + display: none; + } + + #unit-control-panel:not(:hover) { + width: fit-content; + } + + #unit-control-panel:not(:hover)>*:nth-child(2), + #unit-control-panel:not(:hover)>*:nth-child(3) { + display: none; + } } #unit-control-panel h3 { diff --git a/client/public/stylesheets/panels/unitinfo.css b/client/public/stylesheets/panels/unitinfo.css index b970dfa4..20e8e0b0 100644 --- a/client/public/stylesheets/panels/unitinfo.css +++ b/client/public/stylesheets/panels/unitinfo.css @@ -3,45 +3,23 @@ min-height: 100px; bottom: 0px; } - -@media (min-width: 1525px) { - #unit-info-panel>.panel-section { - border-right: 1px solid #555; - padding: 0 30px; - } - - #unit-info-panel>.panel-section:first-child { - padding-left: 0px; - } - - #unit-info-panel>.panel-section:last-child { - padding-right: 0px; - } - - #unit-info-panel>.panel-section:last-of-type { - border-right-width: 0; - } + +#unit-info-panel>.panel-section { + border-right: 1px solid #555; + padding: 0 30px; } -@media (max-width: 1525px) { - #unit-info-panel>.panel-section { - border-bottom: 1px solid #555; - padding: 30px 0px; - } - - #unit-info-panel>.panel-section:first-child { - padding-top: 0px; - } - - #unit-info-panel>.panel-section:last-child { - padding-bottom: 0px; - } - - #unit-info-panel>.panel-section:last-of-type { - border-bottom-width: 0; - } +#unit-info-panel>.panel-section:first-child { + padding-left: 0px; } +#unit-info-panel>.panel-section:last-child { + padding-right: 0px; +} + +#unit-info-panel>.panel-section:last-of-type { + border-right-width: 0; +} #general { display: flex; @@ -63,6 +41,10 @@ #unit-name { margin-bottom: 4px; padding: 0px 0; + width: 200px; + text-overflow: ellipsis; + text-wrap: nowrap; + overflow: hidden; } #current-task { @@ -87,6 +69,7 @@ display: flex; flex-direction: column; justify-content: space-between; + width: 300px; } #loadout-silhouette { @@ -101,9 +84,9 @@ column-gap: 8px; display: flex; flex-flow: column nowrap; - max-height: 108px; - padding-right:40px; + height: 100px; row-gap: 6px; + padding-right: 10px; } #loadout-items>* { diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index eb16edde..1ff9293e 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -714,11 +714,8 @@ nav.ol-panel> :last-child { display: flex; flex-direction: column; row-gap: 5px; - position: absolute; height: fit-content; width: fit-content; - left: calc(100% + 10px); - top: 0px; } #rapid-controls button { diff --git a/client/public/themes/olympus/images/icons/gamepad-solid.svg b/client/public/themes/olympus/images/icons/gamepad-solid.svg new file mode 100644 index 00000000..2fc91782 --- /dev/null +++ b/client/public/themes/olympus/images/icons/gamepad-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/icons/person-military-pointing-solid.svg b/client/public/themes/olympus/images/icons/person-military-pointing-solid.svg new file mode 100644 index 00000000..919b3a6f --- /dev/null +++ b/client/public/themes/olympus/images/icons/person-military-pointing-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts index 22f2ebf8..66acf5e6 100644 --- a/client/src/olympusapp.ts +++ b/client/src/olympusapp.ts @@ -192,6 +192,10 @@ export class OlympusApp { this.#unitsManager = new UnitsManager(); this.#weaponsManager = new WeaponsManager(); + // Toolbars + this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar")) + .add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar")); + // Panels this.getPanelsManager() .add("connectionStatus", new ConnectionStatusPanel("connection-status-panel")) @@ -206,11 +210,7 @@ export class OlympusApp { // Popups this.getPopupsManager() .add("infoPopup", new Popup("info-popup")); - - // Toolbars - this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar")) - .add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar")); - + this.#pluginsManager = new PluginsManager(); /* Load the config file from the app server*/ diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 78081126..d69364ae 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -9,6 +9,7 @@ import { Switch } from "../controls/switch"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants"; import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; import { GeneralSettings, Radio, TACAN } from "../interfaces"; +import { PrimaryToolbar } from "../toolbars/primarytoolbar"; export class UnitControlPanel extends Panel { #altitudeSlider: Slider; @@ -136,6 +137,9 @@ export class UnitControlPanel extends Panel { this.#updateRapidControls(); }); + const element = document.getElementById("toolbar-container"); + if (element) + new ResizeObserver(() => this.#calculateTop()).observe(element); this.hide(); } @@ -470,4 +474,10 @@ export class UnitControlPanel extends Panel { button.addEventListener("click", callback); return button; } + + #calculateTop() { + const element = document.getElementById("toolbar-container"); + if (element) + this.getElement().style.top = `${element.offsetTop + element.offsetHeight + 10}px`; + } } \ No newline at end of file diff --git a/client/views/panels/unitcontrol.ejs b/client/views/panels/unitcontrol.ejs index 85f287ac..eecba75d 100644 --- a/client/views/panels/unitcontrol.ejs +++ b/client/views/panels/unitcontrol.ejs @@ -1,113 +1,115 @@ -
+
+
+ +
+

Selected Units

-

Selected Units

+
-
+
+ + +
+
-
- - +
+ +
+

Controls

+
+
+
Speed
+
+
+
+
+
+ +
+
+
+
+
Altitude +
+
+
+
+
+
+ +
+
+
Multiple categories selected
+
+ +
+

Rules of engagement

+
+ +
+
+ +
+

Reaction to threat

+
+ +
+
+ +
+

Radar & ECM

+
+ +
+
+ +
+

Shots scatter

+
+ +
+
+ +
+

Shots intensity

+
+ +
+
+ +
+

Enable tanker

+
+
+ +
+

Airborne Early Warning

+
+
+ +
+

Operate as

+
+
+ +
+

Unit active

+
+
+ +
+

Follow roads

+
+
+ +
+ +
+ + +
- -
- -
-

Controls

-
-
-
Speed
-
-
-
-
-
- -
-
-
-
-
Altitude -
-
-
-
-
-
- -
-
-
Multiple categories selected
-
- -
-

Rules of engagement

-
- -
-
- -
-

Reaction to threat

-
- -
-
- -
-

Radar & ECM

-
- -
-
- -
-

Shots scatter

-
- -
-
- -
-

Shots intensity

-
- -
-
- -
-

Enable tanker

-
-
- -
-

Airborne Early Warning

-
-
- -
-

Operate as

-
-
- -
-

Unit active

-
-
- -
-

Follow roads

-
-
- -
- -
- - - -
-
diff --git a/client/views/toolbars/commandmode.ejs b/client/views/toolbars/commandmode.ejs index 573c6467..1954bc66 100644 --- a/client/views/toolbars/commandmode.ejs +++ b/client/views/toolbars/commandmode.ejs @@ -1,4 +1,5 @@
-
Options
+
Options
- + +