From 10d250e3a55f8445dd4bcd57f39853d1940f80ac Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 18 May 2023 09:09:20 +0200 Subject: [PATCH 01/10] Added basic template spawning --- client/src/units/aircraftdatabase.ts | 2 +- client/src/units/groundunitsdatabase.ts | 18 + client/src/units/unit.ts | 38 +- client/src/units/unitsmanager.ts | 11 +- installer/olympus.iss | 2 +- scripts/OlympusCommand.lua | 79 +- scripts/templates.lua | 1354 +++++++++++++++++++++++ src/core/src/scriptloader.cpp | 1 + 8 files changed, 1467 insertions(+), 38 deletions(-) create mode 100644 scripts/templates.lua diff --git a/client/src/units/aircraftdatabase.ts b/client/src/units/aircraftdatabase.ts index b738b4b6..f2448468 100644 --- a/client/src/units/aircraftdatabase.ts +++ b/client/src/units/aircraftdatabase.ts @@ -322,7 +322,7 @@ export class AircraftDatabase extends UnitDatabase { }, "H-6J": { "name": "H-6J", - "label": "H-6J Badger, + "label": "H-6J Badger", "era": ["Mid Cold War, Late Cold War", "Modern"], "shortLabel": "H6", "loadouts": [ diff --git a/client/src/units/groundunitsdatabase.ts b/client/src/units/groundunitsdatabase.ts index 8610bf91..a880b8d6 100644 --- a/client/src/units/groundunitsdatabase.ts +++ b/client/src/units/groundunitsdatabase.ts @@ -4,6 +4,24 @@ export class GroundUnitsDatabase extends UnitDatabase { constructor() { super(); this.blueprints = { + "SA-2 SAM Battery": { + "name": "SA-2 SAM Battery", + "label": "SA-2 SAM Battery", + "shortLabel": "SA-2 SAM Battery", + "loadouts": [ + { + "fuel": 1, + "items": [ + ], + "roles": [ + "Template" + ], + "code": "", + "name": "Default" + } + ], + "filename": "" + }, "2B11 mortar": { "name": "2B11 mortar", "label": "2B11 mortar", diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 35560fc2..5140b021 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -646,27 +646,25 @@ export class Unit extends Marker { } #drawTargets() { - for (let typeIndex in this.getMissionData().targets) { - for (let index in this.getMissionData().targets[typeIndex]) { - var targetData = this.getMissionData().targets[typeIndex][index]; - var target = getUnitsManager().getUnitByID(targetData.object["id_"]) - if (target != null) { - var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) - var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) + for (let index in this.getMissionData().targets) { + var targetData = this.getMissionData().targets[index]; + var target = getUnitsManager().getUnitByID(targetData.object["id_"]) + if (target != null) { + var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) + var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) - var color; - if (typeIndex === "radar") - color = "#FFFF00"; - else if (typeIndex === "visual") - color = "#FF00FF"; - else if (typeIndex === "rwr") - color = "#00FF00"; - else - color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 }); - targetPolyline.addTo(getMap()); - this.#targetsPolylines.push(targetPolyline) - } + var color; + if (targetData.detectionMethod === "RADAR") + color = "#FFFF00"; + else if (targetData.detectionMethod === "VISUAL") + color = "#FF00FF"; + else if (targetData.detectionMethod === "RWR") + color = "#00FF00"; + else + color = "#FFFFFF"; + var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 }); + targetPolyline.addTo(getMap()); + this.#targetsPolylines.push(targetPolyline) } } } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index d3e5b88d..f5bc6fd8 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -62,6 +62,7 @@ export class UnitsManager { } update(data: UnitsData) { + var updatedUnits: Unit[] = []; Object.keys(data.units) .filter((ID: string) => !(ID in this.#units)) .reduce((timeout: number, ID: string) => { @@ -75,7 +76,15 @@ export class UnitsManager { Object.keys(data.units) .filter((ID: string) => ID in this.#units) - .forEach((ID: string) => this.#units[parseInt(ID)]?.setData(data.units[ID])); + .forEach((ID: string) => { + updatedUnits.push(this.#units[parseInt(ID)]); + this.#units[parseInt(ID)]?.setData(data.units[ID]) + }); + + this.getSelectedUnits().forEach((unit: Unit) => { + if (!updatedUnits.includes(unit)) + unit.setData({}) + }); } selectUnit(ID: number, deselectAllUnits: boolean = true) { diff --git a/installer/olympus.iss b/installer/olympus.iss index 32b1c2fa..dfc937f3 100644 --- a/installer/olympus.iss +++ b/installer/olympus.iss @@ -22,7 +22,7 @@ Source: "..\scripts\OlympusHook.lua"; DestDir: "{app}\Scripts\Hooks"; Flags: ign Source: "..\olympus.json"; DestDir: "{app}\Mods\Services\Olympus"; Flags: onlyifdoesntexist Source: "..\scripts\OlympusCommand.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion Source: "..\scripts\unitPayloads.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion -;Source: "..\scripts\OlympusMission.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion +Source: "..\scripts\templates.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion Source: "..\scripts\mist.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion Source: "..\mod\*"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion recursesubdirs; Source: "..\bin\*.dll"; DestDir: "{app}\Mods\Services\Olympus\bin"; Flags: ignoreversion; diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 80346a9c..68adfc0f 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -232,17 +232,34 @@ end function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) Olympus.debug("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2) local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)) - local unitTable = - { - [1] = + + local unitTable = {} + + if Olympus.hasKey(templates, unitType) then + for idx, value in pairs(templates[unitType].units) do + unitTable[#unitTable + 1] = { + ["type"] = value.name, + ["x"] = spawnLocation.x + value.dx, + ["y"] = spawnLocation.z + value.dy, + ["playerCanDrive"] = true, + ["heading"] = 0, + ["skill"] = "High" + } + end + else + unitTable = { - ["type"] = unitType, - ["x"] = spawnLocation.x, - ["y"] = spawnLocation.z, - ["playerCanDrive"] = true, - ["heading"] = 0, - }, - } + [1] = + { + ["type"] = unitType, + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, + ["playerCanDrive"] = true, + ["heading"] = 0, + ["skill"] = "High" + }, + } + end local countryID = Olympus.getCountryIDByCoalition(coalition) @@ -475,7 +492,26 @@ function Olympus.isArray(t) end return true end - + +function Olympus.hasValue(tab, val) + for index, value in ipairs(tab) do + if value == val then + return true + end + end + + return false +end + +function Olympus.hasKey(tab, key) + for k, value in pairs(tab) do + if k == key then + return true + end + end + + return false +end function Olympus.setMissionData(arg, time) local missionData = {} @@ -503,14 +539,27 @@ function Olympus.setMissionData(arg, time) if groupName ~= nil then local group = Group.getByName(groupName) if group ~= nil then + -- Get the targets detected by the group controller local controller = group:getController() + local controllerTargets = controller:getDetectedTargets() + for index, unit in pairs(group:getUnits()) do + local unitController = unit:getController() local table = {} table["targets"] = {} - table["targets"]["visual"] = controller:getDetectedTargets(1) - table["targets"]["radar"] = controller:getDetectedTargets(4) - table["targets"]["rwr"] = controller:getDetectedTargets(16) - table["targets"]["other"] = controller:getDetectedTargets(2, 8, 32) + + for i, target in ipairs(controllerTargets) do + for det, enum in pairs(Controller.Detection) do + if target.object ~= nil then + local detected = unitController:isTargetDetected(target.object, enum) + + if detected then + target["detectionMethod"] = det + table["targets"][#table["targets"] + 1] = target + end + end + end + end table["hasTask"] = controller:hasTask() diff --git a/scripts/templates.lua b/scripts/templates.lua new file mode 100644 index 00000000..9c7624b1 --- /dev/null +++ b/scripts/templates.lua @@ -0,0 +1,1354 @@ +templates = +{ + ["NASAMS Bty CJTF Blue"] = + { + ["type"] = "vehicle", + ["name"] = "NASAMS Bty CJTF Blue", + ["country"] = 80, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "NASAMS_Command_Post", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [1] + [2] = + { + ["dx"] = -14.042657040991, + ["dy"] = 54.532318175887, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 2.3561944901923, + }, -- end of [2] + [3] = + { + ["dx"] = 51.255698199675, + ["dy"] = -5.1489742484409, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [3] + [4] = + { + ["dx"] = 29.255535502103, + ["dy"] = 55.936583879986, + ["name"] = "NASAMS_LN_C", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [4] + [5] = + { + ["dx"] = 21.063985561486, + ["dy"] = 15.680967029068, + ["name"] = "NASAMS_Radar_MPQ64F1", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [5] + [6] = + { + ["dx"] = -18.499094251951, + ["dy"] = -14.299637664692, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [6] + [7] = + { + ["dx"] = -7.042956299847, + ["dy"] = -18.311686379602, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [7] + }, -- end of ["units"] + }, -- end of ["NASAMS Bty CJTF Blue"] + ["NASAMS Bty CJTF Red"] = + { + ["type"] = "vehicle", + ["name"] = "NASAMS Bty CJTF Red", + ["country"] = 81, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "NASAMS_Command_Post", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [1] + [2] = + { + ["dx"] = -14.042657040991, + ["dy"] = 54.532318175887, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 2.3561944901923, + }, -- end of [2] + [3] = + { + ["dx"] = 51.255698199675, + ["dy"] = -5.1489742484409, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [3] + [4] = + { + ["dx"] = 29.255535502103, + ["dy"] = 55.936583879986, + ["name"] = "NASAMS_LN_C", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [4] + [5] = + { + ["dx"] = 21.063985561486, + ["dy"] = 15.680967029068, + ["name"] = "NASAMS_Radar_MPQ64F1", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [5] + [6] = + { + ["dx"] = -18.499094251951, + ["dy"] = -14.299637664692, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [6] + [7] = + { + ["dx"] = -7.042956299847, + ["dy"] = -18.311686379602, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [7] + }, -- end of ["units"] + }, -- end of ["NASAMS Bty CJTF Red"] + ["NASAMS Bty USA"] = + { + ["type"] = "vehicle", + ["name"] = "NASAMS Bty USA", + ["country"] = 2, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "NASAMS_Command_Post", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [1] + [2] = + { + ["dx"] = -14.042657040991, + ["dy"] = 54.532318175887, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 2.3561944901923, + }, -- end of [2] + [3] = + { + ["dx"] = 51.255698199675, + ["dy"] = -5.1489742484409, + ["name"] = "NASAMS_LN_B", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [3] + [4] = + { + ["dx"] = 29.255535502103, + ["dy"] = 55.936583879986, + ["name"] = "NASAMS_LN_C", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [4] + [5] = + { + ["dx"] = 21.063985561486, + ["dy"] = 15.680967029068, + ["name"] = "NASAMS_Radar_MPQ64F1", + ["skill"] = "High", + ["heading"] = 6.2765352930821, + }, -- end of [5] + [6] = + { + ["dx"] = -18.499094251951, + ["dy"] = -14.299637664692, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [6] + [7] = + { + ["dx"] = -10.08824481722, + ["dy"] = -14.611150606768, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [7] + }, -- end of ["units"] + }, -- end of ["NASAMS Bty USA"] + ["BRITISH BOFORS AA BATTERY"] = + { + ["type"] = "vehicle", + ["name"] = "BRITISH BOFORS AA BATTERY", + ["country"] = 4, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "bofors40", + ["skill"] = "Average", + ["heading"] = 5.6374134839417, + }, -- end of [1] + [2] = + { + ["dx"] = 8.6139882987, + ["dy"] = 8.0418525209971, + ["name"] = "bofors40", + ["skill"] = "Average", + ["heading"] = 5.6374134839417, + }, -- end of [2] + }, -- end of ["units"] + }, -- end of ["BRITISH BOFORS AA BATTERY"] + ["SA-11 SAM Battery"] = + { + ["country"] = 0, + ["type"] = "vehicle", + ["name"] = "SA-11 SAM Battery", + ["units"] = + { + [7] = + { + ["dx"] = 120.44134814, + ["dy"] = -112.000602515, + ["name"] = "ATZ-10", + ["skill"] = "High", + ["heading"] = 2.4958208303519, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "SA-11 Buk SR 9S18M1", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [1] + [2] = + { + ["dx"] = 2.6563550499995, + ["dy"] = 100.877549925, + ["name"] = "SA-11 Buk LN 9A310M1", + ["skill"] = "High", + ["heading"] = 4.7123889803847, + }, -- end of [2] + [4] = + { + ["dx"] = -105.82160159, + ["dy"] = 4.8958284879991, + ["name"] = "SA-11 Buk LN 9A310M1", + ["skill"] = "High", + ["heading"] = 0.034906585039887, + }, -- end of [4] + [8] = + { + ["dx"] = 27.63662248, + ["dy"] = 21.425097783998, + ["name"] = "ZiL-131 APA-80", + ["skill"] = "High", + ["heading"] = 1.5882496193148, + }, -- end of [8] + [9] = + { + ["dx"] = 42.172491459991, + ["dy"] = -20.017069267, + ["name"] = "Ural-4320-31", + ["skill"] = "High", + ["heading"] = 0.05235987755983, + }, -- end of [9] + [5] = + { + ["dx"] = 99.169831179999, + ["dy"] = -3.3463691979996, + ["name"] = "SA-11 Buk LN 9A310M1", + ["skill"] = "High", + ["heading"] = 3.1415926535898, + }, -- end of [5] + [10] = + { + ["dx"] = 42.172491459991, + ["dy"] = -28.818736966998, + ["name"] = "Ural-4320-31", + ["skill"] = "High", + ["heading"] = 0.034906585039887, + }, -- end of [10] + [3] = + { + ["dx"] = -3.9905785699957, + ["dy"] = -102.252741427, + ["name"] = "SA-11 Buk LN 9A310M1", + ["skill"] = "High", + ["heading"] = 1.553343034275, + }, -- end of [3] + [6] = + { + ["dx"] = 129.64229952999, + ["dy"] = -104.569064854, + ["name"] = "ATZ-10", + ["skill"] = "High", + ["heading"] = 2.3212879051525, + }, -- end of [6] + [12] = + { + ["dx"] = -13.972843719996, + ["dy"] = -17.667666831003, + ["name"] = "SA-11 Buk CC 9S470M1", + ["skill"] = "High", + ["heading"] = 1.553343034275, + }, -- end of [12] + [11] = + { + ["dx"] = 22.603219309996, + ["dy"] = 21.425097783998, + ["name"] = "Ural-375 PBU", + ["skill"] = "High", + ["heading"] = 1.5882496193148, + }, -- end of [11] + }, -- end of ["units"] + }, -- end of ["SA-11 SAM Battery"] + ["Patriot site"] = + { + ["country"] = 2, + ["type"] = "vehicle", + ["name"] = "Patriot site", + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "Patriot str", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [1] + [2] = + { + ["dx"] = -19.60004352557, + ["dy"] = 9.2644465620397, + ["name"] = "Patriot EPP", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [2] + [4] = + { + ["dx"] = -52.779034586449, + ["dy"] = -20.804649357509, + ["name"] = "Patriot AMG", + ["skill"] = "High", + ["heading"] = 4.7123889803847, + }, -- end of [4] + [8] = + { + ["dx"] = 302.6260036147, + ["dy"] = -119.57223135463, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [8] + [16] = + { + ["dx"] = -81.188374167279, + ["dy"] = -130.70164152705, + ["name"] = "Hummer", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [16] + [17] = + { + ["dx"] = -95.083442423507, + ["dy"] = -114.72675693111, + ["name"] = "M978 HEMTT Tanker", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [17] + [9] = + { + ["dx"] = 173.84730488189, + ["dy"] = -29.940192911235, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [9] + [18] = + { + ["dx"] = -55.049316178425, + ["dy"] = -162.07189504143, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [18] + [5] = + { + ["dx"] = -35.511640445024, + ["dy"] = 16.23725082296, + ["name"] = "Patriot cp", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [5] + [10] = + { + ["dx"] = 287.4638047662, + ["dy"] = 8.9459295797569, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [10] + [20] = + { + ["dx"] = -26.639011004663, + ["dy"] = 25.619801495515, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [20] + [11] = + { + ["dx"] = 101.78340133674, + ["dy"] = 47.897296695373, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 0.3490658503988, + }, -- end of [11] + [3] = + { + ["dx"] = -35.248201985043, + ["dy"] = -0.8707412587828, + ["name"] = "Patriot ECS", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [3] + [6] = + { + ["dx"] = 136.71587389408, + ["dy"] = -115.36026572896, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [6] + [12] = + { + ["dx"] = 187.95200156058, + ["dy"] = 104.06103102239, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 0.3490658503988, + }, -- end of [12] + [13] = + { + ["dx"] = -31.67915746152, + ["dy"] = 171.21775733319, + ["name"] = "M1097 Avenger", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [13] + [7] = + { + ["dx"] = 233.22284679177, + ["dy"] = -169.50440608741, + ["name"] = "Patriot ln", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [7] + [14] = + { + ["dx"] = 46.357670822879, + ["dy"] = -267.34404672193, + ["name"] = "M1097 Avenger", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [14] + [19] = + { + ["dx"] = 139.89969071567, + ["dy"] = -62.41809110227, + ["name"] = "M 818", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [19] + [15] = + { + ["dx"] = -68.129344288536, + ["dy"] = -146.06991632772, + ["name"] = "Hummer", + ["skill"] = "High", + ["heading"] = 5.9341194567807, + }, -- end of [15] + }, -- end of ["units"] + }, -- end of ["Patriot site"] + ["Rapier SAM Battery Oman"] = + { + ["units"] = + { + [7] = + { + ["dx"] = 36.182838894005, + ["dy"] = 58.774643101002, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "rapier_fsa_blindfire_radar", + ["skill"] = "High", + ["heading"] = 0.013157135472537, + }, -- end of [1] + [2] = + { + ["dx"] = -14.178539628003, + ["dy"] = 10.201632172, + ["name"] = "rapier_fsa_optical_tracker_unit", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [2] + [4] = + { + ["dx"] = -50.662342819996, + ["dy"] = 58.961975773003, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 5.7595865315813, + }, -- end of [4] + [8] = + { + ["dx"] = 32.038867964002, + ["dy"] = 61.828095365003, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [8] + [9] = + { + ["dx"] = 22.442303705, + ["dy"] = 69.897933492997, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [9] + [5] = + { + ["dx"] = -54.812159296009, + ["dy"] = -56.368340474997, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 0.80285145591739, + }, -- end of [5] + [10] = + { + ["dx"] = 52.758722615996, + ["dy"] = 42.634966846002, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [10] + [3] = + { + ["dx"] = -66.224154607, + ["dy"] = 1.2103631389982, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 0.017453292519943, + }, -- end of [3] + [6] = + { + ["dx"] = 49.050959151995, + ["dy"] = 45.906522844001, + ["name"] = "Land_Rover_101_FC", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [6] + [11] = + { + ["dx"] = 39.890602357991, + ["dy"] = 55.066879637001, + ["name"] = "Land_Rover_101_FC", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [11] + }, -- end of ["units"] + ["type"] = "vehicle", + ["name"] = "Rapier SAM Battery Oman", + ["country"] = 73, + }, -- end of ["Rapier SAM Battery Oman"] + ["Insurgents mortar crew with car"] = + { + ["country"] = 17, + ["type"] = "vehicle", + ["name"] = "insurgents mortar crew with car", + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "2B11 mortar", + ["skill"] = "Average", + ["heading"] = 2.3091703583438, + }, -- end of [1] + [2] = + { + ["dx"] = -2.0000000010041, + ["dy"] = -3.1428571399883, + ["name"] = "Soldier AK", + ["skill"] = "Average", + ["heading"] = 2.3091703583438, + }, -- end of [2] + [4] = + { + ["dx"] = 4.2857142849971, + ["dy"] = -3.7142857200233, + ["name"] = "VAZ Car", + ["skill"] = "Average", + ["heading"] = 2.3091703583438, + }, -- end of [4] + [3] = + { + ["dx"] = 2.2857142851426, + ["dy"] = 1.7142857114086, + ["name"] = "Soldier AK", + ["skill"] = "Average", + ["heading"] = 2.3091703583438, + }, -- end of [3] + }, -- end of ["units"] + }, -- end of ["insurgents mortar crew with car"] + ["SA-6 SAM Battery"] = + { + ["country"] = 0, + ["type"] = "vehicle", + ["name"] = "SA-6 SAM Battery", + ["units"] = + { + [7] = + { + ["dx"] = 120.44134814, + ["dy"] = -112.000602515, + ["name"] = "ATZ-10", + ["skill"] = "High", + ["heading"] = 2.4958208303519, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "Kub 1S91 str", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [1] + [2] = + { + ["dx"] = 2.6563550499995, + ["dy"] = 100.877549925, + ["name"] = "Kub 2P25 ln", + ["skill"] = "High", + ["heading"] = 4.7123889803847, + }, -- end of [2] + [4] = + { + ["dx"] = -105.82160159, + ["dy"] = 4.8958284879991, + ["name"] = "Kub 2P25 ln", + ["skill"] = "High", + ["heading"] = 0.034906585039887, + }, -- end of [4] + [8] = + { + ["dx"] = 25.605996860002, + ["dy"] = 20.019633917, + ["name"] = "ZiL-131 APA-80", + ["skill"] = "High", + ["heading"] = 1.5882496193148, + }, -- end of [8] + [9] = + { + ["dx"] = 42.172491459991, + ["dy"] = -20.017069267, + ["name"] = "Ural-4320-31", + ["skill"] = "High", + ["heading"] = 0.05235987755983, + }, -- end of [9] + [5] = + { + ["dx"] = 99.169831179999, + ["dy"] = -3.3463691979996, + ["name"] = "Kub 2P25 ln", + ["skill"] = "High", + ["heading"] = 3.1415926535898, + }, -- end of [5] + [10] = + { + ["dx"] = 42.172491459991, + ["dy"] = -28.818736966998, + ["name"] = "Ural-4320-31", + ["skill"] = "High", + ["heading"] = 0.034906585039887, + }, -- end of [10] + [3] = + { + ["dx"] = -3.9905785699957, + ["dy"] = -102.252741427, + ["name"] = "Kub 2P25 ln", + ["skill"] = "High", + ["heading"] = 1.553343034275, + }, -- end of [3] + [6] = + { + ["dx"] = 129.64229952999, + ["dy"] = -104.569064854, + ["name"] = "ATZ-10", + ["skill"] = "High", + ["heading"] = 2.3212879051525, + }, -- end of [6] + [11] = + { + ["dx"] = 22.038305519993, + ["dy"] = 20.113878987999, + ["name"] = "Ural-375 PBU", + ["skill"] = "High", + ["heading"] = 1.5882496193148, + }, -- end of [11] + }, -- end of ["units"] + }, -- end of ["SA-6 SAM Battery"] + ["FLAK18 BATTERY"] = + { + ["type"] = "vehicle", + ["name"] = "FLAK18 BATTERY", + ["country"] = 66, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "flak18", + ["skill"] = "Average", + ["heading"] = 6.261241987952, + }, -- end of [1] + [2] = + { + ["dx"] = -3.8613997810025, + ["dy"] = 54.05821418362, + ["name"] = "flak18", + ["skill"] = "Average", + ["heading"] = 6.261241987952, + }, -- end of [2] + [3] = + { + ["dx"] = -50.700600202996, + ["dy"] = -8.27913509211, + ["name"] = "flak18", + ["skill"] = "Average", + ["heading"] = 6.261241987952, + }, -- end of [3] + [4] = + { + ["dx"] = -42.560838181002, + ["dy"] = 64.09794160531, + ["name"] = "flak18", + ["skill"] = "Average", + ["heading"] = 6.261241987952, + }, -- end of [4] + }, -- end of ["units"] + }, -- end of ["FLAK18 BATTERY"] + ["Rapier SAM Battery UAE"] = + { + ["units"] = + { + [7] = + { + ["dx"] = 36.182838894005, + ["dy"] = 58.774643101002, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "rapier_fsa_blindfire_radar", + ["skill"] = "High", + ["heading"] = 0.013157135472537, + }, -- end of [1] + [2] = + { + ["dx"] = -14.178539628003, + ["dy"] = 10.201632172, + ["name"] = "rapier_fsa_optical_tracker_unit", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [2] + [4] = + { + ["dx"] = -50.662342819996, + ["dy"] = 58.961975773003, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 5.7595865315813, + }, -- end of [4] + [8] = + { + ["dx"] = 32.038867964002, + ["dy"] = 61.828095365003, + ["name"] = "Land_Rover_101_FC", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [8] + [9] = + { + ["dx"] = 22.442303705, + ["dy"] = 69.897933492997, + ["name"] = "Land_Rover_101_FC", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [9] + [5] = + { + ["dx"] = -54.812159296009, + ["dy"] = -56.368340474997, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 0.80285145591739, + }, -- end of [5] + [10] = + { + ["dx"] = 52.758722615996, + ["dy"] = 42.634966846002, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [10] + [3] = + { + ["dx"] = -66.224154607, + ["dy"] = 1.2103631389982, + ["name"] = "rapier_fsa_launcher", + ["skill"] = "High", + ["heading"] = 0.017453292519943, + }, -- end of [3] + [6] = + { + ["dx"] = 49.050959151995, + ["dy"] = 45.906522844001, + ["name"] = "Land_Rover_101_FC", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [6] + [11] = + { + ["dx"] = 39.890602357991, + ["dy"] = 55.066879637001, + ["name"] = "Land_Rover_109_S3", + ["skill"] = "High", + ["heading"] = 4.1364303272266, + }, -- end of [11] + }, -- end of ["units"] + ["type"] = "vehicle", + ["name"] = "Rapier SAM Battery UAE", + ["country"] = 74, + }, -- end of ["Rapier SAM Battery UAE"] + ["SA-2 SAM Battery"] = + { + ["country"] = 0, + ["type"] = "vehicle", + ["name"] = "SA-2 SAM Battery", + ["units"] = + { + [13] = + { + ["dx"] = -137.35942972999, + ["dy"] = -151.259040031, + ["name"] = "ATMZ-5", + ["skill"] = "High", + ["heading"] = 1.0297442586767, + }, -- end of [13] + [7] = + { + ["dx"] = 80.690318210007, + ["dy"] = 54.289070001003, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 0.92502450355699, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "SNR_75V", + ["skill"] = "High", + ["heading"] = 0.0038885041518015, + }, -- end of [1] + [2] = + { + ["dx"] = 79.099061770001, + ["dy"] = -47.096697396999, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 5.4803338512622, + }, -- end of [2] + [4] = + { + ["dx"] = -1.8276942699886, + ["dy"] = -98.244225791997, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 4.6774823953448, + }, -- end of [4] + [8] = + { + ["dx"] = 29.956672100001, + ["dy"] = 44.636165102005, + ["name"] = "ZIL-131 KUNG", + ["skill"] = "High", + ["heading"] = 4.1713369122664, + }, -- end of [8] + [11] = + { + ["dx"] = -49.941045745189, + ["dy"] = 164.38476072691, + ["name"] = "Ural-4320 APA-5D", + ["skill"] = "High", + ["heading"] = 0.68067840827779, + }, -- end of [11] + [9] = + { + ["dx"] = 25.126949860001, + ["dy"] = 47.889142120999, + ["name"] = "ZIL-131 KUNG", + ["skill"] = "High", + ["heading"] = 4.1713369122664, + }, -- end of [9] + [5] = + { + ["dx"] = -85.254996139993, + ["dy"] = -46.869375048002, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 3.8048177693476, + }, -- end of [5] + [10] = + { + ["dx"] = -61.142498229994, + ["dy"] = 165.65584994, + ["name"] = "p-19 s-125 sr", + ["skill"] = "High", + ["heading"] = 2.2165681500328, + }, -- end of [10] + [14] = + { + ["dx"] = -168.04336334999, + ["dy"] = -84.915399762001, + ["name"] = "Ural-4320T", + ["skill"] = "High", + ["heading"] = 5.4279739737024, + }, -- end of [14] + [3] = + { + ["dx"] = 0.21820687000582, + ["dy"] = 106.800532487, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 1.535889741755, + }, -- end of [3] + [6] = + { + ["dx"] = -87.755541969993, + ["dy"] = 57.926227576005, + ["name"] = "S_75M_Volhov", + ["skill"] = "High", + ["heading"] = 2.3561944901923, + }, -- end of [6] + [12] = + { + ["dx"] = -118.28563314999, + ["dy"] = -171.162132111, + ["name"] = "ATMZ-5", + ["skill"] = "High", + ["heading"] = 0.87266462599716, + }, -- end of [12] + [15] = + { + ["dx"] = -151.45745328999, + ["dy"] = -70.817376204999, + ["name"] = "Ural-4320T", + ["skill"] = "High", + ["heading"] = 5.3407075111026, + }, -- end of [15] + }, -- end of ["units"] + }, -- end of ["SA-2 SAM Battery"] + ["SA-3 SAM Battery"] = + { + ["country"] = 0, + ["type"] = "vehicle", + ["name"] = "SA-3 SAM Battery", + ["units"] = + { + [7] = + { + ["dx"] = -0.19719014999282, + ["dy"] = 16.417675958997, + ["name"] = "ZIL-131 KUNG", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "snr s-125 tr", + ["skill"] = "High", + ["heading"] = 6.2641478001644, + }, -- end of [1] + [2] = + { + ["dx"] = -54.395544479994, + ["dy"] = 20.411364487998, + ["name"] = "5p73 s-125 ln", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [2] + [4] = + { + ["dx"] = -26.404573919994, + ["dy"] = 38.533723629997, + ["name"] = "5p73 s-125 ln", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [4] + [8] = + { + ["dx"] = 6.6801895600074, + ["dy"] = 13.694317328001, + ["name"] = "ZiL-131 APA-80", + ["skill"] = "High", + ["heading"] = 3.1241393610699, + }, -- end of [8] + [9] = + { + ["dx"] = 73.148097120007, + ["dy"] = 24.177887045, + ["name"] = "GAZ-66", + ["skill"] = "High", + ["heading"] = 1.6057029118348, + }, -- end of [9] + [5] = + { + ["dx"] = -28.378296209994, + ["dy"] = -36.108864519003, + ["name"] = "5p73 s-125 ln", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [5] + [10] = + { + ["dx"] = 67.422903450002, + ["dy"] = 24.220744209997, + ["name"] = "GAZ-66", + ["skill"] = "High", + ["heading"] = 1.6406094968747, + }, -- end of [10] + [3] = + { + ["dx"] = -55.65154957, + ["dy"] = -15.115636602, + ["name"] = "5p73 s-125 ln", + ["skill"] = "High", + ["heading"] = 3.1590459461097, + }, -- end of [3] + [6] = + { + ["dx"] = 33.753619990006, + ["dy"] = -73.286112753005, + ["name"] = "p-19 s-125 sr", + ["skill"] = "High", + ["heading"] = 6.2641478001644, + }, -- end of [6] + [12] = + { + ["dx"] = 39.803062310006, + ["dy"] = -66.502451588, + ["name"] = "ZiL-131 APA-80", + ["skill"] = "High", + ["heading"] = 1.6406094968747, + }, -- end of [12] + [11] = + { + ["dx"] = 62.727714700013, + ["dy"] = 24.526952171, + ["name"] = "GAZ-66", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [11] + }, -- end of ["units"] + }, -- end of ["SA-3 SAM Battery"] + ["Hawk SAM Battery"] = + { + ["country"] = 2, + ["type"] = "vehicle", + ["name"] = "Hawk SAM Battery", + ["units"] = + { + [7] = + { + ["dx"] = -143.06468370894, + ["dy"] = -28.566666166706, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 2.6179938779915, + }, -- end of [7] + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "Hawk pcp", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [1] + [2] = + { + ["dx"] = 32.279602998366, + ["dy"] = -40.996889924631, + ["name"] = "Hawk sr", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [2] + [4] = + { + ["dx"] = -73.507952767894, + ["dy"] = -58.130136438762, + ["name"] = "Hawk tr", + ["skill"] = "High", + ["heading"] = 3.8397243543875, + }, -- end of [4] + [8] = + { + ["dx"] = 71.695053443613, + ["dy"] = 58.889218891971, + ["name"] = "Hawk tr", + ["skill"] = "High", + ["heading"] = 0.69813170079773, + }, -- end of [8] + [9] = + { + ["dx"] = 142.58862828931, + ["dy"] = 26.52486901707, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 5.7595865315813, + }, -- end of [9] + [5] = + { + ["dx"] = -53.646780668367, + ["dy"] = -135.33353399258, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 4.7123889803847, + }, -- end of [5] + [10] = + { + ["dx"] = 139.59213643564, + ["dy"] = 111.20097058237, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 0.69813170079773, + }, -- end of [10] + [3] = + { + ["dx"] = -34.076534003809, + ["dy"] = 41.931256096927, + ["name"] = "Hawk cwar", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [3] + [6] = + { + ["dx"] = -137.08263885655, + ["dy"] = -113.8479052746, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 3.8397243543875, + }, -- end of [6] + [11] = + { + ["dx"] = 51.990893861743, + ["dy"] = 137.17585691391, + ["name"] = "Hawk ln", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [11] + }, -- end of ["units"] + }, -- end of ["Hawk SAM Battery"] + ["SA-10 SAM Battery"] = + { + ["type"] = "vehicle", + ["name"] = "SA-10 SAM Battery", + ["country"] = 0, + ["units"] = + { + [1] = + { + ["dx"] = 0, + ["dy"] = 0, + ["name"] = "S-300PS 40B6M tr", + ["skill"] = "High", + ["heading"] = 4.7123889803847, + }, -- end of [1] + [2] = + { + ["dx"] = 0.69314285699511, + ["dy"] = 127.97571428004, + ["name"] = "S-300PS 40B6MD sr", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [2] + [3] = + { + ["dx"] = 23.579234991223, + ["dy"] = 246.55467524601, + ["name"] = "S-300PS 54K6 cp", + ["skill"] = "High", + ["heading"] = 3.1415926535898, + }, -- end of [3] + [4] = + { + ["dx"] = -22.516027817794, + ["dy"] = 246.55467524601, + ["name"] = "S-300PS 64H6E sr", + ["skill"] = "High", + ["heading"] = 3.1415926535898, + }, -- end of [4] + [5] = + { + ["dx"] = 83.349983285123, + ["dy"] = -1.3806866992963, + ["name"] = "S-300PS 5P85C ln", + ["skill"] = "High", + ["heading"] = 3.1415926535898, + }, -- end of [5] + [6] = + { + ["dx"] = 82.498640577192, + ["dy"] = 16.104647497996, + ["name"] = "S-300PS 5P85D ln", + ["skill"] = "High", + ["heading"] = 3.3161255787892, + }, -- end of [6] + [7] = + { + ["dx"] = 82.547616217693, + ["dy"] = -18.227276489837, + ["name"] = "S-300PS 5P85D ln", + ["skill"] = "High", + ["heading"] = 2.9670597283904, + }, -- end of [7] + [8] = + { + ["dx"] = -82.640406328603, + ["dy"] = -0.41562629467808, + ["name"] = "S-300PS 5P85C ln", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [8] + [9] = + { + ["dx"] = -81.939684967569, + ["dy"] = 17.115632734494, + ["name"] = "S-300PS 5P85D ln", + ["skill"] = "High", + ["heading"] = 6.1086523819802, + }, -- end of [9] + [10] = + { + ["dx"] = -81.939684967569, + ["dy"] = -17.99454369233, + ["name"] = "S-300PS 5P85D ln", + ["skill"] = "High", + ["heading"] = 0.17453292519943, + }, -- end of [10] + [11] = + { + ["dx"] = -9.0858776818495, + ["dy"] = 187.67713509151, + ["name"] = "generator_5i57", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [11] + [12] = + { + ["dx"] = 0.83760223048739, + ["dy"] = 187.51811292395, + ["name"] = "generator_5i57", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [12] + [13] = + { + ["dx"] = -59.823818980018, + ["dy"] = 168.63468487991, + ["name"] = "ATZ-5", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [13] + [14] = + { + ["dx"] = -59.823818980018, + ["dy"] = 179.2654343833, + ["name"] = "ATZ-5", + ["skill"] = "High", + ["heading"] = 0, + }, -- end of [14] + [15] = + { + ["dx"] = 20.947679329896, + ["dy"] = -62.811427216162, + ["name"] = "GAZ-66", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [15] + [16] = + { + ["dx"] = 66.751355714747, + ["dy"] = 151.35592090525, + ["name"] = "ATZ-60_Maz", + ["skill"] = "High", + ["heading"] = 3.9269908169872, + }, -- end of [16] + [17] = + { + ["dx"] = 59.63926918729, + ["dy"] = 158.46800743265, + ["name"] = "ATZ-60_Maz", + ["skill"] = "High", + ["heading"] = 3.9269908169872, + }, -- end of [17] + [18] = + { + ["dx"] = -16.327227612433, + ["dy"] = -62.472874663305, + ["name"] = "KAMAZ Truck", + ["skill"] = "High", + ["heading"] = 1.5707963267949, + }, -- end of [18] + }, -- end of ["units"] + }, -- end of ["SA-10 SAM Battery"] +} -- end of templates diff --git a/src/core/src/scriptloader.cpp b/src/core/src/scriptloader.cpp index 850baba6..0509463e 100644 --- a/src/core/src/scriptloader.cpp +++ b/src/core/src/scriptloader.cpp @@ -50,4 +50,5 @@ void registerLuaFunctions(lua_State* L) executeLuaScript(L, modLocation + "\\Scripts\\mist.lua"); executeLuaScript(L, modLocation + "\\Scripts\\OlympusCommand.lua"); executeLuaScript(L, modLocation + "\\Scripts\\unitPayloads.lua"); + executeLuaScript(L, modLocation + "\\Scripts\\templates.lua"); } From 9eb295d9a1c8465634518074a52fd2d46d6016fc Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 18 May 2023 17:48:03 +0200 Subject: [PATCH 02/10] Added auto selection of units in group --- client/demo.js | 2 +- .../public/stylesheets/unitcontrolpanel.css | 37 +++----- client/src/panels/unitcontrolpanel.ts | 6 +- client/src/units/unit.ts | 91 ++++++++++--------- client/views/dialogs.ejs | 2 +- client/views/unitcontrolpanel.ejs | 2 +- 6 files changed, 70 insertions(+), 70 deletions(-) diff --git a/client/demo.js b/client/demo.js index 2c113e9f..2f178cf6 100644 --- a/client/demo.js +++ b/client/demo.js @@ -4,7 +4,7 @@ const DEMO_UNIT_DATA = { baseData: { AI: false, name: "KC-135", - unitName: "Olympus 1-1", + unitName: "Olympus 1-1 aka Mr. Very long name", groupName: "Group 1", alive: true, category: "Aircraft", diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index a0f7b507..c3942ee8 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -21,7 +21,8 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { border-radius: var(--border-radius-md); display: flex; flex-direction: column; - max-height: 136px; + max-height: 215px; + overflow-x: hidden; overflow-y: auto; row-gap: 4px; } @@ -34,19 +35,29 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { height: 32px; padding: 8px 0; position: relative; - width: 100%; + width: calc(100% - 5px); + margin-right: 5px; } #unit-control-panel #selected-units-container button::before { background-color: var( --primary-neutral ); border-radius: 999px; content: attr(data-short-label); - margin: 0 5px; + margin: 2px 4px; padding: 4px 6px; white-space: nowrap; - width: 30px; + width: fit-content; + min-width: 20px; + max-width: 30px; text-overflow: ellipsis; overflow: hidden; + font-size: 10px; +} + +#unit-control-panel #selected-units-container button:hover::before { + max-width: 100%; + text-overflow: unset; + background-color: black; } #unit-control-panel #selected-units-container button[data-coalition="blue"]::before { @@ -71,24 +82,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { width: fit-content; } - -#unit-control-panel #selected-units-container button:hover::after { - overflow: visible; - text-overflow: initial; -} - -#unit-control-panel #selected-units-container button:hover::after { - background-color: var(--background-grey); -} - -#unit-control-panel #selected-units-container button[data-coalition="blue"]:hover::after { - background-color: var(--primary-blue); -} - -#unit-control-panel #selected-units-container button[data-coalition="red"]:hover::after { - background-color: var(--primary-red); -} - #unit-control-panel h4 { margin-bottom: 8px; } diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index a205669e..db1df72d 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -8,12 +8,12 @@ import { UnitDatabase } from "../units/unitdatabase"; import { Panel } from "./panel"; const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; -const reactionsToThreat: string[] = ["None", "Passive", "Evade"]; +const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"]; const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"]; const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"]; -const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; -const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar off, no countermeasures)", "Attack (Radar only for targeting, countermeasures only if attacked/locked)", "Defend (Radar for searching, jammer if locked, countermeasures inside WEZ)", "Always on (Radar and jammer always on, countermeasures when hostile detected)"]; +const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; +const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"]; const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 5140b021..e5bfc296 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -137,8 +137,56 @@ export class Unit extends Marker { return ""; } - /********************** Unit data *************************/ + setSelected(selected: boolean) { + /* Only alive units can be selected. Some units are not selectable (weapons) */ + if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { + this.#selected = selected; + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected"); + if (selected){ + document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); + this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(true)); + } + else { + document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); + this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(false)); + } + } + } + getSelected() { + return this.#selected; + } + + setSelectable(selectable: boolean) { + this.#selectable = selectable; + } + + getSelectable() { + return this.#selectable; + } + + setHotgroup(hotgroup: number | null) { + this.#hotgroup = hotgroup; + } + + getHotgroup() { + return this.#hotgroup; + } + + setHighlighted(highlighted: boolean) { + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); + this.#highlighted = highlighted; + } + + getHighlighted() { + return this.#highlighted; + } + + getGroupMembers() { + return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getBaseData().groupName === this.getBaseData().groupName;}); + } + + /********************** Unit data *************************/ setData(data: UpdateData) { /* Check if data has changed comparing new values to old values */ const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); @@ -237,47 +285,6 @@ export class Unit extends Marker { return this.getData().optionsData; } - setSelected(selected: boolean) { - /* Only alive units can be selected. Some units are not selectable (weapons) */ - if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { - this.#selected = selected; - this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected"); - if (selected) - document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); - else - document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - } - } - - getSelected() { - return this.#selected; - } - - setSelectable(selectable: boolean) { - this.#selectable = selectable; - } - - getSelectable() { - return this.#selectable; - } - - setHotgroup(hotgroup: number | null) { - this.#hotgroup = hotgroup; - } - - getHotgroup() { - return this.#hotgroup; - } - - setHighlighted(highlighted: boolean) { - this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); - this.#highlighted = highlighted; - } - - getHighlighted() { - return this.#highlighted; - } - /********************** Visibility *************************/ updateVisibility() { this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 2421f78a..41ba1d34 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -67,7 +67,7 @@
-
-

Emissions & countermeasures

+

Radar & ECM

From 9cc0058e322c6a9333d062379bb3008f33548694 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 19 May 2023 09:17:11 +0200 Subject: [PATCH 03/10] Minor graphic tweaks --- client/demo.js | 4 ++-- client/public/stylesheets/unitcontrolpanel.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/demo.js b/client/demo.js index 2f178cf6..300b7d85 100644 --- a/client/demo.js +++ b/client/demo.js @@ -5,7 +5,7 @@ const DEMO_UNIT_DATA = { AI: false, name: "KC-135", unitName: "Olympus 1-1 aka Mr. Very long name", - groupName: "Group 1", + groupName: "Group 2", alive: true, category: "Aircraft", }, @@ -284,7 +284,7 @@ const DEMO_UNIT_DATA = { ["7"]:{ baseData: { AI: true, - name: "CVN-75", + name: "CVN-75 Very long name", unitName: "Olympus 1-7", groupName: "Group 1", alive: true, diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/unitcontrolpanel.css index c3942ee8..cb314504 100644 --- a/client/public/stylesheets/unitcontrolpanel.css +++ b/client/public/stylesheets/unitcontrolpanel.css @@ -8,7 +8,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { left: 10px; position: absolute; top: 80px; - width: 260px; + width: 240px; z-index: 1000; } From bf88c9b951114858c0abc6baab4cbcd37a33d2af Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 19 May 2023 14:38:28 +0200 Subject: [PATCH 04/10] Implemented group destination spacing --- client/demo.js | 4 +- client/public/stylesheets/olympus.css | 12 ++ client/src/map/map.ts | 155 ++++++++++++++++++-------- client/src/other/utils.ts | 120 ++++++++++---------- client/src/units/unit.ts | 16 +-- client/src/units/unitsmanager.ts | 56 +++++++++- 6 files changed, 240 insertions(+), 123 deletions(-) diff --git a/client/demo.js b/client/demo.js index 300b7d85..ac750b83 100644 --- a/client/demo.js +++ b/client/demo.js @@ -71,7 +71,7 @@ const DEMO_UNIT_DATA = { AI: true, name: "KC-135", unitName: "Olympus 1-2", - groupName: "Group 1", + groupName: "Group 3", alive: true, category: "Aircraft", }, @@ -114,7 +114,7 @@ const DEMO_UNIT_DATA = { AI: true, name: "2S6 Tunguska", unitName: "Olympus 1-3", - groupName: "Group 1", + groupName: "Group 4", alive: true, category: "GroundUnit", }, diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index a0cd6fc7..3478faa6 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -930,4 +930,16 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { .hotgroup-selector>.unit-hotgroup { display: flex; translate: 0% -300%; +} + +.ol-destination-preview-icon { + width: 52px; + height: 52px; + background-color: var(--secondary-yellow); + pointer-events: none; + border-radius: 999px; +} + +.ol-destination-preview { + pointer-events: none; } \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index a531e33a..a1f18db7 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -9,6 +9,7 @@ import { AirbaseContextMenu } from "../controls/airbasecontextmenu"; import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../missionhandler/airbase"; import { Unit } from "../units/unit"; +import { bearing } from "../other/utils"; // TODO a bit of a hack, this module is provided as pure javascript only require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js") @@ -24,6 +25,13 @@ var temporaryIcon = new L.Icon({ iconAnchor: [26, 26] }); +var destinationPreviewIcon = new L.DivIcon({ + html: `
`, + iconSize: [52, 52], + iconAnchor: [26, 26], + className: "ol-destination-preview" +}) + export class ClickableMiniMap extends MiniMap { constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { super(layer, options); @@ -51,6 +59,10 @@ export class Map extends L.Map { #miniMap: ClickableMiniMap | null = null; #miniMapLayerGroup: L.LayerGroup; #temporaryMarkers: L.Marker[] = []; + #destinationPreviewMarkers: L.Marker[] = []; + #destinationGroupRotation: number = 0; + #computeDestinationRotation: boolean = false; + #destinationRotationCenter: L.LatLng | null = null; #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); @@ -67,53 +79,18 @@ export class Map extends L.Map { this.setLayer("ArcGIS Satellite"); /* Minimap */ - /* Draw the limits of the maps in the minimap*/ - var latlngs = [[ // NTTR - new L.LatLng(39.7982463, -119.985425), - new L.LatLng(34.4037128, -119.7806729), - new L.LatLng(34.3483316, -112.4529351), - new L.LatLng(39.7372411, -112.1130805), - new L.LatLng(39.7982463, -119.985425) - ], - [ // Syria - new L.LatLng(37.3630556, 29.2686111), - new L.LatLng(31.8472222, 29.8975), - new L.LatLng(32.1358333, 42.1502778), - new L.LatLng(37.7177778, 42.3716667), - new L.LatLng(37.3630556, 29.2686111) - ], - [ // Caucasus - new L.LatLng(39.6170191, 27.634935), - new L.LatLng(38.8735863, 47.1423108), - new L.LatLng(47.3907982, 49.3101946), - new L.LatLng(48.3955879, 26.7753625), - new L.LatLng(39.6170191, 27.634935) - ], - [ // Persian Gulf - new L.LatLng(32.9355285, 46.5623682), - new L.LatLng(21.729393, 47.572675), - new L.LatLng(21.8501348, 63.9734737), - new L.LatLng(33.131584, 64.7313594), - new L.LatLng(32.9355285, 46.5623682) - ], - [ // Marianas - new L.LatLng(22.09, 135.0572222), - new L.LatLng(10.5777778, 135.7477778), - new L.LatLng(10.7725, 149.3918333), - new L.LatLng(22.5127778, 149.5427778), - new L.LatLng(22.09, 135.0572222) - ] - ]; - var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 }); this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]); - var miniMapPolyline = new L.Polyline(latlngs, { color: '#202831' }); + var miniMapPolyline = new L.Polyline(this.#getMinimapBoundaries(), { color: '#202831' }); miniMapPolyline.addTo(this.#miniMapLayerGroup); /* Scale */ //@ts-ignore TODO more hacking because the module is provided as a pure javascript module only L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this); + /* Map source dropdown */ + this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()) + /* Init the state machine */ this.#state = IDLE; @@ -127,7 +104,10 @@ export class Map extends L.Map { this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e)); - + this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); + this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); + + /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { ev.detail._element.classList.toggle("off"); document.body.toggleAttribute("data-hide-" + ev.detail.coalition); @@ -144,8 +124,7 @@ export class Map extends L.Map { this.#panToUnit(this.#centerUnit); }); - this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()) - + /* Pan interval */ this.#panInterval = window.setInterval(() => { this.panBy(new L.Point( ((this.#panLeft? -1: 0) + (this.#panRight? 1: 0)) * this.#deafultPanDelta, ((this.#panUp? -1: 0) + (this.#panDown? 1: 0)) * this.#deafultPanDelta)); @@ -205,9 +184,34 @@ export class Map extends L.Map { this.#state = state; if (this.#state === IDLE) { L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled'); + + /* Remove all the destination preview markers */ + this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { + this.removeLayer(marker); + }) + this.#destinationPreviewMarkers = []; + + this.#destinationGroupRotation = 0; + this.#computeDestinationRotation = false; + this.#destinationRotationCenter = null; } else if (this.#state === MOVE_UNIT) { L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled'); + + /* Remove all the exising destination preview markers */ + this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { + this.removeLayer(marker); + }) + this.#destinationPreviewMarkers = []; + + if (getUnitsManager().getSelectedUnits({excludeHumans: true}).length < 20) { + /* Create the unit destination preview markers */ + this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({excludeHumans: true}).map((unit: Unit) => { + var marker = new L.Marker(this.getMouseCoordinates(), {icon: destinationPreviewIcon, interactive: false}); + marker.addTo(this); + return marker; + }) + } } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -328,7 +332,6 @@ export class Map extends L.Map { if (this.#miniMap) this.setView(e.latlng); }) - } getMiniMapLayerGroup() { @@ -433,7 +436,7 @@ export class Map extends L.Map { if (!e.originalEvent.ctrlKey) { getUnitsManager().selectedUnitsClearDestinations(); } - getUnitsManager().selectedUnitsAddDestination(e.latlng) + getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null? this.#destinationRotationCenter: e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation) } } @@ -448,14 +451,31 @@ export class Map extends L.Map { #onMouseDown(e: any) { this.hideAllContextMenus(); + + if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) + { + this.#computeDestinationRotation = true; + this.#destinationRotationCenter = this.getMouseCoordinates(); + } } #onMouseUp(e: any) { + if (this.#state == MOVE_UNIT) + { + this.#computeDestinationRotation = false; + this.#destinationRotationCenter = null; + this.#destinationGroupRotation = 0; + } } #onMouseMove(e: any) { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; + + if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) + this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); + + this.#updateDestinationPreview(e); } #onZoom(e: any) { @@ -467,4 +487,51 @@ export class Map extends L.Map { var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude); this.setView(unitPosition, this.getZoom(), { animate: false }); } + + #getMinimapBoundaries() { + /* Draw the limits of the maps in the minimap*/ + return [[ // NTTR + new L.LatLng(39.7982463, -119.985425), + new L.LatLng(34.4037128, -119.7806729), + new L.LatLng(34.3483316, -112.4529351), + new L.LatLng(39.7372411, -112.1130805), + new L.LatLng(39.7982463, -119.985425) + ], + [ // Syria + new L.LatLng(37.3630556, 29.2686111), + new L.LatLng(31.8472222, 29.8975), + new L.LatLng(32.1358333, 42.1502778), + new L.LatLng(37.7177778, 42.3716667), + new L.LatLng(37.3630556, 29.2686111) + ], + [ // Caucasus + new L.LatLng(39.6170191, 27.634935), + new L.LatLng(38.8735863, 47.1423108), + new L.LatLng(47.3907982, 49.3101946), + new L.LatLng(48.3955879, 26.7753625), + new L.LatLng(39.6170191, 27.634935) + ], + [ // Persian Gulf + new L.LatLng(32.9355285, 46.5623682), + new L.LatLng(21.729393, 47.572675), + new L.LatLng(21.8501348, 63.9734737), + new L.LatLng(33.131584, 64.7313594), + new L.LatLng(32.9355285, 46.5623682) + ], + [ // Marianas + new L.LatLng(22.09, 135.0572222), + new L.LatLng(10.5777778, 135.7477778), + new L.LatLng(10.7725, 149.3918333), + new L.LatLng(22.5127778, 149.5427778), + new L.LatLng(22.09, 135.0572222) + ] + ]; + } + + #updateDestinationPreview(e: any) { + Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null? this.#destinationRotationCenter: this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + if (idx < this.#destinationPreviewMarkers.length) + this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey? latlng: this.getMouseCoordinates()); + }) + } } diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 69c164c7..b3899d41 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -11,48 +11,6 @@ export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) return brng; } - -export function ConvertDDToDMS(D: number, lng: boolean) { - var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N"; - var deg = 0 | (D < 0 ? (D = -D) : D); - var min = 0 | (((D += 1e-9) % 1) * 60); - var sec = (0 | (((D * 60) % 1) * 6000)) / 100; - var dec = Math.round((sec - Math.floor(sec)) * 100); - var sec = Math.floor(sec); - if (lng) - return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; - else - return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; -} - - -export function dataPointMap( container:HTMLElement, data:any) { - - Object.keys( data ).forEach( ( key ) => { - - const val = "" + data[ key ]; // Ensure a string - - container.querySelectorAll( `[data-point="${key}"]`).forEach( el => { - - // We could probably have options here - if ( el instanceof HTMLInputElement ) { - el.value = val; - } else if ( el instanceof HTMLElement ) { - el.innerText = val; - } - }); - - }); - -} - - -export function deg2rad(deg: number) { - var pi = Math.PI; - return deg * (pi / 180); -} - - export function distance(lat1: number, lon1: number, lat2: number, lon2: number) { const R = 6371e3; // metres const φ1 = deg2rad(lat1); // φ, λ in radians @@ -68,6 +26,42 @@ export function distance(lat1: number, lon1: number, lat2: number, lon2: number) return d; } +export function ConvertDDToDMS(D: number, lng: boolean) { + var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N"; + var deg = 0 | (D < 0 ? (D = -D) : D); + var min = 0 | (((D += 1e-9) % 1) * 60); + var sec = (0 | (((D * 60) % 1) * 6000)) / 100; + var dec = Math.round((sec - Math.floor(sec)) * 100); + var sec = Math.floor(sec); + if (lng) + return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; + else + return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; +} + +export function dataPointMap( container:HTMLElement, data:any) { + Object.keys( data ).forEach( ( key ) => { + const val = "" + data[ key ]; // Ensure a string + container.querySelectorAll( `[data-point="${key}"]`).forEach( el => { + // We could probably have options here + if ( el instanceof HTMLInputElement ) { + el.value = val; + } else if ( el instanceof HTMLElement ) { + el.innerText = val; + } + }); + }); +} + +export function deg2rad(deg: number) { + var pi = Math.PI; + return deg * (pi / 180); +} + +export function rad2deg(rad: number) { + var pi = Math.PI; + return rad / (pi / 180); +} export function generateUUIDv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { @@ -76,33 +70,15 @@ export function generateUUIDv4() { }); } - export function keyEventWasInInput( event:KeyboardEvent ) { - const target = event.target; - return ( target instanceof HTMLElement && ( [ "INPUT", "TEXTAREA" ].includes( target.nodeName ) ) ); - } - -export function rad2deg(rad: number) { - var pi = Math.PI; - return rad / (pi / 180); -} - - export function reciprocalHeading(heading: number): number { - - if (heading > 180) { - return heading - 180; - } - - return heading + 180; - + return heading > 180? heading - 180: heading + 180; } - export const zeroAppend = function (num: number, places: number) { var string = String(num); while (string.length < places) { @@ -111,7 +87,6 @@ export const zeroAppend = function (num: number, places: number) { return string; } - export const zeroPad = function (num: number, places: number) { var string = String(num); while (string.length < places) { @@ -120,7 +95,6 @@ export const zeroPad = function (num: number, places: number) { return string; } - export function similarity(s1: string, s2: string) { var longer = s1; var shorter = s2; @@ -160,4 +134,24 @@ export function editDistance(s1: string, s2: string) { costs[s2.length] = lastValue; } return costs[s2.length]; +} + +export function latLngToMercator(lat: number, lng: number): {x: number, y: number} { + var rMajor = 6378137; //Equatorial Radius, WGS84 + var shift = Math.PI * rMajor; + var x = lng * shift / 180; + var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); + y = y * shift / 180; + + return {x: x, y: y}; +} + +export function mercatorToLatLng(x: number, y: number) { + var rMajor = 6378137; //Equatorial Radius, WGS84 + var shift = Math.PI * rMajor; + var lng = x / shift * 180.0; + var lat = y / shift * 180.0; + lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); + + return { lng: lng, lat: lat }; } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index e5bfc296..161d7fd4 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -142,14 +142,11 @@ export class Unit extends Marker { if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected"); - if (selected){ + if (selected) document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); - this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(true)); - } - else { + else document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(false)); - } + this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected)); } } @@ -174,8 +171,11 @@ export class Unit extends Marker { } setHighlighted(highlighted: boolean) { - this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); - this.#highlighted = highlighted; + if (this.#highlighted != highlighted) { + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); + this.#highlighted = highlighted; + this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted)); + } } getHighlighted() { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index f5bc6fd8..cba8fb29 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -3,7 +3,7 @@ import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; import { Unit } from "./unit"; import { cloneUnit } from "../server/server"; import { IDLE, MOVE_UNIT } from "../map/map"; -import { keyEventWasInInput } from "../other/utils"; +import { deg2rad, keyEventWasInInput, latLngToMercator, mercatorToLatLng } from "../other/utils"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -171,8 +171,16 @@ export class UnitsManager { }; /*********************** Actions on selected units ************************/ - selectedUnitsAddDestination(latlng: L.LatLng) { + selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) { var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + + /* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative distances */ + var unitDestinations: {[key: number]: LatLng} = {}; + if (mantainRelativePosition) + unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation); + else + selectedUnits.forEach((unit: Unit) => {unitDestinations[unit.ID] = latlng}); + for (let idx in selectedUnits) { const unit = selectedUnits[idx]; /* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */ @@ -180,11 +188,14 @@ export class UnitsManager { const leader = this.getUnitByID(unit.getFormationData().leaderID) if (leader && leader.getSelected()) leader.addDestination(latlng); - else + else unit.addDestination(latlng); } - else - unit.addDestination(latlng); + else { + if (unit.ID in unitDestinations) + unit.addDestination(unitDestinations[unit.ID]); + } + } this.#showActionMessage(selectedUnits, " new destination added"); } @@ -307,7 +318,7 @@ export class UnitsManager { else if (formation === "Front") { offset.x = 100; offset.y = 0; offset.z = 0; } else offset = undefined; } - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({excludeHumans: true}); var count = 1; var xr = 0; var yr = 1; var zr = -1; var layer = 1; @@ -351,6 +362,39 @@ export class UnitsManager { getHotgroupPanel().refreshHotgroups(); } + selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) + { + var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + /* Compute the center of the group */ + var center = {x: 0, y: 0}; + selectedUnits.forEach((unit: Unit) => { + var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); + center.x += mercator.x / selectedUnits.length; + center.y += mercator.y / selectedUnits.length; + }); + + /* Compute the distances from the center of the group */ + + var unitDestinations: {[key: number]: LatLng} = {}; + selectedUnits.forEach((unit: Unit) => { + var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); + var distancesFromCenter = {dx: mercator.x - center.x, dy: mercator.y - center.y}; + + /* Rotate the distance according to the group rotation */ + var rotatedDistancesFromCenter: {dx: number, dy: number} = {dx: 0, dy: 0}; + rotatedDistancesFromCenter.dx = distancesFromCenter.dx * Math.cos(deg2rad(rotation)) - distancesFromCenter.dy * Math.sin(deg2rad(rotation)); + rotatedDistancesFromCenter.dy = distancesFromCenter.dx * Math.sin(deg2rad(rotation)) + distancesFromCenter.dy * Math.cos(deg2rad(rotation)); + + /* Compute the final position of the unit */ + var destMercator = latLngToMercator(latlng.lat, latlng.lng); // Convert destination point to mercator + var unitMercator = {x: destMercator.x + rotatedDistancesFromCenter.dx, y: destMercator.y + rotatedDistancesFromCenter.dy}; // Compute final position of this unit in mercator coordinates + var unitLatLng = mercatorToLatLng(unitMercator.x, unitMercator.y); + unitDestinations[unit.ID] = new LatLng(unitLatLng.lat, unitLatLng.lng); + }); + + return unitDestinations; + } + /***********************************************/ copyUnits() { this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ From a6583cc317cc19a581e6c2d8869dfa8c10501f9b Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 19 May 2023 21:20:33 +0200 Subject: [PATCH 05/10] Added resources router --- client/app.js | 2 + client/public/stylesheets/olympus.css | 2 +- client/public/stylesheets/units.css | 65 ++--- .../themes/olympus/images/units/aircraft.svg | 4 + client/public/themes/olympus/olympus.css | 226 ------------------ client/public/themes/olympus/theme.css | 199 +++++++++++++++ client/routes/resources.js | 31 +++ client/src/units/unit.ts | 11 +- client/views/index.ejs | 4 +- 9 files changed, 271 insertions(+), 273 deletions(-) create mode 100644 client/public/themes/olympus/images/units/aircraft.svg delete mode 100644 client/public/themes/olympus/olympus.css create mode 100644 client/public/themes/olympus/theme.css create mode 100644 client/routes/resources.js diff --git a/client/app.js b/client/app.js index 8fbd41c1..a10aaf2f 100644 --- a/client/app.js +++ b/client/app.js @@ -9,6 +9,7 @@ var atcRouter = require('./routes/api/atc'); var indexRouter = require('./routes/index'); var uikitRouter = require('./routes/uikit'); var usersRouter = require('./routes/users'); +var resourcesRouter = require('./routes/resources'); var app = express(); @@ -22,6 +23,7 @@ app.use('/', indexRouter); app.use('/api/atc', atcRouter); app.use('/users', usersRouter); app.use('/uikit', uikitRouter); +app.use('/resources', resourcesRouter); app.set('view engine', 'ejs'); diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 3478faa6..b23a92a8 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -4,7 +4,7 @@ @import url("connectionstatuspanel.css"); @import url("contextmenus.css"); @import url("mouseinfopanel.css"); -@import url("units.css"); +@import url("/stylesheets/units.css"); @import url("unitdatatable.css"); @import url("unitcontrolpanel.css"); @import url("unitinfopanel.css"); diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index b48389d6..4bdb7666 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -81,55 +81,34 @@ /****************************** Marker ******************************/ - -[data-object|="unit"] .unit-marker { - background-color: transparent; - background-repeat: no-repeat; - background-size: cover; +.unit-marker { position: absolute; transform-origin: center; z-index: 3; + height: var(--unit-height); + width: var(--unit-width); } -/* Air */ +[data-is-highlighted] .unit-marker { + stroke: white; +} +[data-is-selected] .unit-marker { +} + +[data-coalition="blue"] .unit-marker { +} + +[data-coalition="red"] .unit-marker { +} + +[data-coalition="neutral"] .unit-marker { + +} + +/* Aircraft */ [data-object|="unit-aircraft"] .unit-marker { - background-image: var(--unit-aircraft-marker-neutral-url); - height: var(--unit-aircraft-marker-height); - width: var(--unit-aircraft-marker-width); -} - -[data-object|="unit-aircraft"][data-is-highlighted] .unit-marker { - background-image: var(--unit-aircraft-marker-neutral-hover-url); -} - -[data-object|="unit-aircraft"][data-is-selected] .unit-marker { - background-image: var(--unit-aircraft-marker-neutral-selected-url); -} - -[data-object|="unit-aircraft"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-aircraft-marker-blue-url); -} - -[data-object|="unit-aircraft"][data-coalition="blue"][data-is-highlighted] .unit-marker { - background-image: var(--unit-aircraft-marker-blue-hover-url); -} - -[data-object|="unit-aircraft"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var(--unit-aircraft-marker-blue-selected-url); -} - - -[data-object|="unit-aircraft"][data-coalition="red"] .unit-marker { - background-image: var(--unit-aircraft-marker-red-url); -} - -[data-object|="unit-aircraft"][data-coalition="red"][data-is-highlighted] .unit-marker { - background-image: var(--unit-aircraft-marker-red-hover-url); -} - -[data-object|="unit-aircraft"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var(--unit-aircraft-marker-red-selected-url); + } /* Ground vehicles (not SAMs) */ @@ -561,7 +540,7 @@ [data-object|="unit-aircraft"][data-is-dead] .unit-ammo, [data-object|="unit-aircraft"][data-is-dead]:hover .unit-fuel, [data-object|="unit-aircraft"][data-is-dead]:hover .unit-ammo { - display: none !important; + display: none; } [data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* { diff --git a/client/public/themes/olympus/images/units/aircraft.svg b/client/public/themes/olympus/images/units/aircraft.svg new file mode 100644 index 00000000..f714a9af --- /dev/null +++ b/client/public/themes/olympus/images/units/aircraft.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css deleted file mode 100644 index ed623daa..00000000 --- a/client/public/themes/olympus/olympus.css +++ /dev/null @@ -1,226 +0,0 @@ -:root { - - /** Colours **/ - - /*** Coalition: neutral **/ - --primary-neutral : #949ba7; - --secondary-neutral-outline : #111111; - --secondary-neutral-text : #111111; - - /*** Coalition: blue **/ - --primary-blue : #247be2; - --secondary-blue-outline : #082e44; - --secondary-blue-text : #017DC1; - - /*** Coalition: red **/ - --primary-red : #ff5858; - --secondary-red-outline : #262222; - --secondary-red-text : #D42121; - - --accent-green : #8bff63; - --accent-light-blue : #5ca7ff; - --accent-light-red : #F5B6B6; - - --background-grey : #3d4651; - --background-slate-blue : #363c43; - --background-offwhite : #f2f2f3; - --background-steel : #202831; - - --secondary-dark-steel : #181e25; - --secondary-gunmetal-grey : #2f2f2f; - --secondary-light-grey : #797e83; - --secondary-yellow : #ffd46893; - - --background-hover : #f2f2f333; - - --nav-text : #ECECEC; - - - --ol-select-secondary: #545F6C; - - - /*** General border radii **/ - - --border-radius-xs : 2px; - --border-radius-sm : 5px; - --border-radius-md : 10px; - --border-radius-lg : 15px; - - - /*** Font stuff **/ - --font-weight-bolder : 600; - - - - /*** Navbar ***/ - - --visibility-control-aircraft-visible-url: url( "/themes/olympus/images/visibility_aircraft_visible.svg" ); - --visibility-control-aircraft-hidden-url: url( "/themes/olympus/images/visibility_aircraft_hidden.svg" ); - - --visibility-control-groundunit-visible-url: url( "/themes/olympus/images/visibility_ground_visible.svg" ); - --visibility-control-groundunit-hidden-url: url( "/themes/olympus/images/visibility_ground_hidden.svg" ); - - --visibility-control-sam-visible-url: url( "/themes/olympus/images/visibility_sam_visible.svg" ); - --visibility-control-sam-hidden-url: url( "/themes/olympus/images/visibility_sam_hidden.svg" ); - - --visibility-control-navyunit-visible-url: url( "/themes/olympus/images/visibility_navyunit_visible.svg" ); - --visibility-control-navyunit-hidden-url: url( "/themes/olympus/images/visibility_navyunit_hidden.svg" ); - - --visibility-control-threat-visible-url: url( "/themes/olympus/images/visibility_threat_visible.svg" ); - --visibility-control-threat-hidden-url: url( "/themes/olympus/images/visibility_threat_hidden.svg" ); - - - - /*** Unit marker settings ***/ - - - /*** All markers **/ - --unit-border-radius: var( --border-radius-xs ); - --unit-font-size: 14px; - --unit-font-weight: bolder; - --unit-height: 32px; - --unit-label-border-width: 2px; - --unit-spotlight-fill: var( --secondary-yellow ); - --unit-spotlight-radius: 26px; - --unit-stroke-width: 3px; - --unit-width: 32px; - - - /*** Air units ***/ - --unit-aircraft-ammo-gap: calc( 2px + var( --unit-stroke-width ) ); - --unit-aircraft-ammo-border-radius: 50%; - --unit-aircraft-ammo-border-width: 2px; - --unit-aircraft-ammo-radius: 2px; - --unit-aircraft-ammo-spacing: 2px; - --unit-aircraft-ammo-x:0px; - --unit-aircraft-ammo-y:30px; - --unit-aircraft-fuel-border-width: 2px; - --unit-aircraft-fuel-height: 6px; - --unit-aircraft-fuel-width: 36px; - --unit-aircraft-fuel-x:0px; - --unit-aircraft-fuel-y:22px; - --unit-aircraft-height: 28px; - --unit-aircraft-vvi-width: 4px; - --unit-aircraft-width: var( --unit-aircraft-height ); - - --unit-aircraft-marker-height: 50px; - --unit-aircraft-marker-width: 50px; - - --unit-aircraft-marker-blue-url: url( "/themes/olympus/images/icon_aircraft_blue.svg" ); - --unit-aircraft-marker-blue-hover-url: url( "/themes/olympus/images/icon_aircraft_blue_hover.svg" ); - --unit-aircraft-marker-blue-selected-url: url( "/themes/olympus/images/icon_aircraft_blue_selected.svg" ); - --unit-aircraft-marker-blue-dead-url: url( "/themes/olympus/images/icon_death_blue.svg" ); - - --unit-aircraft-marker-neutral-url: url( "/themes/olympus/images/icon_aircraft_neutral.svg" ); - --unit-aircraft-marker-neutral-hover-url: url( "/themes/olympus/images/icon_aircraft_neutral_hover.svg" ); - --unit-aircraft-marker-neutral-selected-url: url( "/themes/olympus/images/icon_aircraft_neutral_selected.svg" ); - --unit-aircraft-marker-neutral-dead-url: url( "/themes/olympus/images/icon_death_neutral.svg" ); - - --unit-aircraft-marker-red-url: url( "/themes/olympus/images/icon_aircraft_red.svg" ); - --unit-aircraft-marker-red-hover-url: url( "/themes/olympus/images/icon_aircraft_red_hover.svg" ); - --unit-aircraft-marker-red-selected-url: url( "/themes/olympus/images/icon_aircraft_red_selected.svg" ); - --unit-aircraft-marker-red-dead-url: url( "/themes/olympus/images/icon_death_red.svg" ); - - - /*** Air units' states ***/ - - --unit-aircraft-state-height: 50px; - --unit-aircraft-state-width: 50px; - - --unit-aircraft-state-rtb: url( "/themes/olympus/images/state_rtb.svg" ); - --unit-aircraft-state-idle: url( "/themes/olympus/images/state_idle.svg" ); - --unit-aircraft-state-attack: url( "/themes/olympus/images/state_attack.svg" ); - --unit-aircraft-state-follow: url( "/themes/olympus/images/state_follow.svg" ); - --unit-aircraft-state-refuel: url( "/themes/olympus/images/state_refuel.svg" ); - --unit-aircraft-state-human: url( "/themes/olympus/images/state_human.svg" ); - --unit-aircraft-state-dcs: url( "/themes/olympus/images/state_dcs.svg" ); - - /*** Ground ***/ - --unit-groundunit-marker-height: 50px; - --unit-groundunit-marker-width: 50px; - - --unit-groundunit-marker-blue-url: url( "/themes/olympus/images/icon_ground_blue.svg" ); - --unit-groundunit-marker-blue-hover-url: url( "/themes/olympus/images/icon_ground_blue_hover.svg" ); - --unit-groundunit-marker-blue-selected-url: url( "/themes/olympus/images/icon_ground_blue_selected.svg" ); - - --unit-groundunit-marker-neutral-url: url( "/themes/olympus/images/icon_ground_neutral.svg" ); - --unit-groundunit-marker-neutral-hover-url: url( "/themes/olympus/images/icon_ground_neutral_hover.svg" ); - --unit-groundunit-marker-neutral-selected-url: url( "/themes/olympus/images/icon_ground_neutral_selected.svg" ); - - --unit-groundunit-marker-red-url: url( "/themes/olympus/images/icon_ground_red.svg" ); - --unit-groundunit-marker-red-hover-url: url( "/themes/olympus/images/icon_ground_red_hover.svg" ); - --unit-groundunit-marker-red-selected-url: url( "/themes/olympus/images/icon_ground_red_selected.svg" ); - - - /*** SAMs ***/ - --unit-sam-marker-height: 50px; - --unit-sam-marker-width: 50px; - - --unit-sam-marker-blue-url: url( "/themes/olympus/images/icon_aa_blue.svg" ); - --unit-sam-marker-blue-hover-url: url( "/themes/olympus/images/icon_aa_blue_hover.svg" ); - --unit-sam-marker-blue-selected-url: url( "/themes/olympus/images/icon_aa_blue_selected.svg" ); - - --unit-sam-marker-neutral-url: url( "/themes/olympus/images/icon_aa_neutral.svg" ); - --unit-sam-marker-neutral-hover-url: url( "/themes/olympus/images/icon_aa_neutral_hover.svg" ); - --unit-sam-marker-neutral-selected-url: url( "/themes/olympus/images/icon_aa_neutral_selected.svg" ); - - --unit-sam-marker-red-url: url( "/themes/olympus/images/icon_aa_red.svg" ); - --unit-sam-marker-red-hover-url: url( "/themes/olympus/images/icon_aa_red_hover.svg" ); - --unit-sam-marker-red-selected-url: url( "/themes/olympus/images/icon_aa_red_selected.svg" ); - - - /*** navyunit ***/ - --unit-navyunit-marker-height: 50px; - --unit-navyunit-marker-width: 50px; - - --unit-navyunit-marker-blue-url: url( "/themes/olympus/images/icon_ship_blue.svg" ); - --unit-navyunit-marker-blue-hover-url: url( "/themes/olympus/images/icon_ship_blue_hover.svg" ); - --unit-navyunit-marker-blue-selected-url: url( "/themes/olympus/images/icon_ship_blue_selected.svg" ); - - --unit-navyunit-marker-neutral-url: url( "/themes/olympus/images/icon_ship_neutral.svg" ); - --unit-navyunit-marker-neutral-hover-url: url( "/themes/olympus/images/icon_ship_neutral_hover.svg" ); - --unit-navyunit-marker-neutral-selected-url: url( "/themes/olympus/images/icon_ship_neutral_selected.svg" ); - - --unit-navyunit-marker-red-url: url( "/themes/olympus/images/icon_ship_red.svg" ); - --unit-navyunit-marker-red-hover-url: url( "/themes/olympus/images/icon_ship_red_hover.svg" ); - --unit-navyunit-marker-red-selected-url: url( "/themes/olympus/images/icon_ship_red_selected.svg" ); - - - /*** Building ***/ - --unit-building-marker-height: 50px; - --unit-building-marker-width: 50px; - - --unit-building-marker-blue-url: url( "/themes/olympus/images/icon_building_blue.svg" ); - --unit-building-marker-neutral-url: url( "/themes/olympus/images/icon_building_neutral.svg" ); - --unit-building-marker-red-url: url( "/themes/olympus/images/icon_building_red.svg" ); - - - /*** Weapons ***/ - --unit-missile-marker-height: 50px; - --unit-missile-marker-width: 50px; - - --unit-missile-marker-blue-url: url( "/themes/olympus/images/icon_missile_blue.svg" ); - --unit-missile-marker-neutral-url: url( "/themes/olympus/images/icon_missile_neutral.svg" ); - --unit-missile-marker-red-url: url( "/themes/olympus/images/icon_missile_red.svg" ); - - --unit-bomb-marker-height: 50px; - --unit-bomb-marker-width: 50px; - - --unit-bomb-marker-blue-url: url( "/themes/olympus/images/icon_bomb_blue.svg" ); - --unit-bomb-marker-neutral-url: url( "/themes/olympus/images/icon_bomb_neutral.svg" ); - --unit-bomb-marker-red-url: url( "/themes/olympus/images/icon_bomb_red.svg" ); - - - /*** Context menu ***/ - --spawn-aircraft-url: url( "/themes/olympus/images/spawn_aircraft.svg" ); - --spawn-groundunit-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/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css new file mode 100644 index 00000000..06595b96 --- /dev/null +++ b/client/public/themes/olympus/theme.css @@ -0,0 +1,199 @@ +:root { + + /** Colours **/ + + /*** Coalition: neutral **/ + --primary-neutral: #949ba7; + --secondary-neutral-outline: #111111; + --secondary-neutral-text: #111111; + + /*** Coalition: blue **/ + --primary-blue: #247be2; + --secondary-blue-outline: #082e44; + --secondary-blue-text: #017DC1; + + /*** Coalition: red **/ + --primary-red: #ff5858; + --secondary-red-outline: #262222; + --secondary-red-text: #D42121; + + --accent-green: #8bff63; + --accent-light-blue: #5ca7ff; + --accent-light-red: #F5B6B6; + + --background-grey: #3d4651; + --background-slate-blue: #363c43; + --background-offwhite: #f2f2f3; + --background-steel: #202831; + + --secondary-dark-steel: #181e25; + --secondary-gunmetal-grey: #2f2f2f; + --secondary-light-grey: #797e83; + --secondary-yellow: #ffd46893; + + --background-hover: #f2f2f333; + + --nav-text: #ECECEC; + + --ol-select-secondary: #545F6C; + + /*** General border radii **/ + --border-radius-xs: 2px; + --border-radius-sm: 5px; + --border-radius-md: 10px; + --border-radius-lg: 15px; + + /*** Font stuff **/ + --font-weight-bolder: 600; + + /*** Navbar ***/ + --visibility-control-aircraft-visible-url: url("/themes/olympus/images/visibility_aircraft_visible.svg"); + --visibility-control-aircraft-hidden-url: url("/themes/olympus/images/visibility_aircraft_hidden.svg"); + + --visibility-control-groundunit-visible-url: url("/themes/olympus/images/visibility_ground_visible.svg"); + --visibility-control-groundunit-hidden-url: url("/themes/olympus/images/visibility_ground_hidden.svg"); + + --visibility-control-sam-visible-url: url("/themes/olympus/images/visibility_sam_visible.svg"); + --visibility-control-sam-hidden-url: url("/themes/olympus/images/visibility_sam_hidden.svg"); + + --visibility-control-navyunit-visible-url: url("/themes/olympus/images/visibility_navyunit_visible.svg"); + --visibility-control-navyunit-hidden-url: url("/themes/olympus/images/visibility_navyunit_hidden.svg"); + + --visibility-control-threat-visible-url: url("/themes/olympus/images/visibility_threat_visible.svg"); + --visibility-control-threat-hidden-url: url("/themes/olympus/images/visibility_threat_hidden.svg"); + + /*** Unit marker settings ***/ + /*** All markers **/ + --unit-border-radius: var(--border-radius-xs); + --unit-font-size: 14px; + --unit-font-weight: bolder; + --unit-label-border-width: 2px; + --unit-spotlight-fill: var(--secondary-yellow); + --unit-spotlight-radius: 26px; + --unit-stroke-width: 3px; + --unit-height: 50px; + --unit-width: 50px; + + /*** Air units ***/ + --unit-aircraft-ammo-gap: calc(2px + var(--unit-stroke-width)); + --unit-aircraft-ammo-border-radius: 50%; + --unit-aircraft-ammo-border-width: 2px; + --unit-aircraft-ammo-radius: 2px; + --unit-aircraft-ammo-spacing: 2px; + --unit-aircraft-ammo-x: 0px; + --unit-aircraft-ammo-y: 30px; + --unit-aircraft-fuel-border-width: 2px; + --unit-aircraft-fuel-height: 6px; + --unit-aircraft-fuel-width: 36px; + --unit-aircraft-fuel-x: 0px; + --unit-aircraft-fuel-y: 22px; + --unit-aircraft-height: 28px; + --unit-aircraft-vvi-width: 4px; + --unit-aircraft-width: var(--unit-aircraft-height); + + --unit-aircraft-marker-height: 50px; + --unit-aircraft-marker-width: 50px; + + --unit-aircraft-icon: url("/resources/theme/images/units/aircraft.svg"); + + /*** Air units' states ***/ + --unit-aircraft-state-height: 50px; + --unit-aircraft-state-width: 50px; + + --unit-aircraft-state-rtb: url("/themes/olympus/images/state_rtb.svg"); + --unit-aircraft-state-idle: url("/themes/olympus/images/state_idle.svg"); + --unit-aircraft-state-attack: url("/themes/olympus/images/state_attack.svg"); + --unit-aircraft-state-follow: url("/themes/olympus/images/state_follow.svg"); + --unit-aircraft-state-refuel: url("/themes/olympus/images/state_refuel.svg"); + --unit-aircraft-state-human: url("/themes/olympus/images/state_human.svg"); + --unit-aircraft-state-dcs: url("/themes/olympus/images/state_dcs.svg"); + + /*** Ground ***/ + --unit-groundunit-marker-height: 50px; + --unit-groundunit-marker-width: 50px; + + --unit-groundunit-marker-blue-url: url("/themes/olympus/images/icon_ground_blue.svg"); + --unit-groundunit-marker-blue-hover-url: url("/themes/olympus/images/icon_ground_blue_hover.svg"); + --unit-groundunit-marker-blue-selected-url: url("/themes/olympus/images/icon_ground_blue_selected.svg"); + + --unit-groundunit-marker-neutral-url: url("/themes/olympus/images/icon_ground_neutral.svg"); + --unit-groundunit-marker-neutral-hover-url: url("/themes/olympus/images/icon_ground_neutral_hover.svg"); + --unit-groundunit-marker-neutral-selected-url: url("/themes/olympus/images/icon_ground_neutral_selected.svg"); + + --unit-groundunit-marker-red-url: url("/themes/olympus/images/icon_ground_red.svg"); + --unit-groundunit-marker-red-hover-url: url("/themes/olympus/images/icon_ground_red_hover.svg"); + --unit-groundunit-marker-red-selected-url: url("/themes/olympus/images/icon_ground_red_selected.svg"); + + + /*** SAMs ***/ + --unit-sam-marker-height: 50px; + --unit-sam-marker-width: 50px; + + --unit-sam-marker-blue-url: url("/themes/olympus/images/icon_aa_blue.svg"); + --unit-sam-marker-blue-hover-url: url("/themes/olympus/images/icon_aa_blue_hover.svg"); + --unit-sam-marker-blue-selected-url: url("/themes/olympus/images/icon_aa_blue_selected.svg"); + + --unit-sam-marker-neutral-url: url("/themes/olympus/images/icon_aa_neutral.svg"); + --unit-sam-marker-neutral-hover-url: url("/themes/olympus/images/icon_aa_neutral_hover.svg"); + --unit-sam-marker-neutral-selected-url: url("/themes/olympus/images/icon_aa_neutral_selected.svg"); + + --unit-sam-marker-red-url: url("/themes/olympus/images/icon_aa_red.svg"); + --unit-sam-marker-red-hover-url: url("/themes/olympus/images/icon_aa_red_hover.svg"); + --unit-sam-marker-red-selected-url: url("/themes/olympus/images/icon_aa_red_selected.svg"); + + + /*** navyunit ***/ + --unit-navyunit-marker-height: 50px; + --unit-navyunit-marker-width: 50px; + + --unit-navyunit-marker-blue-url: url("/themes/olympus/images/icon_ship_blue.svg"); + --unit-navyunit-marker-blue-hover-url: url("/themes/olympus/images/icon_ship_blue_hover.svg"); + --unit-navyunit-marker-blue-selected-url: url("/themes/olympus/images/icon_ship_blue_selected.svg"); + + --unit-navyunit-marker-neutral-url: url("/themes/olympus/images/icon_ship_neutral.svg"); + --unit-navyunit-marker-neutral-hover-url: url("/themes/olympus/images/icon_ship_neutral_hover.svg"); + --unit-navyunit-marker-neutral-selected-url: url("/themes/olympus/images/icon_ship_neutral_selected.svg"); + + --unit-navyunit-marker-red-url: url("/themes/olympus/images/icon_ship_red.svg"); + --unit-navyunit-marker-red-hover-url: url("/themes/olympus/images/icon_ship_red_hover.svg"); + --unit-navyunit-marker-red-selected-url: url("/themes/olympus/images/icon_ship_red_selected.svg"); + + + /*** Building ***/ + --unit-building-marker-height: 50px; + --unit-building-marker-width: 50px; + + --unit-building-marker-blue-url: url("/themes/olympus/images/icon_building_blue.svg"); + --unit-building-marker-neutral-url: url("/themes/olympus/images/icon_building_neutral.svg"); + --unit-building-marker-red-url: url("/themes/olympus/images/icon_building_red.svg"); + + + /*** Weapons ***/ + --unit-missile-marker-height: 50px; + --unit-missile-marker-width: 50px; + + --unit-missile-marker-blue-url: url("/themes/olympus/images/icon_missile_blue.svg"); + --unit-missile-marker-neutral-url: url("/themes/olympus/images/icon_missile_neutral.svg"); + --unit-missile-marker-red-url: url("/themes/olympus/images/icon_missile_red.svg"); + + --unit-bomb-marker-height: 50px; + --unit-bomb-marker-width: 50px; + + --unit-bomb-marker-blue-url: url("/themes/olympus/images/icon_bomb_blue.svg"); + --unit-bomb-marker-neutral-url: url("/themes/olympus/images/icon_bomb_neutral.svg"); + --unit-bomb-marker-red-url: url("/themes/olympus/images/icon_bomb_red.svg"); + + + /*** Context menu ***/ + --spawn-aircraft-url: url("/themes/olympus/images/spawn_aircraft.svg"); + --spawn-groundunit-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/routes/resources.js b/client/routes/resources.js new file mode 100644 index 00000000..6e6778bb --- /dev/null +++ b/client/routes/resources.js @@ -0,0 +1,31 @@ +const express = require('express'); +const router = express.Router(); +const fs = require('fs'); +const path = require('path'); +const url = require('url'); + +var theme = "olympus"; + +router.get('/theme/*', function (req, res, next) { + if (url.parse(req.url).pathname.slice(-4).toLowerCase() === ".svg") + { + const localPath = path.join(__dirname, '..', 'public', url.parse(req.url).pathname.replace("theme", "themes/" + theme)); + fs.readFile(localPath, function(err, data) { + if (err) { + res.sendStatus(404); + } else { + var svgString = data.toString('utf8'); + for (key in req.query) + svgString = svgString.replaceAll(key, req.query[key]); + + res.header('Content-Type', 'image/svg+xml'); + res.send(svgString); + } + }); + } + else { + res.redirect(req.url.replace("theme", "themes/" + theme)); + } +}); + +module.exports = router; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 161d7fd4..f98dff6e 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -141,7 +141,7 @@ export class Unit extends Marker { /* Only alive units can be selected. Some units are not selectable (weapons) */ if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; - this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected"); + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); if (selected) document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); else @@ -539,6 +539,15 @@ export class Unit extends Marker { var element = this.getElement(); if (element != null) { + /* Set the element styling */ + const unitMarker = element.querySelector(".unit-marker") as HTMLElement; + + const styles = getComputedStyle(document.documentElement); + const primaryBlue = styles.getPropertyValue('--primary-blue'); + + if (unitMarker) + unitMarker.style.backgroundImage = `url("/resources/theme/images/units/aircraft.svg?background-colour=${primaryBlue}")`; + /* Draw the velocity vector */ element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`); diff --git a/client/views/index.ejs b/client/views/index.ejs index afb19752..80ac37c0 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -2,8 +2,8 @@ Olympus client - - + + From 0392c43835a246bd50675d5965eb8ba3571a7dfb Mon Sep 17 00:00:00 2001 From: bobprofisker Date: Sat, 20 May 2023 22:16:41 +0100 Subject: [PATCH 06/10] Add files via upload Wasn't sure if you were serious about wanting the code for how to get all the coalitions out of the mission file, this is how you do it and how you can get a string for what it actually is if that is needed. --- scripts/coals.lua | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 scripts/coals.lua diff --git a/scripts/coals.lua b/scripts/coals.lua new file mode 100644 index 00000000..445e1473 --- /dev/null +++ b/scripts/coals.lua @@ -0,0 +1,47 @@ +coal = {} + +function coal.notify(message, displayFor) + trigger.action.outText(message, displayFor) +end + +function coal.listRed() + coal.coals = env.mission.coalitions.red --solid naming this + coal.notify(mist.utils.tableShow(coal.coals),5) + pickOne = math.random(#coal.coals) + countryIs = country.name[coal.coals[pickOne]] + coal.notify(countryIs,10) +end + +function coal.listBlue() + coal.coals = env.mission.coalitions.blue --solid naming this + coal.notify(mist.utils.tableShow(coal.coals),5) + pickOne = math.random(#coal.coals) + countryIs = country.name[coal.coals[pickOne]] + coal.notify(countryIs,10) +end + +function coal.listNeutrals() + coal.coals = env.mission.coalitions.neutrals --solid naming this + coal.notify(mist.utils.tableShow(coal.coals),5) + pickOne = math.random(#coal.coals) + countryIs = country.name[coal.coals[pickOne]] + coal.notify(countryIs,10) +end + + +do + longRangeShots = missionCommands.addSubMenu("Coal check") + missionCommands.addCommand ("List reds", longRangeShots, coal.listRed) + missionCommands.addCommand ("List blue", longRangeShots, coal.listBlue) + missionCommands.addCommand ("List neutrals", longRangeShots, coal.listNeutrals) + +end + +coal.notify("coals.lua loaded", 2) + + +-- env.mission.coalitions.red +-- env.mission.coalitions.blue +-- env.mission.coalitions.neutrals + +--coalition.getCountryCoalition(countryID) \ No newline at end of file From 32cb147a02ac7e7954b64f9a356d833d1339f2c3 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 22 May 2023 08:22:50 +0200 Subject: [PATCH 07/10] More restyling with svg images --- client/package-lock.json | 26 + client/package.json | 2 + client/public/images/pin.svg | 1 - .../public/images/reference-system-test.svg | 370 ----- client/public/images/reference-system.svg | 218 --- client/public/images/unit.png | Bin 22307 -> 0 bytes client/public/stylesheets/olympus.css | 161 +-- client/public/stylesheets/units.css | 351 +---- .../180.svg} | 0 .../cancel.svg} | 0 .../gas.svg} | 0 .../nothing.svg} | 0 .../rtb.svg} | 0 .../search.svg} | 0 .../{ => buttons/other}/spawn_aircraft.svg | 0 .../{ => buttons/other}/spawn_ground.svg | 0 .../{ => buttons/other}/spawn_smoke.svg | 0 .../olympus/images/buttons/roe/designated.svg | 44 + .../olympus/images/buttons/roe/free.svg | 43 + .../olympus/images/buttons/roe/hold.svg | 43 + .../olympus/images/buttons/roe/return.svg | 43 + .../olympus/images/buttons/threat/evade.svg | 61 + .../images/buttons/threat/manoeuvre.svg | 50 + .../olympus/images/buttons/threat/none.svg | 43 + .../olympus/images/buttons/threat/passive.svg | 64 + .../images/buttons/visibility/aircraft.svg | 5 + .../buttons/visibility/groundunit-other.svg | 13 + .../buttons/visibility/groundunit-sam.svg | 19 + .../images/buttons/visibility/navyunit.svg | 5 + .../images/buttons/visibility/threatring.svg | 5 + .../icons_form_abreast_dark.svg | 0 .../icons_form_abreast_light.svg | 0 .../icons_form_admin_dark.svg | 0 .../icons_form_admin_light.svg | 0 .../icons_form_echelon_dark.svg | 0 .../icons_form_echelon_light.svg | 0 .../icons_form_trail_dark.svg | 0 .../icons_form_trail_light.svg | 0 .../themes/olympus/images/icon_aa_blue.svg | 3 - .../olympus/images/icon_aa_blue_hover.svg | 4 - .../olympus/images/icon_aa_blue_selected.svg | 4 - .../themes/olympus/images/icon_aa_neutral.svg | 3 - .../olympus/images/icon_aa_neutral_hover.svg | 4 - .../images/icon_aa_neutral_selected.svg | 4 - .../themes/olympus/images/icon_aa_red.svg | 3 - .../olympus/images/icon_aa_red_hover.svg | 4 - .../olympus/images/icon_aa_red_selected.svg | 4 - .../olympus/images/icon_airbase_neutral.svg | 83 -- .../olympus/images/icon_airbase_red.svg | 83 -- .../olympus/images/icon_aircraft_blue.svg | 4 - .../images/icon_aircraft_blue_hover.svg | 4 - .../images/icon_aircraft_blue_selected.svg | 4 - .../olympus/images/icon_aircraft_neutral.svg | 4 - .../images/icon_aircraft_neutral_hover.svg | 4 - .../images/icon_aircraft_neutral_selected.svg | 4 - .../olympus/images/icon_aircraft_red.svg | 4 - .../images/icon_aircraft_red_hover.svg | 4 - .../images/icon_aircraft_red_selected.svg | 4 - .../themes/olympus/images/icon_bomb_blue.svg | 3 - .../olympus/images/icon_bomb_neutral.svg | 3 - .../themes/olympus/images/icon_bomb_red.svg | 3 - .../images/icon_building_blue_hover.svg | 5 - .../images/icon_building_blue_selected.svg | 5 - .../olympus/images/icon_building_neutral.svg | 4 - .../images/icon_building_neutral_hover.svg | 5 - .../images/icon_building_neutral_selected.svg | 5 - .../olympus/images/icon_building_red.svg | 4 - .../images/icon_building_red_hover.svg | 5 - .../images/icon_building_red_selected.svg | 5 - .../themes/olympus/images/icon_death_blue.svg | 3 - .../olympus/images/icon_death_neutral.svg | 3 - .../themes/olympus/images/icon_death_red.svg | 3 - .../olympus/images/icon_ground_blue_hover.svg | 5 - .../images/icon_ground_blue_selected.svg | 5 - .../olympus/images/icon_ground_neutral.svg | 4 - .../images/icon_ground_neutral_hover.svg | 5 - .../images/icon_ground_neutral_selected.svg | 5 - .../themes/olympus/images/icon_ground_red.svg | 4 - .../olympus/images/icon_ground_red_hover.svg | 5 - .../images/icon_ground_red_selected.svg | 5 - .../themes/olympus/images/icon_hold_blue.svg | 4 - .../themes/olympus/images/icon_hold_red.svg | 4 - .../olympus/images/icon_missile_neutral.svg | 5 - .../olympus/images/icon_missile_red.svg | 5 - .../olympus/images/icon_navyuni_blue.svg | 4 - .../olympus/images/icon_navyunit_neutral.svg | 4 - .../olympus/images/icon_navyunit_red.svg | 4 - .../themes/olympus/images/icon_rtb_blue.svg | 4 - .../olympus/images/icon_rtb_neutral.svg | 4 - .../themes/olympus/images/icon_sam_blue.svg | 3 - .../olympus/images/icon_sam_neutral.svg | 3 - .../themes/olympus/images/icon_sam_red.svg | 3 - .../themes/olympus/images/icon_ship_blue.svg | 4 - .../olympus/images/icon_ship_blue_hover.svg | 5 - .../images/icon_ship_blue_selected.svg | 5 - .../olympus/images/icon_ship_neutral.svg | 4 - .../images/icon_ship_neutral_hover.svg | 5 - .../images/icon_ship_neutral_selected.svg | 5 - .../themes/olympus/images/icon_ship_red.svg | 4 - .../olympus/images/icon_ship_red_hover.svg | 5 - .../olympus/images/icon_ship_red_selected.svg | 5 - .../olympus/images/icons_actions_180_dark.svg | 4 - .../images/icons_actions_cancel_dark.svg | 3 - .../olympus/images/icons_actions_gas_dark.svg | 3 - .../images/icons_actions_nothing_dark.svg | 3 - .../olympus/images/icons_actions_rtb_dark.svg | 3 - .../images/icons_actions_search_dark.svg | 3 - .../olympus/images/icons_roe_attack_dark.svg | 10 - .../olympus/images/icons_roe_attack_light.svg | 10 - .../olympus/images/icons_roe_defend_dark.svg | 3 - .../olympus/images/icons_roe_defend_light.svg | 3 - .../olympus/images/icons_roe_free_dark.svg | 3 - .../olympus/images/icons_roe_free_light.svg | 3 - .../olympus/images/icons_roe_return_dark.svg | 3 - .../olympus/images/icons_roe_return_light.svg | 3 - .../olympus/images/icons_roe_stop_dark.svg | 3 - .../olympus/images/icons_roe_stop_light.svg | 3 - .../olympus/images/icons_roe_target_dark.svg | 3 - .../olympus/images/icons_roe_target_light.svg | 3 - .../olympus/images/icons_threat_cms_dark.svg | 8 - .../olympus/images/icons_threat_cms_light.svg | 8 - .../images/icons_threat_defend_dark.svg | 7 - .../images/icons_threat_defend_light.svg | 7 - .../images/icons_threat_nothing_dark.svg | 3 - .../images/icons_threat_nothing_light.svg | 3 - .../images/icons_threat_protect_dark.svg | 3 - .../images/icons_threat_protect_light.svg | 3 - .../images/icons_threat_retreat_dark.svg | 3 - .../images/icons_threat_retreat_light.svg | 3 - .../airbase.svg} | 0 .../images/{ => other}/check_square.svg | 0 .../images/{ => other}/chevron-down.svg | 0 .../{ => other}/icons_misc_brush_blue.svg | 0 .../{ => other}/icons_misc_brush_dark.svg | 0 .../{ => other}/icons_misc_brush_light.svg | 0 .../{ => other}/icons_misc_gas_blue.svg | 0 .../{ => other}/icons_misc_gas_dark.svg | 0 .../{ => other}/icons_misc_gas_light.svg | 0 .../{ => other}/icons_misc_map_blue.svg | 0 .../{ => other}/icons_misc_map_dark.svg | 0 .../{ => other}/icons_misc_map_light.svg | 0 .../{ => other}/icons_misc_plane_blue.svg | 0 .../{ => other}/icons_misc_plane_dark.svg | 0 .../{ => other}/icons_misc_plane_light.svg | 0 .../{ => other}/icons_misc_settings_blue.svg | 0 .../{ => other}/icons_misc_settings_dark.svg | 0 .../{ => other}/icons_misc_settings_light.svg | 0 .../{ => other}/icons_misc_visible_blue.svg | 0 .../{ => other}/icons_misc_visible_dark.svg | 0 .../{ => other}/icons_misc_visible_light.svg | 0 .../olympus/images/{ => other}/map_source.svg | 0 .../themes/olympus/images/state_idle.svg | 4 - .../themes/olympus/images/state_rtb.svg | 4 - .../{state_attack.svg => states/attack.svg} | 0 .../images/{state_dcs.svg => states/dcs.svg} | 0 .../{state_follow.svg => states/follow.svg} | 0 .../{state_human.svg => states/human.svg} | 0 .../idle.svg} | 0 .../{state_refuel.svg => states/refuel.svg} | 0 .../{icon_rtb_red.svg => states/rtb.svg} | 0 .../themes/olympus/images/task_tanker.svg | 1256 ----------------- .../themes/olympus/images/units/aircraft.svg | 2 +- .../themes/olympus/images/units/bomb.svg | 3 + .../themes/olympus/images/units/death.svg | 3 + .../groundunit-other.svg} | 3 +- .../olympus/images/units/groundunit-sam.svg | 4 + .../missile.svg} | 2 +- .../themes/olympus/images/units/navyunit.svg | 5 + .../static.svg} | 3 +- .../images/visibility_aircraft_hidden.svg | 4 - .../images/visibility_aircraft_visible.svg | 5 - .../images/visibility_ground_hidden.svg | 12 - .../images/visibility_ground_visible.svg | 13 - .../images/visibility_navyunit_hidden.svg | 4 - .../images/visibility_navyunit_visible.svg | 5 - .../olympus/images/visibility_sam_hidden.svg | 18 - .../olympus/images/visibility_sam_visible.svg | 19 - .../images/visibility_threat_hidden.svg | 4 - .../images/visibility_threat_visible.svg | 5 - client/public/themes/olympus/theme.css | 133 +- client/routes/resources.js | 23 +- client/src/index.ts | 3 + client/src/map/map.ts | 1 + client/src/panels/unitcontrolpanel.ts | 11 +- client/src/units/unit.ts | 27 +- client/views/index.ejs | 2 + client/views/navbar.ejs | 48 +- client/views/uikit.ejs | 12 +- 188 files changed, 627 insertions(+), 3113 deletions(-) delete mode 100644 client/public/images/pin.svg delete mode 100644 client/public/images/reference-system-test.svg delete mode 100644 client/public/images/reference-system.svg delete mode 100644 client/public/images/unit.png rename client/public/themes/olympus/images/{icons_actions_180_light.svg => actions/180.svg} (100%) rename client/public/themes/olympus/images/{icons_actions_cancel_light.svg => actions/cancel.svg} (100%) rename client/public/themes/olympus/images/{icons_actions_gas_light.svg => actions/gas.svg} (100%) rename client/public/themes/olympus/images/{icons_actions_nothing_light.svg => actions/nothing.svg} (100%) rename client/public/themes/olympus/images/{icons_actions_rtb_light.svg => actions/rtb.svg} (100%) rename client/public/themes/olympus/images/{icons_actions_search_light.svg => actions/search.svg} (100%) rename client/public/themes/olympus/images/{ => buttons/other}/spawn_aircraft.svg (100%) rename client/public/themes/olympus/images/{ => buttons/other}/spawn_ground.svg (100%) rename client/public/themes/olympus/images/{ => buttons/other}/spawn_smoke.svg (100%) create mode 100644 client/public/themes/olympus/images/buttons/roe/designated.svg create mode 100644 client/public/themes/olympus/images/buttons/roe/free.svg create mode 100644 client/public/themes/olympus/images/buttons/roe/hold.svg create mode 100644 client/public/themes/olympus/images/buttons/roe/return.svg create mode 100644 client/public/themes/olympus/images/buttons/threat/evade.svg create mode 100644 client/public/themes/olympus/images/buttons/threat/manoeuvre.svg create mode 100644 client/public/themes/olympus/images/buttons/threat/none.svg create mode 100644 client/public/themes/olympus/images/buttons/threat/passive.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/aircraft.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/navyunit.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/threatring.svg rename client/public/themes/olympus/images/{ => formations}/icons_form_abreast_dark.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_abreast_light.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_admin_dark.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_admin_light.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_echelon_dark.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_echelon_light.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_trail_dark.svg (100%) rename client/public/themes/olympus/images/{ => formations}/icons_form_trail_light.svg (100%) delete mode 100644 client/public/themes/olympus/images/icon_aa_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_blue_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_blue_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_neutral_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_neutral_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_red.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_red_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aa_red_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_airbase_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_airbase_red.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_blue_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_blue_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_neutral_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_neutral_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_red.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_red_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_aircraft_red_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_bomb_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_bomb_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_bomb_red.svg delete mode 100644 client/public/themes/olympus/images/icon_building_blue_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_building_blue_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_building_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_building_neutral_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_building_neutral_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_building_red.svg delete mode 100644 client/public/themes/olympus/images/icon_building_red_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_building_red_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_death_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_death_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_death_red.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_blue_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_blue_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_neutral_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_neutral_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_red.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_red_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ground_red_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_hold_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_hold_red.svg delete mode 100644 client/public/themes/olympus/images/icon_missile_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_missile_red.svg delete mode 100644 client/public/themes/olympus/images/icon_navyuni_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_navyunit_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_navyunit_red.svg delete mode 100644 client/public/themes/olympus/images/icon_rtb_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_rtb_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_sam_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_sam_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_sam_red.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_blue.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_blue_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_blue_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_neutral.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_neutral_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_neutral_selected.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_red.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_red_hover.svg delete mode 100644 client/public/themes/olympus/images/icon_ship_red_selected.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_180_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_cancel_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_gas_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_nothing_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_rtb_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_actions_search_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_attack_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_attack_light.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_defend_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_defend_light.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_free_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_free_light.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_return_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_return_light.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_stop_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_stop_light.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_target_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_roe_target_light.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_cms_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_cms_light.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_defend_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_defend_light.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_nothing_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_nothing_light.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_protect_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_protect_light.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_retreat_dark.svg delete mode 100644 client/public/themes/olympus/images/icons_threat_retreat_light.svg rename client/public/themes/olympus/images/{icon_airbase_blue.svg => mission/airbase.svg} (100%) rename client/public/themes/olympus/images/{ => other}/check_square.svg (100%) rename client/public/themes/olympus/images/{ => other}/chevron-down.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_brush_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_brush_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_brush_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_gas_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_gas_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_gas_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_map_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_map_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_map_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_plane_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_plane_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_plane_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_settings_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_settings_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_settings_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_visible_blue.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_visible_dark.svg (100%) rename client/public/themes/olympus/images/{ => other}/icons_misc_visible_light.svg (100%) rename client/public/themes/olympus/images/{ => other}/map_source.svg (100%) delete mode 100644 client/public/themes/olympus/images/state_idle.svg delete mode 100644 client/public/themes/olympus/images/state_rtb.svg rename client/public/themes/olympus/images/{state_attack.svg => states/attack.svg} (100%) rename client/public/themes/olympus/images/{state_dcs.svg => states/dcs.svg} (100%) rename client/public/themes/olympus/images/{state_follow.svg => states/follow.svg} (100%) rename client/public/themes/olympus/images/{state_human.svg => states/human.svg} (100%) rename client/public/themes/olympus/images/{icon_hold_neutral.svg => states/idle.svg} (100%) rename client/public/themes/olympus/images/{state_refuel.svg => states/refuel.svg} (100%) rename client/public/themes/olympus/images/{icon_rtb_red.svg => states/rtb.svg} (100%) delete mode 100644 client/public/themes/olympus/images/task_tanker.svg create mode 100644 client/public/themes/olympus/images/units/bomb.svg create mode 100644 client/public/themes/olympus/images/units/death.svg rename client/public/themes/olympus/images/{icon_ground_blue.svg => units/groundunit-other.svg} (52%) create mode 100644 client/public/themes/olympus/images/units/groundunit-sam.svg rename client/public/themes/olympus/images/{icon_missile_blue.svg => units/missile.svg} (66%) create mode 100644 client/public/themes/olympus/images/units/navyunit.svg rename client/public/themes/olympus/images/{icon_building_blue.svg => units/static.svg} (58%) delete mode 100644 client/public/themes/olympus/images/visibility_aircraft_hidden.svg delete mode 100644 client/public/themes/olympus/images/visibility_aircraft_visible.svg delete mode 100644 client/public/themes/olympus/images/visibility_ground_hidden.svg delete mode 100644 client/public/themes/olympus/images/visibility_ground_visible.svg delete mode 100644 client/public/themes/olympus/images/visibility_navyunit_hidden.svg delete mode 100644 client/public/themes/olympus/images/visibility_navyunit_visible.svg delete mode 100644 client/public/themes/olympus/images/visibility_sam_hidden.svg delete mode 100644 client/public/themes/olympus/images/visibility_sam_visible.svg delete mode 100644 client/public/themes/olympus/images/visibility_threat_hidden.svg delete mode 100644 client/public/themes/olympus/images/visibility_threat_visible.svg diff --git a/client/package-lock.json b/client/package-lock.json index 55970983..b4457d1c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -24,12 +24,14 @@ }, "devDependencies": { "@babel/preset-env": "^7.21.4", + "@iconfu/svg-inject": "^1.2.3", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", "babelify": "^10.0.0", "browserify": "^17.0.0", "concurrently": "^7.6.0", + "cp": "^0.2.0", "esmify": "^2.1.1", "express-basic-auth": "^1.2.1", "nodemon": "^2.0.20", @@ -1769,6 +1771,12 @@ "node": ">=6.9.0" } }, + "node_modules/@iconfu/svg-inject": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", + "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -2881,6 +2889,12 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cp": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cp/-/cp-0.2.0.tgz", + "integrity": "sha512-4ftCvShHjIZG/zzomHyunNpBof3sOFTTmU6s6q9DdqAL/ANqrKV3pr6Z6kVfBI4hjn59DFLImrBqn7GuuMqSZA==", + "dev": true + }, "node_modules/create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -6893,6 +6907,12 @@ "to-fast-properties": "^2.0.0" } }, + "@iconfu/svg-inject": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", + "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==", + "dev": true + }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -7798,6 +7818,12 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cp": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cp/-/cp-0.2.0.tgz", + "integrity": "sha512-4ftCvShHjIZG/zzomHyunNpBof3sOFTTmU6s6q9DdqAL/ANqrKV3pr6Z6kVfBI4hjn59DFLImrBqn7GuuMqSZA==", + "dev": true + }, "create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", diff --git a/client/package.json b/client/package.json index fce35cdf..f9cb7d2a 100644 --- a/client/package.json +++ b/client/package.json @@ -26,12 +26,14 @@ }, "devDependencies": { "@babel/preset-env": "^7.21.4", + "@iconfu/svg-inject": "^1.2.3", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", "babelify": "^10.0.0", "browserify": "^17.0.0", "concurrently": "^7.6.0", + "cp": "^0.2.0", "esmify": "^2.1.1", "express-basic-auth": "^1.2.1", "nodemon": "^2.0.20", diff --git a/client/public/images/pin.svg b/client/public/images/pin.svg deleted file mode 100644 index 19e92ec2..00000000 --- a/client/public/images/pin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/images/reference-system-test.svg b/client/public/images/reference-system-test.svg deleted file mode 100644 index ff6f152a..00000000 --- a/client/public/images/reference-system-test.svg +++ /dev/null @@ -1,370 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - diff --git a/client/public/images/reference-system.svg b/client/public/images/reference-system.svg deleted file mode 100644 index cd71d782..00000000 --- a/client/public/images/reference-system.svg +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/client/public/images/unit.png b/client/public/images/unit.png deleted file mode 100644 index ae72bc19198e8cd98442381e7463d1d3a2504822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22307 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 :last-child { border: none; height: 32px; width: 32px; + padding: 0px; } -#unit-visibility-control-aircraft { - background-image: var(--visibility-control-aircraft-visible-url); +#unit-visibility-control button svg { + pointer-events: none; } -body[data-hide-aircraft] #unit-visibility-control-aircraft { - background-image: var(--visibility-control-aircraft-hidden-url); +#unit-visibility-control button svg .background { + fill: white; } -#unit-visibility-control-groundunit { - background-image: var(--visibility-control-groundunit-visible-url); +#unit-visibility-control button.off svg .foreground { + fill: var(--background-steel); } -body[data-hide-groundunit] #unit-visibility-control-groundunit { - background-image: var(--visibility-control-groundunit-hidden-url); +#unit-visibility-control button.off svg .background { + fill: none; } -#unit-visibility-control-sam { - background-image: var(--visibility-control-sam-visible-url); -} - -body[data-hide-sam] #unit-visibility-control-sam { - background-image: var(--visibility-control-sam-hidden-url); -} - -#unit-visibility-control-threat { - background-image: var(--visibility-control-threat-visible-url); -} - -body[data-hide-threat] #unit-visibility-control-threat { - background-image: var(--visibility-control-threat-hidden-url); -} - -#unit-visibility-control-navyunit { - background-image: var(--visibility-control-navyunit-visible-url); -} - -body[data-hide-navyunit] #unit-visibility-control-navyunit { - background-image: var(--visibility-control-navyunit-hidden-url); +#unit-visibility-control button.off svg .foreground { + fill: white; } #atc-navbar-control { @@ -642,9 +623,16 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { padding: 4px; } -#roe-buttons-container button { +#roe-buttons-container button, +#reaction-to-threat-buttons-container button, +#emissions-countermeasures-buttons-container button { + display: flex; background-color: transparent; border: 1px solid var(--accent-light-blue); + height: 30px; + width: 30px; + align-items: center; + justify-content: center; } #roe-buttons-container button.selected, @@ -654,113 +642,10 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { border-color: white; } -#roe-buttons-container button::before, -#reaction-to-threat-buttons-container button::before, -#emissions-countermeasures-buttons-container button::before { - background-position: center; - background-repeat: no-repeat; - content: ""; - display: block; - height: 24px; - width: 24px; -} - -#roe-buttons-container button[value="Hold"]::before { - background-image: url("/themes/olympus/images/icons_roe_stop_light.svg"); -} - -#roe-buttons-container button[value="Hold"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_stop_dark.svg"); -} - -/**/ -#roe-buttons-container button[value="Return"]::before { - background-image: url("/themes/olympus/images/icons_roe_defend_light.svg"); -} - -#roe-buttons-container button[value="Return"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_defend_dark.svg"); -} - -/**/ -#roe-buttons-container button[value="Designated"]::before { - background-image: url("/themes/olympus/images/icons_roe_target_light.svg"); -} - -#roe-buttons-container button[value="Designated"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_target_dark.svg"); -} - -/**/ -#roe-buttons-container button[value="Free"]::before { - background-image: url("/themes/olympus/images/icons_roe_free_light.svg"); -} - -#roe-buttons-container button[value="Free"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_free_dark.svg"); -} - -/****************************************************************************************/ -#reaction-to-threat-buttons-container button[value="None"]::before { - background-image: url("/themes/olympus/images/icons_threat_nothing_light.svg"); -} - -#reaction-to-threat-buttons-container button[value="None"].selected::before { - background-image: url("/themes/olympus/images/icons_threat_nothing_dark.svg"); -} - -/**/ -#reaction-to-threat-buttons-container button[value="Passive"]::before { - background-image: url("/themes/olympus/images/icons_threat_cms_light.svg"); -} - -#reaction-to-threat-buttons-container button[value="Passive"].selected::before { - background-image: url("/themes/olympus/images/icons_threat_cms_dark.svg"); -} - -/**/ -#reaction-to-threat-buttons-container button[value="Evade"]::before { - background-image: url("/themes/olympus/images/icons_threat_defend_light.svg"); -} - -#reaction-to-threat-buttons-container button[value="Evade"].selected::before { - background-image: url("/themes/olympus/images/icons_threat_defend_dark.svg"); -} - -/****************************************************************************************/ -#emissions-countermeasures-buttons-container button[value="Silent"]::before { - background-image: url("/themes/olympus/images/icons_roe_stop_light.svg"); -} - -#emissions-countermeasures-buttons-container button[value="Silent"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_stop_dark.svg"); -} - -/**/ -#emissions-countermeasures-buttons-container button[value="Defend"]::before { - background-image: url("/themes/olympus/images/icons_roe_defend_light.svg"); -} - -#emissions-countermeasures-buttons-container button[value="Defend"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_defend_dark.svg"); -} - -/**/ -#emissions-countermeasures-buttons-container button[value="Attack"]::before { - background-image: url("/themes/olympus/images/icons_roe_target_light.svg"); -} - -#emissions-countermeasures-buttons-container button[value="Attack"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_target_dark.svg"); -} - -/**/ -#emissions-countermeasures-buttons-container button[value="Free"]::before { - background-image: url("/themes/olympus/images/icons_roe_free_light.svg"); -} - -#emissions-countermeasures-buttons-container button[value="Free"].selected::before { - background-image: url("/themes/olympus/images/icons_roe_free_dark.svg"); +#roe-buttons-container button.selected svg .foreground, +#reaction-to-threat-buttons-container button.selected svg .foreground, +#emissions-countermeasures-buttons-container button.selected svg .foreground { + fill: var(--background-steel); } /****************************************************************************************/ diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index 4bdb7666..a523f47c 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -2,17 +2,9 @@ /* Generic marker settings */ --unit-centre-x: calc(var(--unit-width) / 2); --unit-centre-y: calc(var(--unit-height) / 2); - - --unit-hotgroup-height: 15px; - --unit-hotgroup-width: var(--unit-hotgroup-height); - - - /* Air units' marker settings */ - --unit-aircraft-label-x: calc(var(--unit-centre-x) - (var(--unit-aircraft-width) / 2) + (var(--unit-stroke-width) / 2)); - --unit-aircraft-label-y: calc(var(--unit-centre-y) - (var(--unit-aircraft-height) / 2) + (var(--unit-stroke-width) / 2)); } - +/*** Unit marker elements ***/ [data-object|="unit"] { align-items: center; cursor: pointer; @@ -23,15 +15,6 @@ width: 100%; } -.unit-selected-spotlight { - background-color: var(--unit-spotlight-fill); - border-radius: 50%; - display: none; - padding: var(--unit-spotlight-radius); - position: absolute; - z-index: 1; -} - .unit-vvi { align-self: center; background: var(--secondary-gunmetal-grey); @@ -42,16 +25,6 @@ padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width)); position: absolute; width: var(--unit-aircraft-vvi-width); - z-index: 3; -} - -.unit-marker-border { - border-radius: var(--border-radius-sm); - display: none; - height: calc(var(--unit-aircraft-height) + (var(--unit-label-border-width) * 2)); - position: absolute; - width: calc(var(--unit-aircraft-width) + (var(--unit-label-border-width) * 2)); - z-index: 2; } .unit-hotgroup { @@ -59,13 +32,12 @@ background-color: var(--background-steel); border-radius: var(--border-radius-xs); display: none; - height: var(--unit-hotgroup-height); + height: 15px; justify-content: center; position: absolute; transform: rotate(-45deg); translate: 0 -200%; - width: var(--unit-hotgroup-width); - z-index: 5; + width: 15px; } .unit-hotgroup-id { @@ -77,258 +49,61 @@ translate: -1px 1px; } - -/****************************** - Marker -******************************/ .unit-marker { position: absolute; transform-origin: center; - z-index: 3; height: var(--unit-height); width: var(--unit-width); } -[data-is-highlighted] .unit-marker { - stroke: white; +[data-is-selected] .unit-marker::before { + content: ""; + height: 100%; + width: 100%; + background-color: var(--unit-spotlight-fill); + border-radius: 50%; + position: absolute; + z-index: -1; } -[data-is-selected] .unit-marker { +/*** Basic colours ***/ +[data-coalition="blue"] .unit-marker>svg>.background { + fill: var(--primary-blue); } -[data-coalition="blue"] .unit-marker { +[data-coalition="red"] .unit-marker>svg>.background { + fill: var(--primary-red); } -[data-coalition="red"] .unit-marker { +[data-coalition="neutral"] .unit-marker>svg>.background { + fill: var(--primary-neutral); } -[data-coalition="neutral"] .unit-marker { - +[data-is-selected] .unit-marker>svg>.background { + fill: white; } -/* Aircraft */ -[data-object|="unit-aircraft"] .unit-marker { - +[data-is-highlighted] .unit-marker>svg>.background { + stroke: white; } -/* Ground vehicles (not SAMs) */ - -[data-object|="unit-groundunit"] .unit-marker { - background-image: var(--unit-groundunit-marker-neutral-url); - height: var(--unit-groundunit-marker-height); - width: var(--unit-groundunit-marker-width); -} - -[data-object|="unit-groundunit"][data-is-highlighted] .unit-marker { - background-image: var(--unit-groundunit-marker-neutral-hover-url); -} - -[data-object|="unit-groundunit"][data-is-selected] .unit-marker { - background-image: var(--unit-groundunit-marker-neutral-selected-url); -} - - -[data-object|="unit-groundunit"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-groundunit-marker-blue-url); -} - -[data-object|="unit-groundunit"][data-coalition="blue"][data-is-highlighted] .unit-marker { - background-image: var(--unit-groundunit-marker-blue-hover-url); -} - -[data-object|="unit-groundunit"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var(--unit-groundunit-marker-blue-selected-url); -} - - -[data-object|="unit-groundunit"][data-coalition="red"] .unit-marker { - background-image: var(--unit-groundunit-marker-red-url); -} - -[data-object|="unit-groundunit"][data-coalition="red"][data-is-highlighted] .unit-marker { - background-image: var(--unit-groundunit-marker-red-hover-url); -} - -[data-object|="unit-groundunit"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var(--unit-groundunit-marker-red-selected-url); -} - - -/* SAMs */ - -[data-object|="unit-sam"] .unit-selected-spotlight { - translate: 0 2px; -} - -[data-object|="unit-sam"] .unit-marker { - background-image: var(--unit-sam-marker-neutral-url); - height: var(--unit-sam-marker-height); - width: var(--unit-sam-marker-width); -} - - -[data-object|="unit-sam"][data-is-highlighted] .unit-marker { - background-image: var(--unit-sam-marker-neutral-hover-url); -} - -[data-object|="unit-sam"][data-is-selected] .unit-marker { - background-image: var(--unit-sam-marker-neutral-selected-url); -} - - -[data-object|="unit-sam"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-sam-marker-blue-url); -} - -[data-object|="unit-sam"][data-coalition="blue"][data-is-highlighted] .unit-marker { - background-image: var(--unit-sam-marker-blue-hover-url); -} - -[data-object|="unit-sam"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var(--unit-sam-marker-blue-selected-url); -} - - -[data-object|="unit-sam"][data-coalition="red"] .unit-marker { - background-image: var(--unit-sam-marker-red-url); -} - -[data-object|="unit-sam"][data-coalition="red"][data-is-highlighted] .unit-marker { - background-image: var(--unit-sam-marker-red-hover-url); -} - -[data-object|="unit-sam"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var(--unit-sam-marker-red-selected-url); -} - - -/* navyunit */ - -[data-object|="unit-navyunit"] .unit-selected-spotlight { - translate: 0 -2px; -} - -[data-object|="unit-navyunit"] .unit-marker { - background-image: var(--unit-navyunit-marker-neutral-url); - height: var(--unit-navyunit-marker-height); - width: var(--unit-navyunit-marker-width); -} - - -[data-object|="unit-navyunit"][data-is-highlighted] .unit-marker { - background-image: var(--unit-navyunit-marker-neutral-hover-url); -} - -[data-object|="unit-navyunit"][data-is-selected] .unit-marker { - background-image: var(--unit-navyunit-marker-neutral-selected-url); -} - - -[data-object|="unit-navyunit"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-navyunit-marker-blue-url); -} - -[data-object|="unit-navyunit"][data-coalition="blue"][data-is-highlighted] .unit-marker { - background-image: var(--unit-navyunit-marker-blue-hover-url); -} - -[data-object|="unit-navyunit"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var(--unit-navyunit-marker-blue-selected-url); -} - - -[data-object|="unit-navyunit"][data-coalition="red"] .unit-marker { - background-image: var(--unit-navyunit-marker-red-url); -} - -[data-object|="unit-navyunit"][data-coalition="red"][data-is-highlighted] .unit-marker { - background-image: var(--unit-navyunit-marker-red-hover-url); -} - -[data-object|="unit-navyunit"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var(--unit-navyunit-marker-red-selected-url); -} - - -/* Building */ -[data-object|="unit-building"] .unit-marker { - background-image: var(--unit-building-marker-neutral-url); - height: var(--unit-building-marker-height); - width: var(--unit-building-marker-width); -} - - -[data-object|="unit-building"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-building-marker-blue-url); -} - - -[data-object|="unit-building"][data-coalition="red"] .unit-marker { - background-image: var(--unit-building-marker-red-url); -} - -/* Weapons */ - +/*** Cursors ***/ +[data-is-dead], [data-object|="unit-missile"], [data-object|="unit-bomb"] { cursor: default; } -[data-object|="unit-missile"] .unit-marker { - background-image: var(--unit-missile-marker-neutral-url); - height: var(--unit-missile-marker-height); - width: var(--unit-missile-marker-width); -} - -[data-object|="unit-missile"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-missile-marker-blue-url); -} - -[data-object|="unit-missile"][data-coalition="red"] .unit-marker { - background-image: var(--unit-missile-marker-red-url); -} - -[data-object|="unit-bomb"] .unit-marker { - background-image: var(--unit-bomb-marker-neutral-url); - height: var(--unit-bomb-marker-height); - width: var(--unit-bomb-marker-width); -} - -[data-object|="unit-bomb"][data-coalition="blue"] .unit-marker { - background-image: var(--unit-bomb-marker-blue-url); -} - -[data-object|="unit-bomb"][data-coalition="red"] .unit-marker { - background-image: var(--unit-bomb-marker-red-url); -} - - -/******************************************** -* Labels -********************************************/ - +/*** Labels ***/ [data-object|="unit"] .unit-short-label { color: var(--secondary-gunmetal-grey); font-size: var(--unit-font-size); font-weight: var(--unit-font-weight); line-height: normal; position: absolute; - z-index: 10; } -[data-object|="unit-groundunit"] .unit-short-label { - translate: -1px 0; -} - -[data-object|="unit-sam"] .unit-short-label { - translate: 0 25%; -} - -[data-object|="unit-navyunit"] .unit-short-label { - translate: 0 -50%; -} - - +/*** Fuel indicator ***/ [data-object|="unit"] .unit-fuel { background: white; border: var(--unit-aircraft-fuel-border-width) solid var(--secondary-dark-steel); @@ -338,7 +113,6 @@ position: absolute; translate: var(--unit-aircraft-fuel-x) var(--unit-aircraft-fuel-y); width: var(--unit-aircraft-fuel-width); - z-index: 5; } [data-object|="unit"] .unit-fuel-level { @@ -347,7 +121,7 @@ width: 100%; } - +/*** Ammo indicator ***/ [data-object|="unit"] .unit-ammo { column-gap: var(--unit-aircraft-ammo-spacing); display: none; @@ -364,6 +138,7 @@ padding: var(--unit-aircraft-ammo-radius); } +/*** Unit summary ***/ [data-object|="unit"] .unit-summary { pointer-events: none; column-gap: 6px; @@ -383,7 +158,6 @@ 1px 1px 0 #000; translate: -60px 0; width: fit-content; - z-index: 20; } [data-hide-labels] [data-object|="unit"] .unit-summary { @@ -408,11 +182,22 @@ overflow: visible; } +/*** Common ***/ [data-object|="unit"]:hover .unit-ammo, [data-object|="unit"]:hover .unit-fuel { display: flex; } +@keyframes pulse { + 50% { + opacity: 0; + } +} + +[data-object|="unit"][data-has-low-fuel] .unit-fuel { + 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, @@ -443,7 +228,6 @@ background-color: var(--secondary-blue-outline); } - [data-object|="unit"][data-coalition="red"][data-is-selected] .unit-short-label { color: var(--secondary-red-text); } @@ -460,76 +244,47 @@ background-color: var(--secondary-red-outline); } -@keyframes pulse { - 50% { - opacity: 0; - } -} - -[data-object|="unit"][data-has-low-fuel] .unit-fuel { - animation: pulse 1.5s linear infinite; -} - +/*** Unit state ***/ [data-object|="unit"] .unit-state { background-repeat: no-repeat; position: absolute; - height: var(--unit-aircraft-state-height); - width: var(--unit-aircraft-state-width); - z-index: 10; + height: 20px; + width: 20px; } [data-object|="unit"][data-state="rtb"] .unit-state { - background-image: var(--unit-aircraft-state-rtb); + background-image: url("/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="land"] .unit-state { - background-image: var(--unit-aircraft-state-rtb); + background-image: url("/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="idle"] .unit-state { - background-image: var(--unit-aircraft-state-idle); + background-image: url("/theme/images/states/idle.svg"); } [data-object|="unit"][data-state="attack"] .unit-state { - background-image: var(--unit-aircraft-state-attack); + background-image: url("/theme/images/states/attack.svg"); } [data-object|="unit"][data-state="follow"] .unit-state { - background-image: var(--unit-aircraft-state-follow); + background-image: url("/theme/images/states/follow.svg"); } [data-object|="unit"][data-state="refuel"] .unit-state { - background-image: var(--unit-aircraft-state-refuel); + background-image: url("/theme/images/states/refuel.svg"); } [data-object|="unit"][data-state="human"] .unit-state { - background-image: var(--unit-aircraft-state-human); + background-image: url("/theme/images/states/human.svg"); } [data-object|="unit"][data-state="dcs"] .unit-state { - background-image: var(--unit-aircraft-state-dcs); + background-image: url("/theme/images/states/dcs.svg"); } -/*** DEAD ***/ -[data-object|="unit-aircraft"][ data-is-dead] { - cursor: default; -} - -[data-object|="unit-aircraft"][ data-is-dead] .unit-marker { - background-image: var(--unit-aircraft-marker-neutral-dead-url); - background-position: 50% 50%; - background-size: auto 32px; -} - -[data-object|="unit-aircraft"][ data-is-dead][data-coalition="blue"] .unit-marker { - background-image: var(--unit-aircraft-marker-blue-dead-url); -} - -[data-object|="unit-aircraft"][ data-is-dead][data-coalition="red"] .unit-marker { - background-image: var(--unit-aircraft-marker-red-dead-url); -} - - +/*** Dead unit ***/ [data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight, [data-object|="unit-aircraft"][data-is-dead] .unit-short-label, [data-object|="unit-aircraft"][data-is-dead] .unit-vvi, @@ -543,10 +298,10 @@ display: none; } -[data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* { +[data-object|="unit-aircraft"][data-is-dead] .unit-summary>* { display: none; } -[data-object|="unit-aircraft"][ data-is-dead] .unit-summary .unit-callsign { +[data-object|="unit-aircraft"][data-is-dead] .unit-summary .unit-callsign { display: block; } \ No newline at end of file diff --git a/client/public/themes/olympus/images/icons_actions_180_light.svg b/client/public/themes/olympus/images/actions/180.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_180_light.svg rename to client/public/themes/olympus/images/actions/180.svg diff --git a/client/public/themes/olympus/images/icons_actions_cancel_light.svg b/client/public/themes/olympus/images/actions/cancel.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_cancel_light.svg rename to client/public/themes/olympus/images/actions/cancel.svg diff --git a/client/public/themes/olympus/images/icons_actions_gas_light.svg b/client/public/themes/olympus/images/actions/gas.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_gas_light.svg rename to client/public/themes/olympus/images/actions/gas.svg diff --git a/client/public/themes/olympus/images/icons_actions_nothing_light.svg b/client/public/themes/olympus/images/actions/nothing.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_nothing_light.svg rename to client/public/themes/olympus/images/actions/nothing.svg diff --git a/client/public/themes/olympus/images/icons_actions_rtb_light.svg b/client/public/themes/olympus/images/actions/rtb.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_rtb_light.svg rename to client/public/themes/olympus/images/actions/rtb.svg diff --git a/client/public/themes/olympus/images/icons_actions_search_light.svg b/client/public/themes/olympus/images/actions/search.svg similarity index 100% rename from client/public/themes/olympus/images/icons_actions_search_light.svg rename to client/public/themes/olympus/images/actions/search.svg diff --git a/client/public/themes/olympus/images/spawn_aircraft.svg b/client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg similarity index 100% rename from client/public/themes/olympus/images/spawn_aircraft.svg rename to client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg diff --git a/client/public/themes/olympus/images/spawn_ground.svg b/client/public/themes/olympus/images/buttons/other/spawn_ground.svg similarity index 100% rename from client/public/themes/olympus/images/spawn_ground.svg rename to client/public/themes/olympus/images/buttons/other/spawn_ground.svg diff --git a/client/public/themes/olympus/images/spawn_smoke.svg b/client/public/themes/olympus/images/buttons/other/spawn_smoke.svg similarity index 100% rename from client/public/themes/olympus/images/spawn_smoke.svg rename to client/public/themes/olympus/images/buttons/other/spawn_smoke.svg diff --git a/client/public/themes/olympus/images/buttons/roe/designated.svg b/client/public/themes/olympus/images/buttons/roe/designated.svg new file mode 100644 index 00000000..8815698c --- /dev/null +++ b/client/public/themes/olympus/images/buttons/roe/designated.svg @@ -0,0 +1,44 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/roe/free.svg b/client/public/themes/olympus/images/buttons/roe/free.svg new file mode 100644 index 00000000..ea72b826 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/roe/free.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/roe/hold.svg b/client/public/themes/olympus/images/buttons/roe/hold.svg new file mode 100644 index 00000000..3e2cbd79 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/roe/hold.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/roe/return.svg b/client/public/themes/olympus/images/buttons/roe/return.svg new file mode 100644 index 00000000..4d64a998 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/roe/return.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/threat/evade.svg b/client/public/themes/olympus/images/buttons/threat/evade.svg new file mode 100644 index 00000000..c5691783 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/threat/evade.svg @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg new file mode 100644 index 00000000..de72b10b --- /dev/null +++ b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg @@ -0,0 +1,50 @@ + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/threat/none.svg b/client/public/themes/olympus/images/buttons/threat/none.svg new file mode 100644 index 00000000..6663b0e5 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/threat/none.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/threat/passive.svg b/client/public/themes/olympus/images/buttons/threat/passive.svg new file mode 100644 index 00000000..f0d3b893 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/threat/passive.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/aircraft.svg b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg new file mode 100644 index 00000000..6b19238d --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg new file mode 100644 index 00000000..ef41b8b1 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg new file mode 100644 index 00000000..d75e0075 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/navyunit.svg b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg new file mode 100644 index 00000000..3810ab30 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/threatring.svg b/client/public/themes/olympus/images/buttons/visibility/threatring.svg new file mode 100644 index 00000000..6fff5bfe --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/threatring.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/public/themes/olympus/images/icons_form_abreast_dark.svg b/client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_abreast_dark.svg rename to client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg diff --git a/client/public/themes/olympus/images/icons_form_abreast_light.svg b/client/public/themes/olympus/images/formations/icons_form_abreast_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_abreast_light.svg rename to client/public/themes/olympus/images/formations/icons_form_abreast_light.svg diff --git a/client/public/themes/olympus/images/icons_form_admin_dark.svg b/client/public/themes/olympus/images/formations/icons_form_admin_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_admin_dark.svg rename to client/public/themes/olympus/images/formations/icons_form_admin_dark.svg diff --git a/client/public/themes/olympus/images/icons_form_admin_light.svg b/client/public/themes/olympus/images/formations/icons_form_admin_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_admin_light.svg rename to client/public/themes/olympus/images/formations/icons_form_admin_light.svg diff --git a/client/public/themes/olympus/images/icons_form_echelon_dark.svg b/client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_echelon_dark.svg rename to client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg diff --git a/client/public/themes/olympus/images/icons_form_echelon_light.svg b/client/public/themes/olympus/images/formations/icons_form_echelon_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_echelon_light.svg rename to client/public/themes/olympus/images/formations/icons_form_echelon_light.svg diff --git a/client/public/themes/olympus/images/icons_form_trail_dark.svg b/client/public/themes/olympus/images/formations/icons_form_trail_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_trail_dark.svg rename to client/public/themes/olympus/images/formations/icons_form_trail_dark.svg diff --git a/client/public/themes/olympus/images/icons_form_trail_light.svg b/client/public/themes/olympus/images/formations/icons_form_trail_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_form_trail_light.svg rename to client/public/themes/olympus/images/formations/icons_form_trail_light.svg diff --git a/client/public/themes/olympus/images/icon_aa_blue.svg b/client/public/themes/olympus/images/icon_aa_blue.svg deleted file mode 100644 index 0cf7b80e..00000000 --- a/client/public/themes/olympus/images/icon_aa_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_aa_blue_hover.svg b/client/public/themes/olympus/images/icon_aa_blue_hover.svg deleted file mode 100644 index 083607f2..00000000 --- a/client/public/themes/olympus/images/icon_aa_blue_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aa_blue_selected.svg b/client/public/themes/olympus/images/icon_aa_blue_selected.svg deleted file mode 100644 index 8b2e45dc..00000000 --- a/client/public/themes/olympus/images/icon_aa_blue_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aa_neutral.svg b/client/public/themes/olympus/images/icon_aa_neutral.svg deleted file mode 100644 index a420833b..00000000 --- a/client/public/themes/olympus/images/icon_aa_neutral.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_aa_neutral_hover.svg b/client/public/themes/olympus/images/icon_aa_neutral_hover.svg deleted file mode 100644 index 6ec81838..00000000 --- a/client/public/themes/olympus/images/icon_aa_neutral_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aa_neutral_selected.svg b/client/public/themes/olympus/images/icon_aa_neutral_selected.svg deleted file mode 100644 index 5ea11a76..00000000 --- a/client/public/themes/olympus/images/icon_aa_neutral_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aa_red.svg b/client/public/themes/olympus/images/icon_aa_red.svg deleted file mode 100644 index 9e048861..00000000 --- a/client/public/themes/olympus/images/icon_aa_red.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_aa_red_hover.svg b/client/public/themes/olympus/images/icon_aa_red_hover.svg deleted file mode 100644 index e878c07e..00000000 --- a/client/public/themes/olympus/images/icon_aa_red_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aa_red_selected.svg b/client/public/themes/olympus/images/icon_aa_red_selected.svg deleted file mode 100644 index 7d7e78d3..00000000 --- a/client/public/themes/olympus/images/icon_aa_red_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_airbase_neutral.svg b/client/public/themes/olympus/images/icon_airbase_neutral.svg deleted file mode 100644 index 43222171..00000000 --- a/client/public/themes/olympus/images/icon_airbase_neutral.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/icon_airbase_red.svg b/client/public/themes/olympus/images/icon_airbase_red.svg deleted file mode 100644 index d95872f1..00000000 --- a/client/public/themes/olympus/images/icon_airbase_red.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_blue.svg b/client/public/themes/olympus/images/icon_aircraft_blue.svg deleted file mode 100644 index 7e547339..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_blue_hover.svg b/client/public/themes/olympus/images/icon_aircraft_blue_hover.svg deleted file mode 100644 index 0b59ac37..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_blue_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_blue_selected.svg b/client/public/themes/olympus/images/icon_aircraft_blue_selected.svg deleted file mode 100644 index 251084a6..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_blue_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_neutral.svg b/client/public/themes/olympus/images/icon_aircraft_neutral.svg deleted file mode 100644 index 981602fd..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_neutral_hover.svg b/client/public/themes/olympus/images/icon_aircraft_neutral_hover.svg deleted file mode 100644 index a35613bb..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_neutral_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_neutral_selected.svg b/client/public/themes/olympus/images/icon_aircraft_neutral_selected.svg deleted file mode 100644 index 8dcdef70..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_neutral_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_red.svg b/client/public/themes/olympus/images/icon_aircraft_red.svg deleted file mode 100644 index 5777ae70..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_red_hover.svg b/client/public/themes/olympus/images/icon_aircraft_red_hover.svg deleted file mode 100644 index d975fd7b..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_red_hover.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_aircraft_red_selected.svg b/client/public/themes/olympus/images/icon_aircraft_red_selected.svg deleted file mode 100644 index 702d4c27..00000000 --- a/client/public/themes/olympus/images/icon_aircraft_red_selected.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_bomb_blue.svg b/client/public/themes/olympus/images/icon_bomb_blue.svg deleted file mode 100644 index 136deb4f..00000000 --- a/client/public/themes/olympus/images/icon_bomb_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_bomb_neutral.svg b/client/public/themes/olympus/images/icon_bomb_neutral.svg deleted file mode 100644 index e32f31b9..00000000 --- a/client/public/themes/olympus/images/icon_bomb_neutral.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_bomb_red.svg b/client/public/themes/olympus/images/icon_bomb_red.svg deleted file mode 100644 index c86bf6f4..00000000 --- a/client/public/themes/olympus/images/icon_bomb_red.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_building_blue_hover.svg b/client/public/themes/olympus/images/icon_building_blue_hover.svg deleted file mode 100644 index f67b9fe7..00000000 --- a/client/public/themes/olympus/images/icon_building_blue_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_building_blue_selected.svg b/client/public/themes/olympus/images/icon_building_blue_selected.svg deleted file mode 100644 index ad0e3fc1..00000000 --- a/client/public/themes/olympus/images/icon_building_blue_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_building_neutral.svg b/client/public/themes/olympus/images/icon_building_neutral.svg deleted file mode 100644 index b88ef5e9..00000000 --- a/client/public/themes/olympus/images/icon_building_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_building_neutral_hover.svg b/client/public/themes/olympus/images/icon_building_neutral_hover.svg deleted file mode 100644 index 43784a42..00000000 --- a/client/public/themes/olympus/images/icon_building_neutral_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_building_neutral_selected.svg b/client/public/themes/olympus/images/icon_building_neutral_selected.svg deleted file mode 100644 index b009365c..00000000 --- a/client/public/themes/olympus/images/icon_building_neutral_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_building_red.svg b/client/public/themes/olympus/images/icon_building_red.svg deleted file mode 100644 index ff503707..00000000 --- a/client/public/themes/olympus/images/icon_building_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_building_red_hover.svg b/client/public/themes/olympus/images/icon_building_red_hover.svg deleted file mode 100644 index a5c99d6e..00000000 --- a/client/public/themes/olympus/images/icon_building_red_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_building_red_selected.svg b/client/public/themes/olympus/images/icon_building_red_selected.svg deleted file mode 100644 index 220ce1b9..00000000 --- a/client/public/themes/olympus/images/icon_building_red_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_death_blue.svg b/client/public/themes/olympus/images/icon_death_blue.svg deleted file mode 100644 index 24f20fbf..00000000 --- a/client/public/themes/olympus/images/icon_death_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_death_neutral.svg b/client/public/themes/olympus/images/icon_death_neutral.svg deleted file mode 100644 index 295d54b5..00000000 --- a/client/public/themes/olympus/images/icon_death_neutral.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_death_red.svg b/client/public/themes/olympus/images/icon_death_red.svg deleted file mode 100644 index 11b521a1..00000000 --- a/client/public/themes/olympus/images/icon_death_red.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_ground_blue_hover.svg b/client/public/themes/olympus/images/icon_ground_blue_hover.svg deleted file mode 100644 index 764c9f9c..00000000 --- a/client/public/themes/olympus/images/icon_ground_blue_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ground_blue_selected.svg b/client/public/themes/olympus/images/icon_ground_blue_selected.svg deleted file mode 100644 index c3e77fbd..00000000 --- a/client/public/themes/olympus/images/icon_ground_blue_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ground_neutral.svg b/client/public/themes/olympus/images/icon_ground_neutral.svg deleted file mode 100644 index df956461..00000000 --- a/client/public/themes/olympus/images/icon_ground_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_ground_neutral_hover.svg b/client/public/themes/olympus/images/icon_ground_neutral_hover.svg deleted file mode 100644 index fdd6e2e0..00000000 --- a/client/public/themes/olympus/images/icon_ground_neutral_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ground_neutral_selected.svg b/client/public/themes/olympus/images/icon_ground_neutral_selected.svg deleted file mode 100644 index d4651a2c..00000000 --- a/client/public/themes/olympus/images/icon_ground_neutral_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ground_red.svg b/client/public/themes/olympus/images/icon_ground_red.svg deleted file mode 100644 index bb78e1df..00000000 --- a/client/public/themes/olympus/images/icon_ground_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_ground_red_hover.svg b/client/public/themes/olympus/images/icon_ground_red_hover.svg deleted file mode 100644 index 7bce4e24..00000000 --- a/client/public/themes/olympus/images/icon_ground_red_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ground_red_selected.svg b/client/public/themes/olympus/images/icon_ground_red_selected.svg deleted file mode 100644 index afe9a623..00000000 --- a/client/public/themes/olympus/images/icon_ground_red_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_hold_blue.svg b/client/public/themes/olympus/images/icon_hold_blue.svg deleted file mode 100644 index 257c167a..00000000 --- a/client/public/themes/olympus/images/icon_hold_blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_hold_red.svg b/client/public/themes/olympus/images/icon_hold_red.svg deleted file mode 100644 index d2ec1a41..00000000 --- a/client/public/themes/olympus/images/icon_hold_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_missile_neutral.svg b/client/public/themes/olympus/images/icon_missile_neutral.svg deleted file mode 100644 index 73be3528..00000000 --- a/client/public/themes/olympus/images/icon_missile_neutral.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_missile_red.svg b/client/public/themes/olympus/images/icon_missile_red.svg deleted file mode 100644 index c16f8d00..00000000 --- a/client/public/themes/olympus/images/icon_missile_red.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_navyuni_blue.svg b/client/public/themes/olympus/images/icon_navyuni_blue.svg deleted file mode 100644 index e2d296d0..00000000 --- a/client/public/themes/olympus/images/icon_navyuni_blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_navyunit_neutral.svg b/client/public/themes/olympus/images/icon_navyunit_neutral.svg deleted file mode 100644 index 13a87c0b..00000000 --- a/client/public/themes/olympus/images/icon_navyunit_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_navyunit_red.svg b/client/public/themes/olympus/images/icon_navyunit_red.svg deleted file mode 100644 index 91a0aff9..00000000 --- a/client/public/themes/olympus/images/icon_navyunit_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_rtb_blue.svg b/client/public/themes/olympus/images/icon_rtb_blue.svg deleted file mode 100644 index 0ddecdf6..00000000 --- a/client/public/themes/olympus/images/icon_rtb_blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_rtb_neutral.svg b/client/public/themes/olympus/images/icon_rtb_neutral.svg deleted file mode 100644 index 66803354..00000000 --- a/client/public/themes/olympus/images/icon_rtb_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_sam_blue.svg b/client/public/themes/olympus/images/icon_sam_blue.svg deleted file mode 100644 index bc8729c1..00000000 --- a/client/public/themes/olympus/images/icon_sam_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_sam_neutral.svg b/client/public/themes/olympus/images/icon_sam_neutral.svg deleted file mode 100644 index 9eb5ab0b..00000000 --- a/client/public/themes/olympus/images/icon_sam_neutral.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_sam_red.svg b/client/public/themes/olympus/images/icon_sam_red.svg deleted file mode 100644 index 36aac80d..00000000 --- a/client/public/themes/olympus/images/icon_sam_red.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_ship_blue.svg b/client/public/themes/olympus/images/icon_ship_blue.svg deleted file mode 100644 index a981861f..00000000 --- a/client/public/themes/olympus/images/icon_ship_blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_ship_blue_hover.svg b/client/public/themes/olympus/images/icon_ship_blue_hover.svg deleted file mode 100644 index 34b0fe59..00000000 --- a/client/public/themes/olympus/images/icon_ship_blue_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ship_blue_selected.svg b/client/public/themes/olympus/images/icon_ship_blue_selected.svg deleted file mode 100644 index f472cd42..00000000 --- a/client/public/themes/olympus/images/icon_ship_blue_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ship_neutral.svg b/client/public/themes/olympus/images/icon_ship_neutral.svg deleted file mode 100644 index df1643d9..00000000 --- a/client/public/themes/olympus/images/icon_ship_neutral.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_ship_neutral_hover.svg b/client/public/themes/olympus/images/icon_ship_neutral_hover.svg deleted file mode 100644 index 2e332bcd..00000000 --- a/client/public/themes/olympus/images/icon_ship_neutral_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ship_neutral_selected.svg b/client/public/themes/olympus/images/icon_ship_neutral_selected.svg deleted file mode 100644 index 6a7e36e3..00000000 --- a/client/public/themes/olympus/images/icon_ship_neutral_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ship_red.svg b/client/public/themes/olympus/images/icon_ship_red.svg deleted file mode 100644 index f130f9f3..00000000 --- a/client/public/themes/olympus/images/icon_ship_red.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icon_ship_red_hover.svg b/client/public/themes/olympus/images/icon_ship_red_hover.svg deleted file mode 100644 index 7233b4e1..00000000 --- a/client/public/themes/olympus/images/icon_ship_red_hover.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icon_ship_red_selected.svg b/client/public/themes/olympus/images/icon_ship_red_selected.svg deleted file mode 100644 index 0c41f7c2..00000000 --- a/client/public/themes/olympus/images/icon_ship_red_selected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icons_actions_180_dark.svg b/client/public/themes/olympus/images/icons_actions_180_dark.svg deleted file mode 100644 index 79e0ade7..00000000 --- a/client/public/themes/olympus/images/icons_actions_180_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/icons_actions_cancel_dark.svg b/client/public/themes/olympus/images/icons_actions_cancel_dark.svg deleted file mode 100644 index 3582a48c..00000000 --- a/client/public/themes/olympus/images/icons_actions_cancel_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_actions_gas_dark.svg b/client/public/themes/olympus/images/icons_actions_gas_dark.svg deleted file mode 100644 index 4a5d7357..00000000 --- a/client/public/themes/olympus/images/icons_actions_gas_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_actions_nothing_dark.svg b/client/public/themes/olympus/images/icons_actions_nothing_dark.svg deleted file mode 100644 index f6bb0463..00000000 --- a/client/public/themes/olympus/images/icons_actions_nothing_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_actions_rtb_dark.svg b/client/public/themes/olympus/images/icons_actions_rtb_dark.svg deleted file mode 100644 index 081bff14..00000000 --- a/client/public/themes/olympus/images/icons_actions_rtb_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_actions_search_dark.svg b/client/public/themes/olympus/images/icons_actions_search_dark.svg deleted file mode 100644 index 497481c1..00000000 --- a/client/public/themes/olympus/images/icons_actions_search_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_attack_dark.svg b/client/public/themes/olympus/images/icons_roe_attack_dark.svg deleted file mode 100644 index db976cbb..00000000 --- a/client/public/themes/olympus/images/icons_roe_attack_dark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/client/public/themes/olympus/images/icons_roe_attack_light.svg b/client/public/themes/olympus/images/icons_roe_attack_light.svg deleted file mode 100644 index 06ff8bb3..00000000 --- a/client/public/themes/olympus/images/icons_roe_attack_light.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/client/public/themes/olympus/images/icons_roe_defend_dark.svg b/client/public/themes/olympus/images/icons_roe_defend_dark.svg deleted file mode 100644 index 82c305aa..00000000 --- a/client/public/themes/olympus/images/icons_roe_defend_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_defend_light.svg b/client/public/themes/olympus/images/icons_roe_defend_light.svg deleted file mode 100644 index 1d956938..00000000 --- a/client/public/themes/olympus/images/icons_roe_defend_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_free_dark.svg b/client/public/themes/olympus/images/icons_roe_free_dark.svg deleted file mode 100644 index a3b0b02f..00000000 --- a/client/public/themes/olympus/images/icons_roe_free_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_free_light.svg b/client/public/themes/olympus/images/icons_roe_free_light.svg deleted file mode 100644 index 1fdf5d13..00000000 --- a/client/public/themes/olympus/images/icons_roe_free_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_return_dark.svg b/client/public/themes/olympus/images/icons_roe_return_dark.svg deleted file mode 100644 index e42485a7..00000000 --- a/client/public/themes/olympus/images/icons_roe_return_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_return_light.svg b/client/public/themes/olympus/images/icons_roe_return_light.svg deleted file mode 100644 index 030534d7..00000000 --- a/client/public/themes/olympus/images/icons_roe_return_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_stop_dark.svg b/client/public/themes/olympus/images/icons_roe_stop_dark.svg deleted file mode 100644 index 3582a48c..00000000 --- a/client/public/themes/olympus/images/icons_roe_stop_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_stop_light.svg b/client/public/themes/olympus/images/icons_roe_stop_light.svg deleted file mode 100644 index dc3335a8..00000000 --- a/client/public/themes/olympus/images/icons_roe_stop_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_target_dark.svg b/client/public/themes/olympus/images/icons_roe_target_dark.svg deleted file mode 100644 index cb68d86b..00000000 --- a/client/public/themes/olympus/images/icons_roe_target_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_roe_target_light.svg b/client/public/themes/olympus/images/icons_roe_target_light.svg deleted file mode 100644 index a9ec508c..00000000 --- a/client/public/themes/olympus/images/icons_roe_target_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_cms_dark.svg b/client/public/themes/olympus/images/icons_threat_cms_dark.svg deleted file mode 100644 index f6f53fd5..00000000 --- a/client/public/themes/olympus/images/icons_threat_cms_dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/client/public/themes/olympus/images/icons_threat_cms_light.svg b/client/public/themes/olympus/images/icons_threat_cms_light.svg deleted file mode 100644 index 6032f934..00000000 --- a/client/public/themes/olympus/images/icons_threat_cms_light.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/client/public/themes/olympus/images/icons_threat_defend_dark.svg b/client/public/themes/olympus/images/icons_threat_defend_dark.svg deleted file mode 100644 index 962971eb..00000000 --- a/client/public/themes/olympus/images/icons_threat_defend_dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/client/public/themes/olympus/images/icons_threat_defend_light.svg b/client/public/themes/olympus/images/icons_threat_defend_light.svg deleted file mode 100644 index 0e598866..00000000 --- a/client/public/themes/olympus/images/icons_threat_defend_light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/client/public/themes/olympus/images/icons_threat_nothing_dark.svg b/client/public/themes/olympus/images/icons_threat_nothing_dark.svg deleted file mode 100644 index 3582a48c..00000000 --- a/client/public/themes/olympus/images/icons_threat_nothing_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_nothing_light.svg b/client/public/themes/olympus/images/icons_threat_nothing_light.svg deleted file mode 100644 index dc3335a8..00000000 --- a/client/public/themes/olympus/images/icons_threat_nothing_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_protect_dark.svg b/client/public/themes/olympus/images/icons_threat_protect_dark.svg deleted file mode 100644 index 97c1deb0..00000000 --- a/client/public/themes/olympus/images/icons_threat_protect_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_protect_light.svg b/client/public/themes/olympus/images/icons_threat_protect_light.svg deleted file mode 100644 index a3ae6b88..00000000 --- a/client/public/themes/olympus/images/icons_threat_protect_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_retreat_dark.svg b/client/public/themes/olympus/images/icons_threat_retreat_dark.svg deleted file mode 100644 index cb31ee40..00000000 --- a/client/public/themes/olympus/images/icons_threat_retreat_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icons_threat_retreat_light.svg b/client/public/themes/olympus/images/icons_threat_retreat_light.svg deleted file mode 100644 index e17d43f4..00000000 --- a/client/public/themes/olympus/images/icons_threat_retreat_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/icon_airbase_blue.svg b/client/public/themes/olympus/images/mission/airbase.svg similarity index 100% rename from client/public/themes/olympus/images/icon_airbase_blue.svg rename to client/public/themes/olympus/images/mission/airbase.svg diff --git a/client/public/themes/olympus/images/check_square.svg b/client/public/themes/olympus/images/other/check_square.svg similarity index 100% rename from client/public/themes/olympus/images/check_square.svg rename to client/public/themes/olympus/images/other/check_square.svg diff --git a/client/public/themes/olympus/images/chevron-down.svg b/client/public/themes/olympus/images/other/chevron-down.svg similarity index 100% rename from client/public/themes/olympus/images/chevron-down.svg rename to client/public/themes/olympus/images/other/chevron-down.svg diff --git a/client/public/themes/olympus/images/icons_misc_brush_blue.svg b/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_brush_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_brush_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_brush_dark.svg b/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_brush_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_brush_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_brush_light.svg b/client/public/themes/olympus/images/other/icons_misc_brush_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_brush_light.svg rename to client/public/themes/olympus/images/other/icons_misc_brush_light.svg diff --git a/client/public/themes/olympus/images/icons_misc_gas_blue.svg b/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_gas_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_gas_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_gas_dark.svg b/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_gas_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_gas_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_gas_light.svg b/client/public/themes/olympus/images/other/icons_misc_gas_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_gas_light.svg rename to client/public/themes/olympus/images/other/icons_misc_gas_light.svg diff --git a/client/public/themes/olympus/images/icons_misc_map_blue.svg b/client/public/themes/olympus/images/other/icons_misc_map_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_map_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_map_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_map_dark.svg b/client/public/themes/olympus/images/other/icons_misc_map_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_map_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_map_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_map_light.svg b/client/public/themes/olympus/images/other/icons_misc_map_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_map_light.svg rename to client/public/themes/olympus/images/other/icons_misc_map_light.svg diff --git a/client/public/themes/olympus/images/icons_misc_plane_blue.svg b/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_plane_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_plane_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_plane_dark.svg b/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_plane_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_plane_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_plane_light.svg b/client/public/themes/olympus/images/other/icons_misc_plane_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_plane_light.svg rename to client/public/themes/olympus/images/other/icons_misc_plane_light.svg diff --git a/client/public/themes/olympus/images/icons_misc_settings_blue.svg b/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_settings_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_settings_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_settings_dark.svg b/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_settings_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_settings_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_settings_light.svg b/client/public/themes/olympus/images/other/icons_misc_settings_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_settings_light.svg rename to client/public/themes/olympus/images/other/icons_misc_settings_light.svg diff --git a/client/public/themes/olympus/images/icons_misc_visible_blue.svg b/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_visible_blue.svg rename to client/public/themes/olympus/images/other/icons_misc_visible_blue.svg diff --git a/client/public/themes/olympus/images/icons_misc_visible_dark.svg b/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_visible_dark.svg rename to client/public/themes/olympus/images/other/icons_misc_visible_dark.svg diff --git a/client/public/themes/olympus/images/icons_misc_visible_light.svg b/client/public/themes/olympus/images/other/icons_misc_visible_light.svg similarity index 100% rename from client/public/themes/olympus/images/icons_misc_visible_light.svg rename to client/public/themes/olympus/images/other/icons_misc_visible_light.svg diff --git a/client/public/themes/olympus/images/map_source.svg b/client/public/themes/olympus/images/other/map_source.svg similarity index 100% rename from client/public/themes/olympus/images/map_source.svg rename to client/public/themes/olympus/images/other/map_source.svg diff --git a/client/public/themes/olympus/images/state_idle.svg b/client/public/themes/olympus/images/state_idle.svg deleted file mode 100644 index 757ee216..00000000 --- a/client/public/themes/olympus/images/state_idle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/state_rtb.svg b/client/public/themes/olympus/images/state_rtb.svg deleted file mode 100644 index f40e2627..00000000 --- a/client/public/themes/olympus/images/state_rtb.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/state_attack.svg b/client/public/themes/olympus/images/states/attack.svg similarity index 100% rename from client/public/themes/olympus/images/state_attack.svg rename to client/public/themes/olympus/images/states/attack.svg diff --git a/client/public/themes/olympus/images/state_dcs.svg b/client/public/themes/olympus/images/states/dcs.svg similarity index 100% rename from client/public/themes/olympus/images/state_dcs.svg rename to client/public/themes/olympus/images/states/dcs.svg diff --git a/client/public/themes/olympus/images/state_follow.svg b/client/public/themes/olympus/images/states/follow.svg similarity index 100% rename from client/public/themes/olympus/images/state_follow.svg rename to client/public/themes/olympus/images/states/follow.svg diff --git a/client/public/themes/olympus/images/state_human.svg b/client/public/themes/olympus/images/states/human.svg similarity index 100% rename from client/public/themes/olympus/images/state_human.svg rename to client/public/themes/olympus/images/states/human.svg diff --git a/client/public/themes/olympus/images/icon_hold_neutral.svg b/client/public/themes/olympus/images/states/idle.svg similarity index 100% rename from client/public/themes/olympus/images/icon_hold_neutral.svg rename to client/public/themes/olympus/images/states/idle.svg diff --git a/client/public/themes/olympus/images/state_refuel.svg b/client/public/themes/olympus/images/states/refuel.svg similarity index 100% rename from client/public/themes/olympus/images/state_refuel.svg rename to client/public/themes/olympus/images/states/refuel.svg diff --git a/client/public/themes/olympus/images/icon_rtb_red.svg b/client/public/themes/olympus/images/states/rtb.svg similarity index 100% rename from client/public/themes/olympus/images/icon_rtb_red.svg rename to client/public/themes/olympus/images/states/rtb.svg diff --git a/client/public/themes/olympus/images/task_tanker.svg b/client/public/themes/olympus/images/task_tanker.svg deleted file mode 100644 index 32ee5980..00000000 --- a/client/public/themes/olympus/images/task_tanker.svg +++ /dev/null @@ -1,1256 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/units/aircraft.svg b/client/public/themes/olympus/images/units/aircraft.svg index f714a9af..c932ee11 100644 --- a/client/public/themes/olympus/images/units/aircraft.svg +++ b/client/public/themes/olympus/images/units/aircraft.svg @@ -1,4 +1,4 @@ - + diff --git a/client/public/themes/olympus/images/units/bomb.svg b/client/public/themes/olympus/images/units/bomb.svg new file mode 100644 index 00000000..b4447e5c --- /dev/null +++ b/client/public/themes/olympus/images/units/bomb.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/themes/olympus/images/units/death.svg b/client/public/themes/olympus/images/units/death.svg new file mode 100644 index 00000000..acb3d38a --- /dev/null +++ b/client/public/themes/olympus/images/units/death.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/themes/olympus/images/icon_ground_blue.svg b/client/public/themes/olympus/images/units/groundunit-other.svg similarity index 52% rename from client/public/themes/olympus/images/icon_ground_blue.svg rename to client/public/themes/olympus/images/units/groundunit-other.svg index 8b44edf7..a60bf5ea 100644 --- a/client/public/themes/olympus/images/icon_ground_blue.svg +++ b/client/public/themes/olympus/images/units/groundunit-other.svg @@ -1,4 +1,5 @@ - + + diff --git a/client/public/themes/olympus/images/units/groundunit-sam.svg b/client/public/themes/olympus/images/units/groundunit-sam.svg new file mode 100644 index 00000000..6d5b0390 --- /dev/null +++ b/client/public/themes/olympus/images/units/groundunit-sam.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/themes/olympus/images/icon_missile_blue.svg b/client/public/themes/olympus/images/units/missile.svg similarity index 66% rename from client/public/themes/olympus/images/icon_missile_blue.svg rename to client/public/themes/olympus/images/units/missile.svg index 51124e12..ecce0114 100644 --- a/client/public/themes/olympus/images/icon_missile_blue.svg +++ b/client/public/themes/olympus/images/units/missile.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/navyunit.svg b/client/public/themes/olympus/images/units/navyunit.svg new file mode 100644 index 00000000..d6c82025 --- /dev/null +++ b/client/public/themes/olympus/images/units/navyunit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/public/themes/olympus/images/icon_building_blue.svg b/client/public/themes/olympus/images/units/static.svg similarity index 58% rename from client/public/themes/olympus/images/icon_building_blue.svg rename to client/public/themes/olympus/images/units/static.svg index 7f86143a..b986a43f 100644 --- a/client/public/themes/olympus/images/icon_building_blue.svg +++ b/client/public/themes/olympus/images/units/static.svg @@ -1,4 +1,5 @@ - + + diff --git a/client/public/themes/olympus/images/visibility_aircraft_hidden.svg b/client/public/themes/olympus/images/visibility_aircraft_hidden.svg deleted file mode 100644 index 7acbb8dd..00000000 --- a/client/public/themes/olympus/images/visibility_aircraft_hidden.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/visibility_aircraft_visible.svg b/client/public/themes/olympus/images/visibility_aircraft_visible.svg deleted file mode 100644 index 000472f4..00000000 --- a/client/public/themes/olympus/images/visibility_aircraft_visible.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/visibility_ground_hidden.svg b/client/public/themes/olympus/images/visibility_ground_hidden.svg deleted file mode 100644 index 4993ba3e..00000000 --- a/client/public/themes/olympus/images/visibility_ground_hidden.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/visibility_ground_visible.svg b/client/public/themes/olympus/images/visibility_ground_visible.svg deleted file mode 100644 index 4f14666c..00000000 --- a/client/public/themes/olympus/images/visibility_ground_visible.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/visibility_navyunit_hidden.svg b/client/public/themes/olympus/images/visibility_navyunit_hidden.svg deleted file mode 100644 index 48a521bb..00000000 --- a/client/public/themes/olympus/images/visibility_navyunit_hidden.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/visibility_navyunit_visible.svg b/client/public/themes/olympus/images/visibility_navyunit_visible.svg deleted file mode 100644 index bfba32a8..00000000 --- a/client/public/themes/olympus/images/visibility_navyunit_visible.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/visibility_sam_hidden.svg b/client/public/themes/olympus/images/visibility_sam_hidden.svg deleted file mode 100644 index cb9d0a10..00000000 --- a/client/public/themes/olympus/images/visibility_sam_hidden.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/visibility_sam_visible.svg b/client/public/themes/olympus/images/visibility_sam_visible.svg deleted file mode 100644 index 4beca510..00000000 --- a/client/public/themes/olympus/images/visibility_sam_visible.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/client/public/themes/olympus/images/visibility_threat_hidden.svg b/client/public/themes/olympus/images/visibility_threat_hidden.svg deleted file mode 100644 index 2fb993f1..00000000 --- a/client/public/themes/olympus/images/visibility_threat_hidden.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/visibility_threat_visible.svg b/client/public/themes/olympus/images/visibility_threat_visible.svg deleted file mode 100644 index 6a5580de..00000000 --- a/client/public/themes/olympus/images/visibility_threat_visible.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index 06595b96..01af652e 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -1,22 +1,22 @@ :root { - /** Colours **/ - /*** Coalition: neutral **/ + /*** Coalition: neutral ***/ --primary-neutral: #949ba7; --secondary-neutral-outline: #111111; --secondary-neutral-text: #111111; - /*** Coalition: blue **/ + /*** Coalition: blue ***/ --primary-blue: #247be2; --secondary-blue-outline: #082e44; --secondary-blue-text: #017DC1; - /*** Coalition: red **/ + /*** Coalition: red ***/ --primary-red: #ff5858; --secondary-red-outline: #262222; --secondary-red-text: #D42121; + /*** UI Colours **/ --accent-green: #8bff63; --accent-light-blue: #5ca7ff; --accent-light-red: #F5B6B6; @@ -43,25 +43,9 @@ --border-radius-md: 10px; --border-radius-lg: 15px; - /*** Font stuff **/ + /*** Fonts **/ --font-weight-bolder: 600; - /*** Navbar ***/ - --visibility-control-aircraft-visible-url: url("/themes/olympus/images/visibility_aircraft_visible.svg"); - --visibility-control-aircraft-hidden-url: url("/themes/olympus/images/visibility_aircraft_hidden.svg"); - - --visibility-control-groundunit-visible-url: url("/themes/olympus/images/visibility_ground_visible.svg"); - --visibility-control-groundunit-hidden-url: url("/themes/olympus/images/visibility_ground_hidden.svg"); - - --visibility-control-sam-visible-url: url("/themes/olympus/images/visibility_sam_visible.svg"); - --visibility-control-sam-hidden-url: url("/themes/olympus/images/visibility_sam_hidden.svg"); - - --visibility-control-navyunit-visible-url: url("/themes/olympus/images/visibility_navyunit_visible.svg"); - --visibility-control-navyunit-hidden-url: url("/themes/olympus/images/visibility_navyunit_hidden.svg"); - - --visibility-control-threat-visible-url: url("/themes/olympus/images/visibility_threat_visible.svg"); - --visibility-control-threat-hidden-url: url("/themes/olympus/images/visibility_threat_hidden.svg"); - /*** Unit marker settings ***/ /*** All markers **/ --unit-border-radius: var(--border-radius-xs); @@ -89,111 +73,4 @@ --unit-aircraft-fuel-y: 22px; --unit-aircraft-height: 28px; --unit-aircraft-vvi-width: 4px; - --unit-aircraft-width: var(--unit-aircraft-height); - - --unit-aircraft-marker-height: 50px; - --unit-aircraft-marker-width: 50px; - - --unit-aircraft-icon: url("/resources/theme/images/units/aircraft.svg"); - - /*** Air units' states ***/ - --unit-aircraft-state-height: 50px; - --unit-aircraft-state-width: 50px; - - --unit-aircraft-state-rtb: url("/themes/olympus/images/state_rtb.svg"); - --unit-aircraft-state-idle: url("/themes/olympus/images/state_idle.svg"); - --unit-aircraft-state-attack: url("/themes/olympus/images/state_attack.svg"); - --unit-aircraft-state-follow: url("/themes/olympus/images/state_follow.svg"); - --unit-aircraft-state-refuel: url("/themes/olympus/images/state_refuel.svg"); - --unit-aircraft-state-human: url("/themes/olympus/images/state_human.svg"); - --unit-aircraft-state-dcs: url("/themes/olympus/images/state_dcs.svg"); - - /*** Ground ***/ - --unit-groundunit-marker-height: 50px; - --unit-groundunit-marker-width: 50px; - - --unit-groundunit-marker-blue-url: url("/themes/olympus/images/icon_ground_blue.svg"); - --unit-groundunit-marker-blue-hover-url: url("/themes/olympus/images/icon_ground_blue_hover.svg"); - --unit-groundunit-marker-blue-selected-url: url("/themes/olympus/images/icon_ground_blue_selected.svg"); - - --unit-groundunit-marker-neutral-url: url("/themes/olympus/images/icon_ground_neutral.svg"); - --unit-groundunit-marker-neutral-hover-url: url("/themes/olympus/images/icon_ground_neutral_hover.svg"); - --unit-groundunit-marker-neutral-selected-url: url("/themes/olympus/images/icon_ground_neutral_selected.svg"); - - --unit-groundunit-marker-red-url: url("/themes/olympus/images/icon_ground_red.svg"); - --unit-groundunit-marker-red-hover-url: url("/themes/olympus/images/icon_ground_red_hover.svg"); - --unit-groundunit-marker-red-selected-url: url("/themes/olympus/images/icon_ground_red_selected.svg"); - - - /*** SAMs ***/ - --unit-sam-marker-height: 50px; - --unit-sam-marker-width: 50px; - - --unit-sam-marker-blue-url: url("/themes/olympus/images/icon_aa_blue.svg"); - --unit-sam-marker-blue-hover-url: url("/themes/olympus/images/icon_aa_blue_hover.svg"); - --unit-sam-marker-blue-selected-url: url("/themes/olympus/images/icon_aa_blue_selected.svg"); - - --unit-sam-marker-neutral-url: url("/themes/olympus/images/icon_aa_neutral.svg"); - --unit-sam-marker-neutral-hover-url: url("/themes/olympus/images/icon_aa_neutral_hover.svg"); - --unit-sam-marker-neutral-selected-url: url("/themes/olympus/images/icon_aa_neutral_selected.svg"); - - --unit-sam-marker-red-url: url("/themes/olympus/images/icon_aa_red.svg"); - --unit-sam-marker-red-hover-url: url("/themes/olympus/images/icon_aa_red_hover.svg"); - --unit-sam-marker-red-selected-url: url("/themes/olympus/images/icon_aa_red_selected.svg"); - - - /*** navyunit ***/ - --unit-navyunit-marker-height: 50px; - --unit-navyunit-marker-width: 50px; - - --unit-navyunit-marker-blue-url: url("/themes/olympus/images/icon_ship_blue.svg"); - --unit-navyunit-marker-blue-hover-url: url("/themes/olympus/images/icon_ship_blue_hover.svg"); - --unit-navyunit-marker-blue-selected-url: url("/themes/olympus/images/icon_ship_blue_selected.svg"); - - --unit-navyunit-marker-neutral-url: url("/themes/olympus/images/icon_ship_neutral.svg"); - --unit-navyunit-marker-neutral-hover-url: url("/themes/olympus/images/icon_ship_neutral_hover.svg"); - --unit-navyunit-marker-neutral-selected-url: url("/themes/olympus/images/icon_ship_neutral_selected.svg"); - - --unit-navyunit-marker-red-url: url("/themes/olympus/images/icon_ship_red.svg"); - --unit-navyunit-marker-red-hover-url: url("/themes/olympus/images/icon_ship_red_hover.svg"); - --unit-navyunit-marker-red-selected-url: url("/themes/olympus/images/icon_ship_red_selected.svg"); - - - /*** Building ***/ - --unit-building-marker-height: 50px; - --unit-building-marker-width: 50px; - - --unit-building-marker-blue-url: url("/themes/olympus/images/icon_building_blue.svg"); - --unit-building-marker-neutral-url: url("/themes/olympus/images/icon_building_neutral.svg"); - --unit-building-marker-red-url: url("/themes/olympus/images/icon_building_red.svg"); - - - /*** Weapons ***/ - --unit-missile-marker-height: 50px; - --unit-missile-marker-width: 50px; - - --unit-missile-marker-blue-url: url("/themes/olympus/images/icon_missile_blue.svg"); - --unit-missile-marker-neutral-url: url("/themes/olympus/images/icon_missile_neutral.svg"); - --unit-missile-marker-red-url: url("/themes/olympus/images/icon_missile_red.svg"); - - --unit-bomb-marker-height: 50px; - --unit-bomb-marker-width: 50px; - - --unit-bomb-marker-blue-url: url("/themes/olympus/images/icon_bomb_blue.svg"); - --unit-bomb-marker-neutral-url: url("/themes/olympus/images/icon_bomb_neutral.svg"); - --unit-bomb-marker-red-url: url("/themes/olympus/images/icon_bomb_red.svg"); - - - /*** Context menu ***/ - --spawn-aircraft-url: url("/themes/olympus/images/spawn_aircraft.svg"); - --spawn-groundunit-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/routes/resources.js b/client/routes/resources.js index 6e6778bb..2be3af2f 100644 --- a/client/routes/resources.js +++ b/client/routes/resources.js @@ -1,31 +1,10 @@ const express = require('express'); const router = express.Router(); -const fs = require('fs'); -const path = require('path'); -const url = require('url'); var theme = "olympus"; router.get('/theme/*', function (req, res, next) { - if (url.parse(req.url).pathname.slice(-4).toLowerCase() === ".svg") - { - const localPath = path.join(__dirname, '..', 'public', url.parse(req.url).pathname.replace("theme", "themes/" + theme)); - fs.readFile(localPath, function(err, data) { - if (err) { - res.sendStatus(404); - } else { - var svgString = data.toString('utf8'); - for (key in req.query) - svgString = svgString.replaceAll(key, req.query[key]); - - res.header('Content-Type', 'image/svg+xml'); - res.send(svgString); - } - }); - } - else { - res.redirect(req.url.replace("theme", "themes/" + theme)); - } + res.redirect(req.url.replace("theme", "themes/" + theme)); }); module.exports = router; diff --git a/client/src/index.ts b/client/src/index.ts index 436b3838..2a2dc3ae 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -16,6 +16,8 @@ import { Popup } from "./popups/popup"; import { Dropdown } from "./controls/dropdown"; import { HotgroupPanel } from "./panels/hotgrouppanel"; +import "@iconfu/svg-inject"; + var map: Map; var unitsManager: UnitsManager; @@ -214,6 +216,7 @@ function setupEvents() { document.addEventListener("reloadPage", () => { location.reload(); }) + } export function getMap() { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index a1f18db7..682c6cc9 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -115,6 +115,7 @@ export class Map extends L.Map { }); document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { + ev.detail._element.classList.toggle("off"); document.body.toggleAttribute("data-hide-" + ev.detail.category); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index db1df72d..9f6ef22e 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -56,15 +56,15 @@ export class UnitControlPanel extends Panel { /* Option buttons */ this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { - return this.#createOptionButton(option, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); + return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); }); this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => { - return this.#createOptionButton(option, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); }); + return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); }); }); this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => { - return this.#createOptionButton(option, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); }); + return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); }); }); this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); @@ -342,10 +342,11 @@ export class UnitControlPanel extends Panel { this.#advancedSettingsDialog.classList.add("hide"); } - #createOptionButton(option: string, title: string, callback: EventListenerOrEventListenerObject) { + #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { var button = document.createElement("button"); - button.value = option; button.title = title; + button.value = value; + button.innerHTML = `` button.addEventListener("click", callback); return button; } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f98dff6e..f6636c44 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -127,7 +127,7 @@ export class Unit extends Marker { getMarkerHTML() { return `
-
+
` } @@ -539,15 +539,6 @@ export class Unit extends Marker { var element = this.getElement(); if (element != null) { - /* Set the element styling */ - const unitMarker = element.querySelector(".unit-marker") as HTMLElement; - - const styles = getComputedStyle(document.documentElement); - const primaryBlue = styles.getPropertyValue('--primary-blue'); - - if (unitMarker) - unitMarker.style.backgroundImage = `url("/resources/theme/images/units/aircraft.svg?background-colour=${primaryBlue}")`; - /* Draw the velocity vector */ element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`); @@ -703,14 +694,10 @@ export class Aircraft extends AirUnit { getMarkerHTML() { return `
-
-
-
-
-
-
+
+
${aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""}
@@ -752,8 +739,7 @@ export class GroundUnit extends Unit { getMarkerHTML() { var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; return `
-
-
+
${role?.substring(0, 1)?.toUpperCase() || ""}
@@ -764,7 +750,7 @@ export class GroundUnit extends Unit { getMarkerCategory() { // TODO this is very messy var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - var markerCategory = (role === "SAM") ? "sam" : "groundunit"; + var markerCategory = (role === "SAM") ? "sam" : "other"; return markerCategory; } } @@ -787,8 +773,7 @@ export class Weapon extends Unit { getMarkerHTML(): string { return `
-
-
+
` } diff --git a/client/views/index.ejs b/client/views/index.ejs index 80ac37c0..fe565ac0 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -5,6 +5,7 @@ + @@ -22,6 +23,7 @@
+ <%- include('aic.ejs') %> <%- include('atc.ejs') %> <%- include('contextmenus.ejs') %> diff --git a/client/views/navbar.ejs b/client/views/navbar.ejs index 045aeafb..4c46723c 100644 --- a/client/views/navbar.ejs +++ b/client/views/navbar.ejs @@ -1,9 +1,8 @@ \ No newline at end of file diff --git a/client/views/uikit.ejs b/client/views/uikit.ejs index e26d2eb9..9bcc7e2c 100644 --- a/client/views/uikit.ejs +++ b/client/views/uikit.ejs @@ -170,7 +170,7 @@
Neutral
-
+
Z
@@ -182,7 +182,7 @@
Blue
-
+
Y
@@ -194,7 +194,7 @@
Red
-
+
X
@@ -216,7 +216,7 @@
Neutral
-
+
Z
@@ -228,7 +228,7 @@
Blue
-
+
Y
@@ -240,7 +240,7 @@
Red
-
+
X
From a2664dc676b1386b03cf064e4226ef04268ce39c Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 22 May 2023 17:39:33 +0200 Subject: [PATCH 08/10] Refactoring of files and more svg tidy up --- client/package.json | 2 +- client/public/javascripts/svg-inject.js | 697 ++++++++++++++++++ client/public/stylesheets/{ => aic}/aic.css | 0 client/public/stylesheets/{ => atc}/atc.css | 0 .../stylesheets/{ => atc}/unitdatatable.css | 0 .../stylesheets/{ => layout}/layout.css | 0 .../stylesheets/{ => leaflet}/leaflet.css | 0 .../stylesheets/{ => markers}/airbase.css | 0 .../stylesheets/{ => markers}/units.css | 16 +- client/public/stylesheets/olympus.css | 45 +- .../stylesheets/{ => other}/contextmenus.css | 0 .../public/stylesheets/{ => other}/popup.css | 0 .../connectionstatus.css} | 0 .../mouseinfo.css} | 0 .../unitcontrol.css} | 0 .../unitinfo.css} | 0 .../public/stylesheets/{ => uikit}/uikit.css | 0 .../olympus/images/buttons/roe/designated.svg | 2 +- .../olympus/images/buttons/roe/free.svg | 2 +- .../olympus/images/buttons/roe/hold.svg | 2 +- .../olympus/images/buttons/roe/return.svg | 2 +- .../olympus/images/buttons/threat/evade.svg | 10 +- .../images/buttons/threat/manoeuvre.svg | 2 +- .../olympus/images/buttons/threat/none.svg | 2 +- .../olympus/images/buttons/threat/passive.svg | 12 +- .../images/buttons/visibility/airbase.svg | 76 ++ .../images/buttons/visibility/aircraft.svg | 44 +- .../olympus/images/buttons/visibility/dcs.svg | 32 + .../buttons/visibility/groundunit-other.svg | 75 +- .../buttons/visibility/groundunit-sam.svg | 103 ++- .../images/buttons/visibility/human.svg | 67 ++ .../images/buttons/visibility/navyunit.svg | 44 +- .../images/buttons/visibility/threatring.svg | 5 - .../themes/olympus/images/units/aircraft.svg | 2 +- .../themes/olympus/images/units/bomb.svg | 2 +- .../themes/olympus/images/units/death.svg | 2 +- .../olympus/images/units/groundunit-other.svg | 2 +- .../olympus/images/units/groundunit-sam.svg | 2 +- .../themes/olympus/images/units/missile.svg | 2 +- .../themes/olympus/images/units/navyunit.svg | 2 +- .../themes/olympus/images/units/static.svg | 2 +- client/public/themes/olympus/theme.css | 3 + client/src/aic/aic.ts | 2 +- client/src/{units => atc}/unitdatatable.ts | 2 +- client/src/{ => features}/featureswitches.ts | 0 .../src/{ => features}/toggleablefeature.ts | 0 client/src/index.ts | 6 +- client/src/map/map.ts | 21 + client/src/units/unit.ts | 16 +- client/src/units/unitsmanager.ts | 18 + client/views/{ => aic}/aic.ejs | 0 client/views/{ => atc}/atc.ejs | 4 +- client/views/{ => atc}/unitdatatable.ejs | 0 client/views/index.ejs | 50 +- client/views/{ => log}/log.ejs | 0 client/views/{ => other}/contextmenus.ejs | 0 client/views/{ => other}/dialogs.ejs | 0 client/views/{ => other}/popups.ejs | 0 .../connectionstatus.ejs} | 0 .../hotgroup.ejs} | 0 .../mouseinfo.ejs} | 0 client/views/{ => panels}/navbar.ejs | 21 +- .../unitcontrol.ejs} | 0 .../unitinfo.ejs} | 0 client/views/{ => uikit}/uikit.ejs | 0 65 files changed, 1241 insertions(+), 158 deletions(-) create mode 100644 client/public/javascripts/svg-inject.js rename client/public/stylesheets/{ => aic}/aic.css (100%) rename client/public/stylesheets/{ => atc}/atc.css (100%) rename client/public/stylesheets/{ => atc}/unitdatatable.css (100%) rename client/public/stylesheets/{ => layout}/layout.css (100%) rename client/public/stylesheets/{ => leaflet}/leaflet.css (100%) rename client/public/stylesheets/{ => markers}/airbase.css (100%) rename client/public/stylesheets/{ => markers}/units.css (95%) rename client/public/stylesheets/{ => other}/contextmenus.css (100%) rename client/public/stylesheets/{ => other}/popup.css (100%) rename client/public/stylesheets/{connectionstatuspanel.css => panels/connectionstatus.css} (100%) rename client/public/stylesheets/{mouseinfopanel.css => panels/mouseinfo.css} (100%) rename client/public/stylesheets/{unitcontrolpanel.css => panels/unitcontrol.css} (100%) rename client/public/stylesheets/{unitinfopanel.css => panels/unitinfo.css} (100%) rename client/public/stylesheets/{ => uikit}/uikit.css (100%) create mode 100644 client/public/themes/olympus/images/buttons/visibility/airbase.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/dcs.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/human.svg delete mode 100644 client/public/themes/olympus/images/buttons/visibility/threatring.svg rename client/src/{units => atc}/unitdatatable.ts (97%) rename client/src/{ => features}/featureswitches.ts (100%) rename client/src/{ => features}/toggleablefeature.ts (100%) rename client/views/{ => aic}/aic.ejs (100%) rename client/views/{ => atc}/atc.ejs (80%) rename client/views/{ => atc}/unitdatatable.ejs (100%) rename client/views/{ => log}/log.ejs (100%) rename client/views/{ => other}/contextmenus.ejs (100%) rename client/views/{ => other}/dialogs.ejs (100%) rename client/views/{ => other}/popups.ejs (100%) rename client/views/{connectionstatuspanel.ejs => panels/connectionstatus.ejs} (100%) rename client/views/{hotgrouppanel.ejs => panels/hotgroup.ejs} (100%) rename client/views/{mouseinfopanel.ejs => panels/mouseinfo.ejs} (100%) rename client/views/{ => panels}/navbar.ejs (65%) rename client/views/{unitcontrolpanel.ejs => panels/unitcontrol.ejs} (100%) rename client/views/{unitinfopanel.ejs => panels/unitinfo.ejs} (100%) rename client/views/{ => uikit}/uikit.ejs (100%) diff --git a/client/package.json b/client/package.json index f9cb7d2a..678d17e5 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "version": "v0.2.1-alpha", "private": true, "scripts": { - "copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet.css", + "copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css & copy .\\node_modules\\@iconfu\\svg-inject\\dist\\svg-inject.js .\\public\\javascripts\\svg-inject.js", "start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"", "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" }, diff --git a/client/public/javascripts/svg-inject.js b/client/public/javascripts/svg-inject.js new file mode 100644 index 00000000..4e74af07 --- /dev/null +++ b/client/public/javascripts/svg-inject.js @@ -0,0 +1,697 @@ +/** + * SVGInject - Version 1.2.3 + * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM. + * + * https://github.com/iconfu/svg-inject + * + * Copyright (c) 2018 INCORS, the creators of iconfu.com + * @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE + */ + +(function(window, document) { + // constants for better minification + var _CREATE_ELEMENT_ = 'createElement'; + var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName'; + var _LENGTH_ = 'length'; + var _STYLE_ = 'style'; + var _TITLE_ = 'title'; + var _UNDEFINED_ = 'undefined'; + var _SET_ATTRIBUTE_ = 'setAttribute'; + var _GET_ATTRIBUTE_ = 'getAttribute'; + + var NULL = null; + + // constants + var __SVGINJECT = '__svgInject'; + var ID_SUFFIX = '--inject-'; + var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g"); + var LOAD_FAIL = 'LOAD_FAIL'; + var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED'; + var SVG_INVALID = 'SVG_INVALID'; + var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror']; + var A_ELEMENT = document[_CREATE_ELEMENT_]('a'); + var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_; + var DEFAULT_OPTIONS = { + useCache: true, + copyAttributes: true, + makeIdsUnique: true + }; + // Map of IRI referenceable tag names to properties that can reference them. This is defined in + // https://www.w3.org/TR/SVG11/linking.html#processingIRI + var IRI_TAG_PROPERTIES_MAP = { + clipPath: ['clip-path'], + 'color-profile': NULL, + cursor: NULL, + filter: NULL, + linearGradient: ['fill', 'stroke'], + marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'], + mask: NULL, + pattern: ['fill', 'stroke'], + radialGradient: ['fill', 'stroke'] + }; + var INJECTED = 1; + var FAIL = 2; + + var uniqueIdCounter = 1; + var xmlSerializer; + var domParser; + + + // creates an SVG document from an SVG string + function svgStringToSvgDoc(svgStr) { + domParser = domParser || new DOMParser(); + return domParser.parseFromString(svgStr, 'text/xml'); + } + + + // searializes an SVG element to an SVG string + function svgElemToSvgString(svgElement) { + xmlSerializer = xmlSerializer || new XMLSerializer(); + return xmlSerializer.serializeToString(svgElement); + } + + + // Returns the absolute url for the specified url + function getAbsoluteUrl(url) { + A_ELEMENT.href = url; + return A_ELEMENT.href; + } + + + // Load svg with an XHR request + function loadSvg(url, callback, errorCallback) { + if (url) { + var req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState == 4) { + // readyState is DONE + var status = req.status; + if (status == 200) { + // request status is OK + callback(req.responseXML, req.responseText.trim()); + } else if (status >= 400) { + // request status is error (4xx or 5xx) + errorCallback(); + } else if (status == 0) { + // request status 0 can indicate a failed cross-domain call + errorCallback(); + } + } + }; + req.open('GET', url, true); + req.send(); + } + } + + + // Copy attributes from img element to svg element + function copyAttributes(imgElem, svgElem) { + var attribute; + var attributeName; + var attributeValue; + var attributes = imgElem.attributes; + for (var i = 0; i < attributes[_LENGTH_]; i++) { + attribute = attributes[i]; + attributeName = attribute.name; + // Only copy attributes not explicitly excluded from copying + if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) { + attributeValue = attribute.value; + // If img attribute is "title", insert a title element into SVG element + if (attributeName == _TITLE_) { + var titleElem; + var firstElementChild = svgElem.firstElementChild; + if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) { + // If the SVG element's first child is a title element, keep it as the title element + titleElem = firstElementChild; + } else { + // If the SVG element's first child element is not a title element, create a new title + // ele,emt and set it as the first child + titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_); + svgElem.insertBefore(titleElem, firstElementChild); + } + // Set new title content + titleElem.textContent = attributeValue; + } else { + // Set img attribute to svg element + svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue); + } + } + } + } + + + // This function appends a suffix to IDs of referenced elements in the in order to to avoid ID collision + // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is + // incremented with each injection. References to the IDs are adjusted accordingly. + // We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one + // injected SVG. + // If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG + function makeIdsUnique(svgElem, onlyReferenced) { + var idSuffix = ID_SUFFIX + uniqueIdCounter++; + // Regular expression for functional notations of an IRI references. This will find occurences in the form + // url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID + var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; + // Get all elements with an ID. The SVG spec recommends to put referenced elements inside elements, but + // this is not a requirement, therefore we have to search for IDs in the whole SVG. + var idElements = svgElem.querySelectorAll('[id]'); + var idElem; + // An object containing referenced IDs as keys is used if only referenced IDs should be uniquified. + // If this object does not exist, all IDs will be uniquified. + var referencedIds = onlyReferenced ? [] : NULL; + var tagName; + var iriTagNames = {}; + var iriProperties = []; + var changed = false; + var i, j; + + if (idElements[_LENGTH_]) { + // Make all IDs unique by adding the ID suffix and collect all encountered tag names + // that are IRI referenceable from properities. + for (i = 0; i < idElements[_LENGTH_]; i++) { + tagName = idElements[i].localName; // Use non-namespaced tag name + // Make ID unique if tag name is IRI referenceable + if (tagName in IRI_TAG_PROPERTIES_MAP) { + iriTagNames[tagName] = 1; + } + } + // Get all properties that are mapped to the found IRI referenceable tags + for (tagName in iriTagNames) { + (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { + // Add mapped properties to array of iri referencing properties. + // Use linear search here because the number of possible entries is very small (maximum 11) + if (iriProperties.indexOf(mappedProperty) < 0) { + iriProperties.push(mappedProperty); + } + }); + } + if (iriProperties[_LENGTH_]) { + // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' + iriProperties.push(_STYLE_); + } + // Run through all elements of the SVG and replace IDs in references. + // To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*'). + // Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately. + var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); + var element = svgElem; + var propertyName; + var value; + var newValue; + for (i = -1; element != NULL;) { + if (element.localName == _STYLE_) { + // If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content + value = element.textContent; + newValue = value && value.replace(funcIriRegex, function(match, id) { + if (referencedIds) { + referencedIds[id] = 1; + } + return 'url(#' + id + idSuffix + ')'; + }); + if (newValue !== value) { + element.textContent = newValue; + } + } else if (element.hasAttributes()) { + // Run through all property names for which IDs were found + for (j = 0; j < iriProperties[_LENGTH_]; j++) { + propertyName = iriProperties[j]; + value = element[_GET_ATTRIBUTE_](propertyName); + newValue = value && value.replace(funcIriRegex, function(match, id) { + if (referencedIds) { + referencedIds[id] = 1; + } + return 'url(#' + id + idSuffix + ')'; + }); + if (newValue !== value) { + element[_SET_ATTRIBUTE_](propertyName, newValue); + } + } + // Replace IDs in xlink:ref and href attributes + ['xlink:href', 'href'].forEach(function(refAttrName) { + var iri = element[_GET_ATTRIBUTE_](refAttrName); + if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference + iri = iri.trim(); + element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix); + if (referencedIds) { + // Add ID to referenced IDs + referencedIds[iri.substring(1)] = 1; + } + } + }); + } + element = descElements[++i]; + } + for (i = 0; i < idElements[_LENGTH_]; i++) { + idElem = idElements[i]; + // If set of referenced IDs exists, make only referenced IDs unique, + // otherwise make all IDs unique. + if (!referencedIds || referencedIds[idElem.id]) { + // Add suffix to element's ID + idElem.id += idSuffix; + changed = true; + } + } + } + // return true if SVG element has changed + return changed; + } + + + // For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a + // higher ID counter. This is much more performant than a call to makeIdsUnique(). + function makeIdsUniqueCached(svgString) { + return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++); + } + + + // Inject SVG by replacing the img element with the SVG element in the DOM + function inject(imgElem, svgElem, absUrl, options) { + if (svgElem) { + svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl); + var parentNode = imgElem.parentNode; + if (parentNode) { + if (options.copyAttributes) { + copyAttributes(imgElem, svgElem); + } + // Invoke beforeInject hook if set + var beforeInject = options.beforeInject; + var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem; + // Replace img element with new element. This is the actual injection. + parentNode.replaceChild(injectElem, imgElem); + // Mark img element as injected + imgElem[__SVGINJECT] = INJECTED; + removeOnLoadAttribute(imgElem); + // Invoke afterInject hook if set + var afterInject = options.afterInject; + if (afterInject) { + afterInject(imgElem, injectElem); + } + } + } else { + svgInvalid(imgElem, options); + } + } + + + // Merges any number of options objects into a new object + function mergeOptions() { + var mergedOptions = {}; + var args = arguments; + // Iterate over all specified options objects and add all properties to the new options object + for (var i = 0; i < args[_LENGTH_]; i++) { + var argument = args[i]; + for (var key in argument) { + if (argument.hasOwnProperty(key)) { + mergedOptions[key] = argument[key]; + } + } + } + return mergedOptions; + } + + + // Adds the specified CSS to the document's element + function addStyleToHead(css) { + var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; + if (head) { + var style = document[_CREATE_ELEMENT_](_STYLE_); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + } + + + // Builds an SVG element from the specified SVG string + function buildSvgElement(svgStr, verify) { + if (verify) { + var svgDoc; + try { + // Parse the SVG string with DOMParser + svgDoc = svgStringToSvgDoc(svgStr); + } catch(e) { + return NULL; + } + if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { + // DOMParser does not throw an exception, but instead puts parsererror tags in the document + return NULL; + } + return svgDoc.documentElement; + } else { + var div = document.createElement('div'); + div.innerHTML = svgStr; + return div.firstElementChild; + } + } + + + function removeOnLoadAttribute(imgElem) { + // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and + // make the element visible, not for removing the event listener. + imgElem.removeAttribute('onload'); + } + + + function errorMessage(msg) { + console.error('SVGInject: ' + msg); + } + + + function fail(imgElem, status, options) { + imgElem[__SVGINJECT] = FAIL; + if (options.onFail) { + options.onFail(imgElem, status); + } else { + errorMessage(status); + } + } + + + function svgInvalid(imgElem, options) { + removeOnLoadAttribute(imgElem); + fail(imgElem, SVG_INVALID, options); + } + + + function svgNotSupported(imgElem, options) { + removeOnLoadAttribute(imgElem); + fail(imgElem, SVG_NOT_SUPPORTED, options); + } + + + function loadFail(imgElem, options) { + fail(imgElem, LOAD_FAIL, options); + } + + + function removeEventListeners(imgElem) { + imgElem.onload = NULL; + imgElem.onerror = NULL; + } + + + function imgNotSet(msg) { + errorMessage('no img element'); + } + + + function createSVGInject(globalName, options) { + var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); + var svgLoadCache = {}; + + if (IS_SVG_SUPPORTED) { + // If the browser supports SVG, add a small stylesheet that hides the elements until + // injection is finished. This avoids showing the unstyled SVGs before style is applied. + addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); + } + + + /** + * SVGInject + * + * Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img` + * elements. Returns a Promise object which resolves if all passed in `img` elements have either been + * injected or failed to inject (Only if a global Promise object is available like in all modern browsers + * or through a polyfill). + * + * Options: + * useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`. + * copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value + * is `true`. + * makeIdsUnique: If set to `true` the ID of elements in the `` element that can be references by + * property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a + * running number which increases with each injection. This is done to avoid duplicate IDs in the DOM. + * beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns + * a string it is used as the URL instead of the `img` element's `src` attribute. + * afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a + * parameters. If caching is active this hook will only get called once for injected SVGs with the + * same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs + * with the same absolute path. It's also possible to return an `svg` string or `svg` element which + * will then be used for the injection. + * beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If + * any html element is returned it gets injected instead of applying the default SVG injection. + * afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters. + * onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or + * failed to inject. + * onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter. + * The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG), + * `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed). + * + * @param {HTMLImageElement} img - an img element or an array of img elements + * @param {Object} [options] - optional parameter with [options](#options) for this injection. + */ + function SVGInject(img, options) { + options = mergeOptions(defaultOptions, options); + + var run = function(resolve) { + var allFinish = function() { + var onAllFinish = options.onAllFinish; + if (onAllFinish) { + onAllFinish(); + } + resolve && resolve(); + }; + + if (img && typeof img[_LENGTH_] != _UNDEFINED_) { + // an array like structure of img elements + var injectIndex = 0; + var injectCount = img[_LENGTH_]; + + if (injectCount == 0) { + allFinish(); + } else { + var finish = function() { + if (++injectIndex == injectCount) { + allFinish(); + } + }; + + for (var i = 0; i < injectCount; i++) { + SVGInjectElement(img[i], options, finish); + } + } + } else { + // only one img element + SVGInjectElement(img, options, allFinish); + } + }; + + // return a Promise object if globally available + return typeof Promise == _UNDEFINED_ ? run() : new Promise(run); + } + + + // Injects a single svg element. Options must be already merged with the default options. + function SVGInjectElement(imgElem, options, callback) { + if (imgElem) { + var svgInjectAttributeValue = imgElem[__SVGINJECT]; + if (!svgInjectAttributeValue) { + removeEventListeners(imgElem); + + if (!IS_SVG_SUPPORTED) { + svgNotSupported(imgElem, options); + callback(); + return; + } + // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load + // URL path. Else use the imgElem's src attribute value. + var beforeLoad = options.beforeLoad; + var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src'); + + if (!src) { + // If no image src attribute is set do no injection. This can only be reached by using javascript + // because if no src attribute is set the onload and onerror events do not get called + if (src === '') { + loadFail(imgElem, options); + } + callback(); + return; + } + + // set array so later calls can register callbacks + var onFinishCallbacks = []; + imgElem[__SVGINJECT] = onFinishCallbacks; + + var onFinish = function() { + callback(); + onFinishCallbacks.forEach(function(onFinishCallback) { + onFinishCallback(); + }); + }; + + var absUrl = getAbsoluteUrl(src); + var useCacheOption = options.useCache; + var makeIdsUniqueOption = options.makeIdsUnique; + + var setSvgLoadCacheValue = function(val) { + if (useCacheOption) { + svgLoadCache[absUrl].forEach(function(svgLoad) { + svgLoad(val); + }); + svgLoadCache[absUrl] = val; + } + }; + + if (useCacheOption) { + var svgLoad = svgLoadCache[absUrl]; + + var handleLoadValue = function(loadValue) { + if (loadValue === LOAD_FAIL) { + loadFail(imgElem, options); + } else if (loadValue === SVG_INVALID) { + svgInvalid(imgElem, options); + } else { + var hasUniqueIds = loadValue[0]; + var svgString = loadValue[1]; + var uniqueIdsSvgString = loadValue[2]; + var svgElem; + + if (makeIdsUniqueOption) { + if (hasUniqueIds === NULL) { + // IDs for the SVG string have not been made unique before. This may happen if previous + // injection of a cached SVG have been run with the option makedIdsUnique set to false + svgElem = buildSvgElement(svgString, false); + hasUniqueIds = makeIdsUnique(svgElem, false); + + loadValue[0] = hasUniqueIds; + loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem); + } else if (hasUniqueIds) { + // Make IDs unique for already cached SVGs with better performance + svgString = makeIdsUniqueCached(uniqueIdsSvgString); + } + } + + svgElem = svgElem || buildSvgElement(svgString, false); + + inject(imgElem, svgElem, absUrl, options); + } + onFinish(); + }; + + if (typeof svgLoad != _UNDEFINED_) { + // Value for url exists in cache + if (svgLoad.isCallbackQueue) { + // Same url has been cached, but value has not been loaded yet, so add to callbacks + svgLoad.push(handleLoadValue); + } else { + handleLoadValue(svgLoad); + } + return; + } else { + var svgLoad = []; + // set property isCallbackQueue to Array to differentiate from array with cached loaded values + svgLoad.isCallbackQueue = true; + svgLoadCache[absUrl] = svgLoad; + } + } + + // Load the SVG because it is not cached or caching is disabled + loadSvg(absUrl, function(svgXml, svgString) { + // Use the XML from the XHR request if it is an instance of Document. Otherwise + // (for example of IE9), create the svg document from the svg string. + var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true); + + var afterLoad = options.afterLoad; + if (afterLoad) { + // Invoke afterLoad hook which may modify the SVG element. After load may also return a new + // svg element or svg string + var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem; + if (svgElemOrSvgString) { + // Update svgElem and svgString because of modifications to the SVG element or SVG string in + // the afterLoad hook, so the modified SVG is also used for all later cached injections + var isString = typeof svgElemOrSvgString == 'string'; + svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem); + svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString; + } + } + + if (svgElem instanceof SVGElement) { + var hasUniqueIds = NULL; + if (makeIdsUniqueOption) { + hasUniqueIds = makeIdsUnique(svgElem, false); + } + + if (useCacheOption) { + var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem); + // set an array with three entries to the load cache + setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]); + } + + inject(imgElem, svgElem, absUrl, options); + } else { + svgInvalid(imgElem, options); + setSvgLoadCacheValue(SVG_INVALID); + } + onFinish(); + }, function() { + loadFail(imgElem, options); + setSvgLoadCacheValue(LOAD_FAIL); + onFinish(); + }); + } else { + if (Array.isArray(svgInjectAttributeValue)) { + // svgInjectAttributeValue is an array. Injection is not complete so register callback + svgInjectAttributeValue.push(callback); + } else { + callback(); + } + } + } else { + imgNotSet(); + } + } + + + /** + * Sets the default [options](#options) for SVGInject. + * + * @param {Object} [options] - default [options](#options) for an injection. + */ + SVGInject.setOptions = function(options) { + defaultOptions = mergeOptions(defaultOptions, options); + }; + + + // Create a new instance of SVGInject + SVGInject.create = createSVGInject; + + + /** + * Used in onerror Event of an `` element to handle cases when the loading the original src fails + * (for example if file is not found or if the browser does not support SVG). This triggers a call to the + * options onFail hook if available. The optional second parameter will be set as the new src attribute + * for the img element. + * + * @param {HTMLImageElement} img - an img element + * @param {String} [fallbackSrc] - optional parameter fallback src + */ + SVGInject.err = function(img, fallbackSrc) { + if (img) { + if (img[__SVGINJECT] != FAIL) { + removeEventListeners(img); + + if (!IS_SVG_SUPPORTED) { + svgNotSupported(img, defaultOptions); + } else { + removeOnLoadAttribute(img); + loadFail(img, defaultOptions); + } + if (fallbackSrc) { + removeOnLoadAttribute(img); + img.src = fallbackSrc; + } + } + } else { + imgNotSet(); + } + }; + + window[globalName] = SVGInject; + + return SVGInject; + } + + var SVGInjectInstance = createSVGInject('SVGInject'); + + if (typeof module == 'object' && typeof module.exports == 'object') { + module.exports = SVGInjectInstance; + } +})(window, document); \ No newline at end of file diff --git a/client/public/stylesheets/aic.css b/client/public/stylesheets/aic/aic.css similarity index 100% rename from client/public/stylesheets/aic.css rename to client/public/stylesheets/aic/aic.css diff --git a/client/public/stylesheets/atc.css b/client/public/stylesheets/atc/atc.css similarity index 100% rename from client/public/stylesheets/atc.css rename to client/public/stylesheets/atc/atc.css diff --git a/client/public/stylesheets/unitdatatable.css b/client/public/stylesheets/atc/unitdatatable.css similarity index 100% rename from client/public/stylesheets/unitdatatable.css rename to client/public/stylesheets/atc/unitdatatable.css diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout/layout.css similarity index 100% rename from client/public/stylesheets/layout.css rename to client/public/stylesheets/layout/layout.css diff --git a/client/public/stylesheets/leaflet.css b/client/public/stylesheets/leaflet/leaflet.css similarity index 100% rename from client/public/stylesheets/leaflet.css rename to client/public/stylesheets/leaflet/leaflet.css diff --git a/client/public/stylesheets/airbase.css b/client/public/stylesheets/markers/airbase.css similarity index 100% rename from client/public/stylesheets/airbase.css rename to client/public/stylesheets/markers/airbase.css diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/markers/units.css similarity index 95% rename from client/public/stylesheets/units.css rename to client/public/stylesheets/markers/units.css index a523f47c..121a1059 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/markers/units.css @@ -67,23 +67,23 @@ } /*** Basic colours ***/ -[data-coalition="blue"] .unit-marker>svg>.background { - fill: var(--primary-blue); +[data-coalition="blue"] .unit-marker svg>*:first-child { + fill: var(--unit-background-blue); } -[data-coalition="red"] .unit-marker>svg>.background { - fill: var(--primary-red); +[data-coalition="red"] .unit-marker svg>*:first-child { + fill: var(--unit-background-red); } -[data-coalition="neutral"] .unit-marker>svg>.background { - fill: var(--primary-neutral); +[data-coalition="neutral"] .unit-marker svg>*:first-child { + fill: var(--unit-background-neutral); } -[data-is-selected] .unit-marker>svg>.background { +[data-is-selected] .unit-marker svg>*:first-child { fill: white; } -[data-is-highlighted] .unit-marker>svg>.background { +[data-is-highlighted] .unit-marker svg>*:first-child { stroke: white; } diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 3a0d7326..413afbc1 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -1,14 +1,15 @@ -@import url("layout.css"); -@import url("airbase.css"); -@import url("atc.css"); -@import url("connectionstatuspanel.css"); -@import url("contextmenus.css"); -@import url("mouseinfopanel.css"); -@import url("/stylesheets/units.css"); -@import url("unitdatatable.css"); -@import url("unitcontrolpanel.css"); -@import url("unitinfopanel.css"); -@import url("popup.css"); +@import url("layout/layout.css"); +@import url("atc/atc.css"); +@import url("atc/unitdatatable.css"); +@import url("aic/aic.css"); +@import url("panels/connectionstatus.css"); +@import url("panels/mouseinfo.css"); +@import url("panels/unitcontrol.css"); +@import url("panels/unitinfo.css"); +@import url("other/contextmenus.css"); +@import url("other/popup.css"); +@import url("markers/airbase.css"); +@import url("markers/units.css"); * { -moz-box-sizing: border-box; @@ -593,22 +594,28 @@ nav.ol-panel> :last-child { #unit-visibility-control button svg { pointer-events: none; + height: 16px; + width: 16px; } -#unit-visibility-control button svg .background { - fill: white; +#unit-visibility-control button { + background-color: white; + border: 1px solid transparent; } -#unit-visibility-control button.off svg .foreground { - fill: var(--background-steel); +#unit-visibility-control button.off { + background-color: transparent; + border: 1px solid white; } -#unit-visibility-control button.off svg .background { - fill: none; +#unit-visibility-control button.off svg * { + fill: white !important; + stroke: white !important; } -#unit-visibility-control button.off svg .foreground { - fill: white; +#unit-visibility-control button svg * { + fill: var(--background-steel) !important; + stroke: var(--background-steel) !important; } #atc-navbar-control { diff --git a/client/public/stylesheets/contextmenus.css b/client/public/stylesheets/other/contextmenus.css similarity index 100% rename from client/public/stylesheets/contextmenus.css rename to client/public/stylesheets/other/contextmenus.css diff --git a/client/public/stylesheets/popup.css b/client/public/stylesheets/other/popup.css similarity index 100% rename from client/public/stylesheets/popup.css rename to client/public/stylesheets/other/popup.css diff --git a/client/public/stylesheets/connectionstatuspanel.css b/client/public/stylesheets/panels/connectionstatus.css similarity index 100% rename from client/public/stylesheets/connectionstatuspanel.css rename to client/public/stylesheets/panels/connectionstatus.css diff --git a/client/public/stylesheets/mouseinfopanel.css b/client/public/stylesheets/panels/mouseinfo.css similarity index 100% rename from client/public/stylesheets/mouseinfopanel.css rename to client/public/stylesheets/panels/mouseinfo.css diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/panels/unitcontrol.css similarity index 100% rename from client/public/stylesheets/unitcontrolpanel.css rename to client/public/stylesheets/panels/unitcontrol.css diff --git a/client/public/stylesheets/unitinfopanel.css b/client/public/stylesheets/panels/unitinfo.css similarity index 100% rename from client/public/stylesheets/unitinfopanel.css rename to client/public/stylesheets/panels/unitinfo.css diff --git a/client/public/stylesheets/uikit.css b/client/public/stylesheets/uikit/uikit.css similarity index 100% rename from client/public/stylesheets/uikit.css rename to client/public/stylesheets/uikit/uikit.css diff --git a/client/public/themes/olympus/images/buttons/roe/designated.svg b/client/public/themes/olympus/images/buttons/roe/designated.svg index 8815698c..0846e4e6 100644 --- a/client/public/themes/olympus/images/buttons/roe/designated.svg +++ b/client/public/themes/olympus/images/buttons/roe/designated.svg @@ -37,7 +37,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/free.svg b/client/public/themes/olympus/images/buttons/roe/free.svg index ea72b826..f07b2db6 100644 --- a/client/public/themes/olympus/images/buttons/roe/free.svg +++ b/client/public/themes/olympus/images/buttons/roe/free.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/hold.svg b/client/public/themes/olympus/images/buttons/roe/hold.svg index 3e2cbd79..f735bc21 100644 --- a/client/public/themes/olympus/images/buttons/roe/hold.svg +++ b/client/public/themes/olympus/images/buttons/roe/hold.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/return.svg b/client/public/themes/olympus/images/buttons/roe/return.svg index 4d64a998..5c884188 100644 --- a/client/public/themes/olympus/images/buttons/roe/return.svg +++ b/client/public/themes/olympus/images/buttons/roe/return.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/evade.svg b/client/public/themes/olympus/images/buttons/threat/evade.svg index c5691783..687f852e 100644 --- a/client/public/themes/olympus/images/buttons/threat/evade.svg +++ b/client/public/themes/olympus/images/buttons/threat/evade.svg @@ -36,25 +36,25 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg12" /> - - - - - diff --git a/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg index de72b10b..fcc9376b 100644 --- a/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg +++ b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg12" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/none.svg b/client/public/themes/olympus/images/buttons/threat/none.svg index 6663b0e5..53414370 100644 --- a/client/public/themes/olympus/images/buttons/threat/none.svg +++ b/client/public/themes/olympus/images/buttons/threat/none.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/passive.svg b/client/public/themes/olympus/images/buttons/threat/passive.svg index f0d3b893..ac558fef 100644 --- a/client/public/themes/olympus/images/buttons/threat/passive.svg +++ b/client/public/themes/olympus/images/buttons/threat/passive.svg @@ -36,28 +36,28 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg14" /> - - - - - - diff --git a/client/public/themes/olympus/images/buttons/visibility/airbase.svg b/client/public/themes/olympus/images/buttons/visibility/airbase.svg new file mode 100644 index 00000000..28ad42a4 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/airbase.svg @@ -0,0 +1,76 @@ + + + + + + + + image/svg+xml + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/aircraft.svg b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg index 6b19238d..09a9e322 100644 --- a/client/public/themes/olympus/images/buttons/visibility/aircraft.svg +++ b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg @@ -1,5 +1,41 @@ - - - - + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/dcs.svg b/client/public/themes/olympus/images/buttons/visibility/dcs.svg new file mode 100644 index 00000000..bb7bd479 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/dcs.svg @@ -0,0 +1,32 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg index ef41b8b1..0a448b6f 100644 --- a/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg @@ -1,13 +1,64 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg index d75e0075..2a568fbb 100644 --- a/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg @@ -1,19 +1,86 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/human.svg b/client/public/themes/olympus/images/buttons/visibility/human.svg new file mode 100644 index 00000000..51278e9e --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/human.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/navyunit.svg b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg index 3810ab30..d4ff6fec 100644 --- a/client/public/themes/olympus/images/buttons/visibility/navyunit.svg +++ b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg @@ -1,5 +1,41 @@ - - - - + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/threatring.svg b/client/public/themes/olympus/images/buttons/visibility/threatring.svg deleted file mode 100644 index 6fff5bfe..00000000 --- a/client/public/themes/olympus/images/buttons/visibility/threatring.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/units/aircraft.svg b/client/public/themes/olympus/images/units/aircraft.svg index c932ee11..104f9bcf 100644 --- a/client/public/themes/olympus/images/units/aircraft.svg +++ b/client/public/themes/olympus/images/units/aircraft.svg @@ -1,4 +1,4 @@ - + diff --git a/client/public/themes/olympus/images/units/bomb.svg b/client/public/themes/olympus/images/units/bomb.svg index b4447e5c..a38a168e 100644 --- a/client/public/themes/olympus/images/units/bomb.svg +++ b/client/public/themes/olympus/images/units/bomb.svg @@ -1,3 +1,3 @@ - + diff --git a/client/public/themes/olympus/images/units/death.svg b/client/public/themes/olympus/images/units/death.svg index acb3d38a..c68ab1d1 100644 --- a/client/public/themes/olympus/images/units/death.svg +++ b/client/public/themes/olympus/images/units/death.svg @@ -1,3 +1,3 @@ - + diff --git a/client/public/themes/olympus/images/units/groundunit-other.svg b/client/public/themes/olympus/images/units/groundunit-other.svg index a60bf5ea..6b6dcc7c 100644 --- a/client/public/themes/olympus/images/units/groundunit-other.svg +++ b/client/public/themes/olympus/images/units/groundunit-other.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/groundunit-sam.svg b/client/public/themes/olympus/images/units/groundunit-sam.svg index 6d5b0390..996af35d 100644 --- a/client/public/themes/olympus/images/units/groundunit-sam.svg +++ b/client/public/themes/olympus/images/units/groundunit-sam.svg @@ -1,4 +1,4 @@ - + diff --git a/client/public/themes/olympus/images/units/missile.svg b/client/public/themes/olympus/images/units/missile.svg index ecce0114..d7711d7c 100644 --- a/client/public/themes/olympus/images/units/missile.svg +++ b/client/public/themes/olympus/images/units/missile.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/navyunit.svg b/client/public/themes/olympus/images/units/navyunit.svg index d6c82025..b3b2ed1b 100644 --- a/client/public/themes/olympus/images/units/navyunit.svg +++ b/client/public/themes/olympus/images/units/navyunit.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/static.svg b/client/public/themes/olympus/images/units/static.svg index b986a43f..ead61396 100644 --- a/client/public/themes/olympus/images/units/static.svg +++ b/client/public/themes/olympus/images/units/static.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index 01af652e..a7b402af 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -5,16 +5,19 @@ --primary-neutral: #949ba7; --secondary-neutral-outline: #111111; --secondary-neutral-text: #111111; + --unit-background-neutral: #CFD9E8; /*** Coalition: blue ***/ --primary-blue: #247be2; --secondary-blue-outline: #082e44; --secondary-blue-text: #017DC1; + --unit-background-blue: #3BB9FF; /*** Coalition: red ***/ --primary-red: #ff5858; --secondary-red-outline: #262222; --secondary-red-text: #D42121; + --unit-background-red: #FF5858; /*** UI Colours **/ --accent-green: #8bff63; diff --git a/client/src/aic/aic.ts b/client/src/aic/aic.ts index 770fd695..2aaf8a1c 100644 --- a/client/src/aic/aic.ts +++ b/client/src/aic/aic.ts @@ -1,4 +1,4 @@ -import { ToggleableFeature } from "../toggleablefeature"; +import { ToggleableFeature } from "../features/toggleablefeature"; import { AICFormation_Azimuth } from "./aicformation/azimuth"; import { AICFormation_Range } from "./aicformation/range"; import { AICFormation_Single } from "./aicformation/single"; diff --git a/client/src/units/unitdatatable.ts b/client/src/atc/unitdatatable.ts similarity index 97% rename from client/src/units/unitdatatable.ts rename to client/src/atc/unitdatatable.ts index e54b541c..3b0b97b8 100644 --- a/client/src/units/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -1,6 +1,6 @@ import { getUnitsManager } from ".."; import { Panel } from "../panels/panel"; -import { Unit } from "./unit"; +import { Unit } from "../units/unit"; export class UnitDataTable extends Panel { constructor(id: string) { diff --git a/client/src/featureswitches.ts b/client/src/features/featureswitches.ts similarity index 100% rename from client/src/featureswitches.ts rename to client/src/features/featureswitches.ts diff --git a/client/src/toggleablefeature.ts b/client/src/features/toggleablefeature.ts similarity index 100% rename from client/src/toggleablefeature.ts rename to client/src/features/toggleablefeature.ts diff --git a/client/src/index.ts b/client/src/index.ts index 2a2dc3ae..16d9a8b2 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -7,17 +7,15 @@ import { UnitControlPanel } from "./panels/unitcontrolpanel"; import { MouseInfoPanel } from "./panels/mouseinfopanel"; import { AIC } from "./aic/aic"; import { ATC } from "./atc/atc"; -import { FeatureSwitches } from "./featureswitches"; +import { FeatureSwitches } from "./features/featureswitches"; import { LogPanel } from "./panels/logpanel"; import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server"; -import { UnitDataTable } from "./units/unitdatatable"; +import { UnitDataTable } from "./atc/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; import { Popup } from "./popups/popup"; import { Dropdown } from "./controls/dropdown"; import { HotgroupPanel } from "./panels/hotgrouppanel"; -import "@iconfu/svg-inject"; - var map: Map; var unitsManager: UnitsManager; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 682c6cc9..f98a58c9 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -32,6 +32,8 @@ var destinationPreviewIcon = new L.DivIcon({ className: "ol-destination-preview" }) +const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; + export class ClickableMiniMap extends MiniMap { constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { super(layer, options); @@ -69,6 +71,7 @@ export class Map extends L.Map { #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); #mapSourceDropdown: Dropdown; + #optionButtons: { [key: string]: HTMLButtonElement[] } = {} constructor(ID: string) { /* Init the leaflet map */ @@ -130,6 +133,15 @@ export class Map extends L.Map { this.panBy(new L.Point( ((this.#panLeft? -1: 0) + (this.#panRight? 1: 0)) * this.#deafultPanDelta, ((this.#panUp? -1: 0) + (this.#panDown? 1: 0)) * this.#deafultPanDelta)); }, 20); + + /* Option buttons */ + this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", (e: any) => { + getUnitsManager().setHiddenType(option, (e?.currentTarget as HTMLElement)?.classList.contains("off")); + (e?.currentTarget as HTMLElement)?.classList.toggle("off"); + }); + }); + document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); } setLayer(layerName: string) { @@ -535,4 +547,13 @@ export class Map extends L.Map { this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey? latlng: this.getMouseCoordinates()); }) } + + #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { + var button = document.createElement("button"); + button.title = title; + button.value = value; + button.innerHTML = `` + button.addEventListener("click", callback); + return button; + } } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f6636c44..a7a7ec09 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -287,9 +287,15 @@ export class Unit extends Marker { /********************** Visibility *************************/ updateVisibility() { - this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || - document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null || - !this.getBaseData().alive) + var hidden = false; + const hiddenUnits = getUnitsManager().getHiddenTypes(); + if (this.getMissionData().flags.Human && hiddenUnits.includes("human")) + hidden = true; + else if (this.getBaseData().AI == false && hiddenUnits.includes("dcs")) + hidden = true; + else if (hiddenUnits.includes(this.getMarkerCategory())) + hidden = true; + this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || hidden || !this.getBaseData().alive); } setHidden(hidden: boolean) { @@ -739,7 +745,7 @@ export class GroundUnit extends Unit { getMarkerHTML() { var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; return `
-
+
${role?.substring(0, 1)?.toUpperCase() || ""}
@@ -750,7 +756,7 @@ export class GroundUnit extends Unit { getMarkerCategory() { // TODO this is very messy var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - var markerCategory = (role === "SAM") ? "sam" : "other"; + var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other"; return markerCategory; } } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index cba8fb29..07964d7a 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -10,6 +10,7 @@ export class UnitsManager { #copiedUnits: Unit[]; #selectionEventDisabled: boolean = false; #pasteDisabled: boolean = false; + #hiddenTypes: string[] = []; constructor() { this.#units = {}; @@ -87,6 +88,23 @@ export class UnitsManager { }); } + setHiddenType(key: string, value: boolean) + { + if (value) + { + if (this.#hiddenTypes.includes(key)) + delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)]; + } + else + this.#hiddenTypes.push(key); + Object.values(this.getUnits()).forEach((unit: Unit) => unit.updateVisibility()); + } + + getHiddenTypes() + { + return this.#hiddenTypes; + } + selectUnit(ID: number, deselectAllUnits: boolean = true) { if (deselectAllUnits) this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false)); diff --git a/client/views/aic.ejs b/client/views/aic/aic.ejs similarity index 100% rename from client/views/aic.ejs rename to client/views/aic/aic.ejs diff --git a/client/views/atc.ejs b/client/views/atc/atc.ejs similarity index 80% rename from client/views/atc.ejs rename to client/views/atc/atc.ejs index 19046c70..65984a3a 100644 --- a/client/views/atc.ejs +++ b/client/views/atc/atc.ejs @@ -1,10 +1,10 @@ -<%- include('atc/board.ejs', { +<%- include('board.ejs', { "boardId": "strip-board-tower", "boardType": "tower", "headers": [ "Flight", "a. Alt", "alt", "a. Speed", "Speed" ] }) %> -<%- include('atc/board.ejs', { +<%- include('board.ejs', { "boardId": "strip-board-ground", "boardType": "ground", "headers": [ "Flight", "Status", "T/O Time", "TTG" ] diff --git a/client/views/unitdatatable.ejs b/client/views/atc/unitdatatable.ejs similarity index 100% rename from client/views/unitdatatable.ejs rename to client/views/atc/unitdatatable.ejs diff --git a/client/views/index.ejs b/client/views/index.ejs index fe565ac0..863572aa 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -2,47 +2,39 @@ Olympus client - - - + + + + - - - - +
- <%- include('aic.ejs') %> - <%- include('atc.ejs') %> - <%- include('contextmenus.ejs') %> - <%- include('unitcontrolpanel.ejs') %> - <%- include('unitinfopanel.ejs') %> - <%- include('mouseinfopanel.ejs') %> - <%- include('navbar.ejs') %> - <%- include('connectionstatuspanel.ejs') %> - <%- include('dialogs.ejs') %> - <%- include('unitdatatable.ejs') %> - <%- include('popups.ejs') %> - <%- include('hotgrouppanel.ejs') %> + <%- include('aic/aic.ejs') %> -
+ <%- include('atc/atc.ejs') %> + <%- include('atc/unitdatatable.ejs') %> + + <%- include('panels/unitcontrol.ejs') %> + <%- include('panels/unitinfo.ejs') %> + <%- include('panels/mouseinfo.ejs') %> + <%- include('panels/connectionstatus.ejs') %> + <%- include('panels/hotgroup.ejs') %> + <%- include('panels/navbar.ejs') %> + + <%- include('other/dialogs.ejs') %> + <%- include('other/popups.ejs') %> + <%- include('other/contextmenus.ejs') %> + +
- <% /* %> - <%- include('log.ejs') %> - <% */ %> - diff --git a/client/views/log.ejs b/client/views/log/log.ejs similarity index 100% rename from client/views/log.ejs rename to client/views/log/log.ejs diff --git a/client/views/contextmenus.ejs b/client/views/other/contextmenus.ejs similarity index 100% rename from client/views/contextmenus.ejs rename to client/views/other/contextmenus.ejs diff --git a/client/views/dialogs.ejs b/client/views/other/dialogs.ejs similarity index 100% rename from client/views/dialogs.ejs rename to client/views/other/dialogs.ejs diff --git a/client/views/popups.ejs b/client/views/other/popups.ejs similarity index 100% rename from client/views/popups.ejs rename to client/views/other/popups.ejs diff --git a/client/views/connectionstatuspanel.ejs b/client/views/panels/connectionstatus.ejs similarity index 100% rename from client/views/connectionstatuspanel.ejs rename to client/views/panels/connectionstatus.ejs diff --git a/client/views/hotgrouppanel.ejs b/client/views/panels/hotgroup.ejs similarity index 100% rename from client/views/hotgrouppanel.ejs rename to client/views/panels/hotgroup.ejs diff --git a/client/views/mouseinfopanel.ejs b/client/views/panels/mouseinfo.ejs similarity index 100% rename from client/views/mouseinfopanel.ejs rename to client/views/panels/mouseinfo.ejs diff --git a/client/views/navbar.ejs b/client/views/panels/navbar.ejs similarity index 65% rename from client/views/navbar.ejs rename to client/views/panels/navbar.ejs index 4c46723c..06aeeeb9 100644 --- a/client/views/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -30,26 +30,7 @@
- - - - - +
diff --git a/client/views/unitcontrolpanel.ejs b/client/views/panels/unitcontrol.ejs similarity index 100% rename from client/views/unitcontrolpanel.ejs rename to client/views/panels/unitcontrol.ejs diff --git a/client/views/unitinfopanel.ejs b/client/views/panels/unitinfo.ejs similarity index 100% rename from client/views/unitinfopanel.ejs rename to client/views/panels/unitinfo.ejs diff --git a/client/views/uikit.ejs b/client/views/uikit/uikit.ejs similarity index 100% rename from client/views/uikit.ejs rename to client/views/uikit/uikit.ejs From e7ce9ac76dfc9b3bd8f4dbbfa64454d8dcada25b Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 24 May 2023 08:21:04 +0200 Subject: [PATCH 09/10] More svg injection, removing stringed html from code --- client/copy.bat | 3 + client/demo.js | 9 +- client/package-lock.json | 59 +- client/package.json | 6 +- client/prepare.bat | 2 + client/public/images/bullseye.png | Bin 1382 -> 0 bytes client/public/images/bullseye.xcf | Bin 263593 -> 0 bytes client/public/images/bullseye0.png | Bin 4552 -> 0 bytes client/public/images/bullseye1.png | Bin 3800 -> 0 bytes client/public/images/bullseye2.png | Bin 4357 -> 0 bytes client/public/images/buttons/edit.svg | 3 - client/public/images/buttons/reorder.svg | 21 - client/public/images/formations/azimuth.png | Bin 2145 -> 0 bytes client/public/images/formations/range.png | Bin 5057 -> 0 bytes client/public/images/formations/single.png | Bin 1895 -> 0 bytes client/public/images/icons/bullseye-solid.svg | 1 - client/public/images/icons/formation.png | Bin 19556 -> 0 bytes client/public/images/icons/leader.png | Bin 22070 -> 0 bytes client/public/images/icons/singleton.png | Bin 14026 -> 0 bytes .../images/icons/square-check-regular.svg | 1 - .../public/images/icons/trash-can-regular.svg | 1 - .../public/javascripts/leaflet.nauticscale.js | 38 + client/public/javascripts/svg-inject.js | 697 ------------------ client/public/stylesheets/layout/layout.css | 249 +------ client/public/stylesheets/markers/airbase.css | 39 +- .../public/stylesheets/markers/bullseye.css | 22 + client/public/stylesheets/markers/units.css | 50 +- client/public/stylesheets/olympus.css | 382 +++++++--- .../public/stylesheets/other/contextmenus.css | 226 +++--- client/public/stylesheets/other/popup.css | 10 - .../stylesheets/panels/connectionstatus.css | 16 +- .../public/stylesheets/panels/mouseinfo.css | 68 +- .../public/stylesheets/panels/unitcontrol.css | 33 +- client/public/stylesheets/panels/unitinfo.css | 107 ++- .../themes/olympus/images/actions/180.svg | 4 - .../themes/olympus/images/actions/cancel.svg | 3 - .../themes/olympus/images/actions/gas.svg | 3 - .../themes/olympus/images/actions/nothing.svg | 3 - .../themes/olympus/images/actions/rtb.svg | 3 - .../themes/olympus/images/actions/search.svg | 3 - .../images/buttons/emissions/attack.svg | 44 ++ .../images/buttons/emissions/defend.svg | 43 ++ .../olympus/images/buttons/emissions/free.svg | 43 ++ .../images/buttons/emissions/silent.svg | 43 ++ .../spawn_aircraft.svg => spawn/aircraft.svg} | 0 .../spawn_ground.svg => spawn/ground.svg} | 0 .../spawn_smoke.svg => spawn/smoke.svg} | 0 .../olympus/images/buttons/threat/evade.svg | 3 +- .../olympus}/images/icons/altitude.svg | 0 .../images/icons/arrow-pointer-solid.svg | 0 .../images/icons/arrows-to-eye-solid.svg | 0 .../images/{other => icons}/check_square.svg | 0 .../images/{other => icons}/chevron-down.svg | 0 .../olympus}/images/icons/echelon-lh.svg | 0 .../olympus}/images/icons/echelon-rh.svg | 0 .../olympus}/images/icons/echelon.svg | 0 .../olympus}/images/icons/follow.svg | 0 .../olympus}/images/icons/formation-end.svg | 0 .../images/icons/formation-middle.svg | 0 .../olympus}/images/icons/front.svg | 0 .../olympus}/images/icons/fuel.svg | 0 .../olympus}/images/icons/gears-solid.svg | 0 .../images/icons/grip-lines-solid.svg | 0 .../olympus}/images/icons/heading.svg | 0 .../olympus}/images/icons/line-abreast.svg | 0 .../images/{other => icons}/map_source.svg | 0 .../olympus/images/icons}/pin.png | Bin .../olympus}/images/icons/ruler.svg | 0 .../olympus}/images/icons/speed.svg | 0 .../images/icons/square-check-solid.svg | 0 .../olympus}/images/icons/square-regular.svg | 0 .../olympus}/images/icons/sword.svg | 0 .../olympus}/images/icons/trail.svg | 0 .../images/{mission => markers}/airbase.svg | 0 .../olympus/images/markers/bullseye.svg | 43 ++ .../olympus/images/markers}/marker-icon.png | Bin .../olympus/images/markers}/marker-shadow.png | Bin .../images/markers/temporary-icon.png} | Bin .../images/other/icons_misc_brush_blue.svg | 3 - .../images/other/icons_misc_brush_dark.svg | 3 - .../images/other/icons_misc_brush_light.svg | 3 - .../images/other/icons_misc_gas_blue.svg | 3 - .../images/other/icons_misc_gas_dark.svg | 3 - .../images/other/icons_misc_gas_light.svg | 3 - .../images/other/icons_misc_map_blue.svg | 3 - .../images/other/icons_misc_map_dark.svg | 3 - .../images/other/icons_misc_map_light.svg | 3 - .../images/other/icons_misc_plane_blue.svg | 3 - .../images/other/icons_misc_plane_dark.svg | 3 - .../images/other/icons_misc_plane_light.svg | 3 - .../images/other/icons_misc_settings_blue.svg | 3 - .../images/other/icons_misc_settings_dark.svg | 3 - .../other/icons_misc_settings_light.svg | 3 - .../images/other/icons_misc_visible_blue.svg | 3 - .../images/other/icons_misc_visible_dark.svg | 3 - .../images/other/icons_misc_visible_light.svg | 3 - .../olympus/images/splash/1.png} | Bin client/src/@types/server.d.ts | 2 +- client/src/controls/airbasecontextmenu.ts | 33 +- client/src/controls/contextmenu.ts | 15 +- client/src/controls/dropdown.ts | 52 +- client/src/controls/mapcontextmenu.ts | 14 +- client/src/controls/slider.ts | 55 +- client/src/controls/unitcontextmenu.ts | 23 +- client/src/features/featureswitches.ts | 52 +- client/src/features/toggleablefeature.ts | 12 +- client/src/map/boxselect.ts | 14 +- client/src/map/clickableminimap.ts | 12 + client/src/map/custommarker.ts | 24 + client/src/map/destinationpreviewmarker.ts | 15 + client/src/map/map.ts | 114 ++- client/src/map/temporaryunitmarker.ts | 13 + client/src/missionhandler/airbase.ts | 28 +- client/src/missionhandler/bullseye.ts | 36 + client/src/missionhandler/missionhandler.ts | 90 +-- client/src/other/utils.ts | 6 + client/src/panels/mouseinfopanel.ts | 4 +- client/src/panels/unitcontrolpanel.ts | 2 +- client/src/units/unit.ts | 223 ++++-- client/views/index.ejs | 4 +- client/views/panels/navbar.ejs | 2 +- client/views/panels/unitcontrol.ejs | 6 +- client/views/uikit/uikit.ejs | 82 +-- 123 files changed, 1439 insertions(+), 1829 deletions(-) create mode 100644 client/copy.bat create mode 100644 client/prepare.bat delete mode 100644 client/public/images/bullseye.png delete mode 100644 client/public/images/bullseye.xcf delete mode 100644 client/public/images/bullseye0.png delete mode 100644 client/public/images/bullseye1.png delete mode 100644 client/public/images/bullseye2.png delete mode 100644 client/public/images/buttons/edit.svg delete mode 100644 client/public/images/buttons/reorder.svg delete mode 100644 client/public/images/formations/azimuth.png delete mode 100644 client/public/images/formations/range.png delete mode 100644 client/public/images/formations/single.png delete mode 100644 client/public/images/icons/bullseye-solid.svg delete mode 100644 client/public/images/icons/formation.png delete mode 100644 client/public/images/icons/leader.png delete mode 100644 client/public/images/icons/singleton.png delete mode 100644 client/public/images/icons/square-check-regular.svg delete mode 100644 client/public/images/icons/trash-can-regular.svg create mode 100644 client/public/javascripts/leaflet.nauticscale.js delete mode 100644 client/public/javascripts/svg-inject.js create mode 100644 client/public/stylesheets/markers/bullseye.css delete mode 100644 client/public/themes/olympus/images/actions/180.svg delete mode 100644 client/public/themes/olympus/images/actions/cancel.svg delete mode 100644 client/public/themes/olympus/images/actions/gas.svg delete mode 100644 client/public/themes/olympus/images/actions/nothing.svg delete mode 100644 client/public/themes/olympus/images/actions/rtb.svg delete mode 100644 client/public/themes/olympus/images/actions/search.svg create mode 100644 client/public/themes/olympus/images/buttons/emissions/attack.svg create mode 100644 client/public/themes/olympus/images/buttons/emissions/defend.svg create mode 100644 client/public/themes/olympus/images/buttons/emissions/free.svg create mode 100644 client/public/themes/olympus/images/buttons/emissions/silent.svg rename client/public/themes/olympus/images/buttons/{other/spawn_aircraft.svg => spawn/aircraft.svg} (100%) rename client/public/themes/olympus/images/buttons/{other/spawn_ground.svg => spawn/ground.svg} (100%) rename client/public/themes/olympus/images/buttons/{other/spawn_smoke.svg => spawn/smoke.svg} (100%) rename client/public/{ => themes/olympus}/images/icons/altitude.svg (100%) rename client/public/{ => themes/olympus}/images/icons/arrow-pointer-solid.svg (100%) rename client/public/{ => themes/olympus}/images/icons/arrows-to-eye-solid.svg (100%) rename client/public/themes/olympus/images/{other => icons}/check_square.svg (100%) rename client/public/themes/olympus/images/{other => icons}/chevron-down.svg (100%) rename client/public/{ => themes/olympus}/images/icons/echelon-lh.svg (100%) rename client/public/{ => themes/olympus}/images/icons/echelon-rh.svg (100%) rename client/public/{ => themes/olympus}/images/icons/echelon.svg (100%) rename client/public/{ => themes/olympus}/images/icons/follow.svg (100%) rename client/public/{ => themes/olympus}/images/icons/formation-end.svg (100%) rename client/public/{ => themes/olympus}/images/icons/formation-middle.svg (100%) rename client/public/{ => themes/olympus}/images/icons/front.svg (100%) rename client/public/{ => themes/olympus}/images/icons/fuel.svg (100%) rename client/public/{ => themes/olympus}/images/icons/gears-solid.svg (100%) rename client/public/{ => themes/olympus}/images/icons/grip-lines-solid.svg (100%) rename client/public/{ => themes/olympus}/images/icons/heading.svg (100%) rename client/public/{ => themes/olympus}/images/icons/line-abreast.svg (100%) rename client/public/themes/olympus/images/{other => icons}/map_source.svg (100%) rename client/public/{images => themes/olympus/images/icons}/pin.png (100%) rename client/public/{ => themes/olympus}/images/icons/ruler.svg (100%) rename client/public/{ => themes/olympus}/images/icons/speed.svg (100%) rename client/public/{ => themes/olympus}/images/icons/square-check-solid.svg (100%) rename client/public/{ => themes/olympus}/images/icons/square-regular.svg (100%) rename client/public/{ => themes/olympus}/images/icons/sword.svg (100%) rename client/public/{ => themes/olympus}/images/icons/trail.svg (100%) rename client/public/themes/olympus/images/{mission => markers}/airbase.svg (100%) create mode 100644 client/public/themes/olympus/images/markers/bullseye.svg rename client/public/{images => themes/olympus/images/markers}/marker-icon.png (100%) rename client/public/{images => themes/olympus/images/markers}/marker-shadow.png (100%) rename client/public/{images/icon-temporary.png => themes/olympus/images/markers/temporary-icon.png} (100%) delete mode 100644 client/public/themes/olympus/images/other/icons_misc_brush_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_brush_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_brush_light.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_gas_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_gas_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_gas_light.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_map_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_map_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_map_light.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_plane_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_plane_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_plane_light.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_settings_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_settings_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_settings_light.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_visible_blue.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_visible_dark.svg delete mode 100644 client/public/themes/olympus/images/other/icons_misc_visible_light.svg rename client/public/{images/splash/splash_pic_ship.png => themes/olympus/images/splash/1.png} (100%) create mode 100644 client/src/map/clickableminimap.ts create mode 100644 client/src/map/custommarker.ts create mode 100644 client/src/map/destinationpreviewmarker.ts create mode 100644 client/src/map/temporaryunitmarker.ts create mode 100644 client/src/missionhandler/bullseye.ts diff --git a/client/copy.bat b/client/copy.bat new file mode 100644 index 00000000..1acc220e --- /dev/null +++ b/client/copy.bat @@ -0,0 +1,3 @@ +copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css +copy .\\node_modules\\@iconfu\\svg-inject\\dist\\svg-inject.js .\\public\\javascripts\\svg-inject.js +copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js diff --git a/client/demo.js b/client/demo.js index ac750b83..292e2f52 100644 --- a/client/demo.js +++ b/client/demo.js @@ -677,15 +677,18 @@ class DemoDataGenerator { var ret = {bullseyes: { "0": { latitude: 37.25, - longitude: -115.8 + longitude: -115.8, + coalition: "neutral" }, "1": { latitude: 37.25, - longitude: -115.75 + longitude: -115.75, + coalition: "red" }, "2": { latitude: 37.25, - longitude: -115.7 + longitude: -115.7, + coalition: "blue" } }}; ret.time = Date.now(); diff --git a/client/package-lock.json b/client/package-lock.json index b4457d1c..44990dba 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -25,6 +25,7 @@ "devDependencies": { "@babel/preset-env": "^7.21.4", "@iconfu/svg-inject": "^1.2.3", + "@tanem/svg-injector": "^10.1.55", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", @@ -1688,9 +1689,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -1831,6 +1832,17 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@tanem/svg-injector": { + "version": "10.1.55", + "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.55.tgz", + "integrity": "sha512-xh8ejdvjDaH1eddZC0CdI45eeid4BIU2ppjNEhiTiWMYcLGT19KWjbES/ttDS4mq9gIAQfXx57g5zimEVohqYA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.5", + "content-type": "^1.0.5", + "tslib": "^2.5.0" + } + }, "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", @@ -2824,9 +2836,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -5344,9 +5356,9 @@ } }, "node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", "dev": true }, "node_modules/tty-browserify": { @@ -6842,9 +6854,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "requires": { "regenerator-runtime": "^0.13.11" @@ -6960,6 +6972,17 @@ } } }, + "@tanem/svg-injector": { + "version": "10.1.55", + "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.55.tgz", + "integrity": "sha512-xh8ejdvjDaH1eddZC0CdI45eeid4BIU2ppjNEhiTiWMYcLGT19KWjbES/ttDS4mq9gIAQfXx57g5zimEVohqYA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.5", + "content-type": "^1.0.5", + "tslib": "^2.5.0" + } + }, "@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", @@ -7768,9 +7791,9 @@ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==" }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.1.3", @@ -9806,9 +9829,9 @@ } }, "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", "dev": true }, "tty-browserify": { diff --git a/client/package.json b/client/package.json index 678d17e5..8fa54c31 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "version": "v0.2.1-alpha", "private": true, "scripts": { - "copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css & copy .\\node_modules\\@iconfu\\svg-inject\\dist\\svg-inject.js .\\public\\javascripts\\svg-inject.js", + "copy": "copy.bat", "start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"", "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" }, @@ -16,17 +16,15 @@ "debug": "~2.6.9", "ejs": "^3.1.8", "express": "~4.16.1", - "geomag": "^1.0.0", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", "leaflet.nauticscale": "^1.1.0", - "milsymbol": "^2.0.0", "morgan": "~1.9.1", "save": "^2.9.0" }, "devDependencies": { "@babel/preset-env": "^7.21.4", - "@iconfu/svg-inject": "^1.2.3", + "@tanem/svg-injector": "^10.1.55", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", diff --git a/client/prepare.bat b/client/prepare.bat new file mode 100644 index 00000000..f163f70e --- /dev/null +++ b/client/prepare.bat @@ -0,0 +1,2 @@ +copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css +copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js diff --git a/client/public/images/bullseye.png b/client/public/images/bullseye.png deleted file mode 100644 index f70249546be7042ad275a9ff5d40cb7b0186ddcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1382 zcmV-s1)2JZP)EX>4Tx04R}tkv&MmP!xqvQ>7vm2Rn##$WWauh>EC_RIvyaN?V~-2a}inL6e3g z#l=x@EjakISaoo5*44pP5CnffTwR{K$3L|nWrS; zby?xO#aXS?SnHnrg~7bGlIA+CFydH30!fIFQ9~IOScuZDkzyi6=P?iekmFC1OD0zt zj2sK7LWSh`!T;d*Y|X;NxSJG=0o^aQ{V@y#c7bNyw!e>UyLkfmpMfi_?XNa~=}*$@ zZ7p&H^lt+f*KJMS11@)f!6#iZBuDbo5()+2{fxdT2MpW-y=(5=TIV=@05UYIy{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j>AC2RkYPd~jv}000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0009$NklE=>@?WZIN8kQbo(NUeIRmER!(Y(IgT)RJtdOJ&0dDNu%h+Z0+-7y4)_l;R)* zEO6M}+1Z)>=bt(Fk3=u4sFZpPWWc*+^v$w{^F05~f1drTp=mU}*btFEaICd{1317L zW8MJABGS*YY~#TURw|XYh#ZMX4mhRM2{2Gfz0_L2^xr3d6Omj*j-n`P-^*wihFxGJ zB13EKp0&1FSnm080~W2DaUB1ulo|q~D2jes)@~1QK@hwE+H=->CG%~sTxI24ZsYGpHi}S_-4l_#)oQJO%G*BR;`kQ5KD?JjqtBlGBEUYd zzq`9DGsZ8y4gmfv=g*!i$37$3zBhT)Vz!Z7@L9)WnHlYJi%NVC~o zUr3q0v`r~Mc3rPRXKM=903 z0n$zD6UTA)UQClD$;O;!@0C*ORavuFtyUkd+w}Fc5B#RJJ}&_4+>=9pVahy^^p<|Z o`~1R)$e+fTi#*TY@qd!P0POehO~k`~g8%>k07*qoM6N<$g0tU+Z~y=R diff --git a/client/public/images/bullseye.xcf b/client/public/images/bullseye.xcf deleted file mode 100644 index 097722f6875070d185e944d4580c54f5eab676ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263593 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�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

&cU@WqEP}0pxAU_lzzUQW0PTlI3>uc9v=VN-- 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 - - diff --git a/client/public/images/buttons/reorder.svg b/client/public/images/buttons/reorder.svg deleted file mode 100644 index 4ae9b04e..00000000 --- a/client/public/images/buttons/reorder.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/client/public/images/formations/azimuth.png b/client/public/images/formations/azimuth.png deleted file mode 100644 index 1264e20a86bbb26860fa032c4f3cad1cb53789b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2145 zcmbtVYfuwc6i%^L#j%~vSn%0y!{|UI**p-sfsjEGo(YctB@BWMn*|n3Hf|OYK(Mur z)rz8ku|X@A7I2`gBcd$`#iAf0U^OZu27DnFg#{Evi9$D#S39MD+?m}y=icx8&bjBF zySpO(99MCB@sx&+upKjIVQMP3m zqytt6k<6#RPD&;T0lYCB0o<5u2Fzly05^yU!7%KiKp?I&z+rJXAd3sKT^MW^9?Ol# z;Q$jCoeGF6;(1b`&qO%Nq;MNO=A#4Ix*-h`*3erzDsts^GCjLN1#vguEcgg-tnL!nNKI#P;Th zI4qGj*L#vH#NZSqq9P`_ifOLbtXxYwDAiPCA%df+h{6ZQlz?^3JaqP4+-B9A;3{U% z<Kh zGk08jO^n~>|B>>SFtS*4!X|XRk1zSX%Ikc6w3nEHe}MX%I#CovAcE^G5U;DX!Q^ z3T_$B``NS&`)^9v&vD*4rX9cEYl|L|t9o_*0T3gB;-FFi6h6U6Y~AnTRWfvfdxmE*lx8qJfTYH`VXpOx=lU$M~L*j6Rr^Cbi6 zg#vEvh@>!F9J?=}0~#qWFK=&eulsOQN$@wTk2P`o4Q5kOvy^k}Hn(4IE)-omPy$M} zc=4;w6{5eI1a=Xc4JY$k9LL0FXSx6^>yZWY#P?U7`hn!-CZB%>*Z$xP?)#nbaDnN}UH?Yk0&QD$~i+jIxKF_E5LsdW?{?s@#ZtXt>Vr9I@*6t$8Y5bC@(9 zyxPzp+MRp@ud1o7<$HRnT<_)Oz|J=M{ zMaG<#%4Q^f)W5c-#`*c+SaI)eEZzKMOTa01qqzJKiP%RhC->=dTeI25$E6v&Mb}mY zUU1!RKwlVZ8;g?m6f2gWZK+g~{osy40cWYHc`D4uem6{VRhi5;%;5e7?Mzpk(c#f=yUJYZl7CM*l<{o zw$eUgD7YlM@o;Z!)ksWuL!CC><(XK!LFgm1Y>11ozBFT-=-T$4V)G!x-A9q z=XMSeIWH&SCiXTr#_>RjwxDUH@Gm^#MOhp&6jKM(lpnKwH6fcA~(Grk;u dny|Otp*4g85}&QM`Ieu)zbHuf%ev3E{tY-Tq+$R7 diff --git a/client/public/images/formations/range.png b/client/public/images/formations/range.png deleted file mode 100644 index 9df137cb0eb556708b0a3841ff25c225470fefe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5057 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 diff --git a/client/public/images/formations/single.png b/client/public/images/formations/single.png deleted file mode 100644 index 2a92faee67cca47b4cecfd235283dbe3c53169c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1895 zcmb_d4NMbP9PivF#(_FBGhndZs-RBWyK9TkgTe|eQfE;L8N_8~$K3%Z+y~c#7ATqx zN8E-~9s7t3iHdVB%a+aPB1BBwEAFHcWU@!2>8uVt^@{P9 zGYW_%^@{8m3u56@r~*2zT%a<`)3b>3LPAF>k`kc=2L=cjO2i=tQ^eXZhhE|5#egj> z!wSgnA{OcuQ%U7mHbW_#KtZu8R7oHR0>$c7I)WgyB&E}=h17^z4I>&D)hbafhQwlO zH8lMwKtO@a!>q>C>2P4CR}_dMkHN6rZdchgDo)6UQJqc)BWhT!Rss*Dt&A0Mhmy4| zpJ6akHbS6zk>*%PV#IT~5>c-J$xc*ZHRyC26g3%jS}kfY#pqJvVhnMy zDTW!Wkt0eNiWO(DWB{9t%!!rc!0;flkrL=qicA$a2J+X8(R1fwoRe=FOU{{#!89ip z2FbwE$<8|InI#|}$qYyrOakJkSfF?TNc)7c(+zISC9~0x<+wcBYhf?HAgk_c65Yjd zK@q{f^N%fQAByQ()*XJMcImNXb6bDA=D-(?`5C6BH6uMg-@bn3WMviY8+;P7?}_h- zys=0KI#qSm;F+4vG6Oq*aRNK*r~Ly?68s^iz|Mby@131u=B=AmH~0L+xuD}C6U{#s zon8F)x!iU2Wl*{ojgMHgBWcuIv1$2n?@31gM$!43zX_*`_DA1KXmq>U3Ew56xY_l6 zY0Ld0{eq;?(s;MgGazCOXY0GiR`yj!JBKhAX5 zlgoEJwSC!xs-r8sdwmgd;!MnjJYv^IPpPv8-GE(N-|siNDuY{HE;QAADtr8{ueZ1% zW<%HMTvu%kSh@5`g>%%m4go8P*9M68meve)WD2>|TO$FER#2WWIZ?Au_`GujULi3I zOpJV#VfB;@6>ii5km%m+8g7l+Q;{qg9+?y0HQKXgfT+p%Zg}g**=_BihnudxSm!zS z^{Un0{-&0f!lMSazIXBPLBMfj$nNZ(9DC0G?1gdP2a_zay1L{2rcfzfy7#psenXI3 zA3PllID+1pydxEjREG!c-Y2+~ZePy%uim=4(r+~2+Tgv53-{-p*g`!+ZYS%+=KL?O zj8!xDdqCV{r_(~C1``h!9s*J6ch}7WNgTMPjpUtcC$FS>4y#9fIS*brw6MCPr4C7K z3yo^<=-1$&FPWgk%49eg6uxN)1K}%@wuRjV?HKR7xR!T*>U}xQV|rrzo5yQi6W7uC z`MYYKFPmz8W7e;}-d|R30~zn!8VnGP7f!fW4PT4=(DVQ4E^t%+wdx;|?KHeLaLEE! j1G3LDL)(%bnJ?S2BqD77x87>$*Jn1R8$U~a_4Pjh6RrzR diff --git a/client/public/images/icons/bullseye-solid.svg b/client/public/images/icons/bullseye-solid.svg deleted file mode 100644 index c6400970..00000000 --- a/client/public/images/icons/bullseye-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/images/icons/formation.png b/client/public/images/icons/formation.png deleted file mode 100644 index b140e9f63e07a795d4a22ccbb257607eb1c6dee6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19556 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 z 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 E0%|ysq5uE@ diff --git a/client/public/images/icons/square-check-regular.svg b/client/public/images/icons/square-check-regular.svg deleted file mode 100644 index 4ce0ff82..00000000 --- a/client/public/images/icons/square-check-regular.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/images/icons/trash-can-regular.svg b/client/public/images/icons/trash-can-regular.svg deleted file mode 100644 index 011e1a5e..00000000 --- a/client/public/images/icons/trash-can-regular.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/javascripts/leaflet.nauticscale.js b/client/public/javascripts/leaflet.nauticscale.js new file mode 100644 index 00000000..ccf19ac3 --- /dev/null +++ b/client/public/javascripts/leaflet.nauticscale.js @@ -0,0 +1,38 @@ +L.Control.ScaleNautic = L.Control.Scale.extend({ + options: { + nautic: false + }, + + _addScales: function(options, className, container) { + L.Control.Scale.prototype._addScales.call(this, options, className, container); + L.setOptions(options); + if (this.options.nautic) { + this._nScale = L.DomUtil.create('div', className, container); + } + }, + + _updateScales: function (maxMeters) { + L.Control.Scale.prototype._updateScales.call(this, maxMeters); + if (this.options.nautic && maxMeters) { + this._updateNautic(maxMeters); + } + }, + + _updateNautic: function (maxMeters) { + var scale = this._nScale, + maxNauticalMiles = maxMeters / 1852, nauticalMiles; + + if(maxMeters >= 1852) { + nauticalMiles = L.Control.Scale.prototype._getRoundNum.call(this, maxNauticalMiles); + } else { + nauticalMiles = maxNauticalMiles > 0.1 ? Math.round(maxNauticalMiles * 10) / 10 : Math.round(maxNauticalMiles * 100) / 100; + } + + scale.style.width = Math.round(this.options.maxWidth * (nauticalMiles / maxNauticalMiles)) - 10 + 'px'; + scale.innerHTML = nauticalMiles + ' nm'; + } +}); + +L.control.scalenautic = function (options) { + return new L.Control.ScaleNautic(options); +}; diff --git a/client/public/javascripts/svg-inject.js b/client/public/javascripts/svg-inject.js deleted file mode 100644 index 4e74af07..00000000 --- a/client/public/javascripts/svg-inject.js +++ /dev/null @@ -1,697 +0,0 @@ -/** - * SVGInject - Version 1.2.3 - * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM. - * - * https://github.com/iconfu/svg-inject - * - * Copyright (c) 2018 INCORS, the creators of iconfu.com - * @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE - */ - -(function(window, document) { - // constants for better minification - var _CREATE_ELEMENT_ = 'createElement'; - var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName'; - var _LENGTH_ = 'length'; - var _STYLE_ = 'style'; - var _TITLE_ = 'title'; - var _UNDEFINED_ = 'undefined'; - var _SET_ATTRIBUTE_ = 'setAttribute'; - var _GET_ATTRIBUTE_ = 'getAttribute'; - - var NULL = null; - - // constants - var __SVGINJECT = '__svgInject'; - var ID_SUFFIX = '--inject-'; - var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g"); - var LOAD_FAIL = 'LOAD_FAIL'; - var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED'; - var SVG_INVALID = 'SVG_INVALID'; - var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror']; - var A_ELEMENT = document[_CREATE_ELEMENT_]('a'); - var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_; - var DEFAULT_OPTIONS = { - useCache: true, - copyAttributes: true, - makeIdsUnique: true - }; - // Map of IRI referenceable tag names to properties that can reference them. This is defined in - // https://www.w3.org/TR/SVG11/linking.html#processingIRI - var IRI_TAG_PROPERTIES_MAP = { - clipPath: ['clip-path'], - 'color-profile': NULL, - cursor: NULL, - filter: NULL, - linearGradient: ['fill', 'stroke'], - marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'], - mask: NULL, - pattern: ['fill', 'stroke'], - radialGradient: ['fill', 'stroke'] - }; - var INJECTED = 1; - var FAIL = 2; - - var uniqueIdCounter = 1; - var xmlSerializer; - var domParser; - - - // creates an SVG document from an SVG string - function svgStringToSvgDoc(svgStr) { - domParser = domParser || new DOMParser(); - return domParser.parseFromString(svgStr, 'text/xml'); - } - - - // searializes an SVG element to an SVG string - function svgElemToSvgString(svgElement) { - xmlSerializer = xmlSerializer || new XMLSerializer(); - return xmlSerializer.serializeToString(svgElement); - } - - - // Returns the absolute url for the specified url - function getAbsoluteUrl(url) { - A_ELEMENT.href = url; - return A_ELEMENT.href; - } - - - // Load svg with an XHR request - function loadSvg(url, callback, errorCallback) { - if (url) { - var req = new XMLHttpRequest(); - req.onreadystatechange = function() { - if (req.readyState == 4) { - // readyState is DONE - var status = req.status; - if (status == 200) { - // request status is OK - callback(req.responseXML, req.responseText.trim()); - } else if (status >= 400) { - // request status is error (4xx or 5xx) - errorCallback(); - } else if (status == 0) { - // request status 0 can indicate a failed cross-domain call - errorCallback(); - } - } - }; - req.open('GET', url, true); - req.send(); - } - } - - - // Copy attributes from img element to svg element - function copyAttributes(imgElem, svgElem) { - var attribute; - var attributeName; - var attributeValue; - var attributes = imgElem.attributes; - for (var i = 0; i < attributes[_LENGTH_]; i++) { - attribute = attributes[i]; - attributeName = attribute.name; - // Only copy attributes not explicitly excluded from copying - if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) { - attributeValue = attribute.value; - // If img attribute is "title", insert a title element into SVG element - if (attributeName == _TITLE_) { - var titleElem; - var firstElementChild = svgElem.firstElementChild; - if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) { - // If the SVG element's first child is a title element, keep it as the title element - titleElem = firstElementChild; - } else { - // If the SVG element's first child element is not a title element, create a new title - // ele,emt and set it as the first child - titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_); - svgElem.insertBefore(titleElem, firstElementChild); - } - // Set new title content - titleElem.textContent = attributeValue; - } else { - // Set img attribute to svg element - svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue); - } - } - } - } - - - // This function appends a suffix to IDs of referenced elements in the in order to to avoid ID collision - // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is - // incremented with each injection. References to the IDs are adjusted accordingly. - // We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one - // injected SVG. - // If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG - function makeIdsUnique(svgElem, onlyReferenced) { - var idSuffix = ID_SUFFIX + uniqueIdCounter++; - // Regular expression for functional notations of an IRI references. This will find occurences in the form - // url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID - var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; - // Get all elements with an ID. The SVG spec recommends to put referenced elements inside elements, but - // this is not a requirement, therefore we have to search for IDs in the whole SVG. - var idElements = svgElem.querySelectorAll('[id]'); - var idElem; - // An object containing referenced IDs as keys is used if only referenced IDs should be uniquified. - // If this object does not exist, all IDs will be uniquified. - var referencedIds = onlyReferenced ? [] : NULL; - var tagName; - var iriTagNames = {}; - var iriProperties = []; - var changed = false; - var i, j; - - if (idElements[_LENGTH_]) { - // Make all IDs unique by adding the ID suffix and collect all encountered tag names - // that are IRI referenceable from properities. - for (i = 0; i < idElements[_LENGTH_]; i++) { - tagName = idElements[i].localName; // Use non-namespaced tag name - // Make ID unique if tag name is IRI referenceable - if (tagName in IRI_TAG_PROPERTIES_MAP) { - iriTagNames[tagName] = 1; - } - } - // Get all properties that are mapped to the found IRI referenceable tags - for (tagName in iriTagNames) { - (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { - // Add mapped properties to array of iri referencing properties. - // Use linear search here because the number of possible entries is very small (maximum 11) - if (iriProperties.indexOf(mappedProperty) < 0) { - iriProperties.push(mappedProperty); - } - }); - } - if (iriProperties[_LENGTH_]) { - // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' - iriProperties.push(_STYLE_); - } - // Run through all elements of the SVG and replace IDs in references. - // To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*'). - // Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately. - var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); - var element = svgElem; - var propertyName; - var value; - var newValue; - for (i = -1; element != NULL;) { - if (element.localName == _STYLE_) { - // If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content - value = element.textContent; - newValue = value && value.replace(funcIriRegex, function(match, id) { - if (referencedIds) { - referencedIds[id] = 1; - } - return 'url(#' + id + idSuffix + ')'; - }); - if (newValue !== value) { - element.textContent = newValue; - } - } else if (element.hasAttributes()) { - // Run through all property names for which IDs were found - for (j = 0; j < iriProperties[_LENGTH_]; j++) { - propertyName = iriProperties[j]; - value = element[_GET_ATTRIBUTE_](propertyName); - newValue = value && value.replace(funcIriRegex, function(match, id) { - if (referencedIds) { - referencedIds[id] = 1; - } - return 'url(#' + id + idSuffix + ')'; - }); - if (newValue !== value) { - element[_SET_ATTRIBUTE_](propertyName, newValue); - } - } - // Replace IDs in xlink:ref and href attributes - ['xlink:href', 'href'].forEach(function(refAttrName) { - var iri = element[_GET_ATTRIBUTE_](refAttrName); - if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference - iri = iri.trim(); - element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix); - if (referencedIds) { - // Add ID to referenced IDs - referencedIds[iri.substring(1)] = 1; - } - } - }); - } - element = descElements[++i]; - } - for (i = 0; i < idElements[_LENGTH_]; i++) { - idElem = idElements[i]; - // If set of referenced IDs exists, make only referenced IDs unique, - // otherwise make all IDs unique. - if (!referencedIds || referencedIds[idElem.id]) { - // Add suffix to element's ID - idElem.id += idSuffix; - changed = true; - } - } - } - // return true if SVG element has changed - return changed; - } - - - // For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a - // higher ID counter. This is much more performant than a call to makeIdsUnique(). - function makeIdsUniqueCached(svgString) { - return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++); - } - - - // Inject SVG by replacing the img element with the SVG element in the DOM - function inject(imgElem, svgElem, absUrl, options) { - if (svgElem) { - svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl); - var parentNode = imgElem.parentNode; - if (parentNode) { - if (options.copyAttributes) { - copyAttributes(imgElem, svgElem); - } - // Invoke beforeInject hook if set - var beforeInject = options.beforeInject; - var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem; - // Replace img element with new element. This is the actual injection. - parentNode.replaceChild(injectElem, imgElem); - // Mark img element as injected - imgElem[__SVGINJECT] = INJECTED; - removeOnLoadAttribute(imgElem); - // Invoke afterInject hook if set - var afterInject = options.afterInject; - if (afterInject) { - afterInject(imgElem, injectElem); - } - } - } else { - svgInvalid(imgElem, options); - } - } - - - // Merges any number of options objects into a new object - function mergeOptions() { - var mergedOptions = {}; - var args = arguments; - // Iterate over all specified options objects and add all properties to the new options object - for (var i = 0; i < args[_LENGTH_]; i++) { - var argument = args[i]; - for (var key in argument) { - if (argument.hasOwnProperty(key)) { - mergedOptions[key] = argument[key]; - } - } - } - return mergedOptions; - } - - - // Adds the specified CSS to the document's element - function addStyleToHead(css) { - var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; - if (head) { - var style = document[_CREATE_ELEMENT_](_STYLE_); - style.type = 'text/css'; - style.appendChild(document.createTextNode(css)); - head.appendChild(style); - } - } - - - // Builds an SVG element from the specified SVG string - function buildSvgElement(svgStr, verify) { - if (verify) { - var svgDoc; - try { - // Parse the SVG string with DOMParser - svgDoc = svgStringToSvgDoc(svgStr); - } catch(e) { - return NULL; - } - if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { - // DOMParser does not throw an exception, but instead puts parsererror tags in the document - return NULL; - } - return svgDoc.documentElement; - } else { - var div = document.createElement('div'); - div.innerHTML = svgStr; - return div.firstElementChild; - } - } - - - function removeOnLoadAttribute(imgElem) { - // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and - // make the element visible, not for removing the event listener. - imgElem.removeAttribute('onload'); - } - - - function errorMessage(msg) { - console.error('SVGInject: ' + msg); - } - - - function fail(imgElem, status, options) { - imgElem[__SVGINJECT] = FAIL; - if (options.onFail) { - options.onFail(imgElem, status); - } else { - errorMessage(status); - } - } - - - function svgInvalid(imgElem, options) { - removeOnLoadAttribute(imgElem); - fail(imgElem, SVG_INVALID, options); - } - - - function svgNotSupported(imgElem, options) { - removeOnLoadAttribute(imgElem); - fail(imgElem, SVG_NOT_SUPPORTED, options); - } - - - function loadFail(imgElem, options) { - fail(imgElem, LOAD_FAIL, options); - } - - - function removeEventListeners(imgElem) { - imgElem.onload = NULL; - imgElem.onerror = NULL; - } - - - function imgNotSet(msg) { - errorMessage('no img element'); - } - - - function createSVGInject(globalName, options) { - var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); - var svgLoadCache = {}; - - if (IS_SVG_SUPPORTED) { - // If the browser supports SVG, add a small stylesheet that hides the elements until - // injection is finished. This avoids showing the unstyled SVGs before style is applied. - addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); - } - - - /** - * SVGInject - * - * Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img` - * elements. Returns a Promise object which resolves if all passed in `img` elements have either been - * injected or failed to inject (Only if a global Promise object is available like in all modern browsers - * or through a polyfill). - * - * Options: - * useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`. - * copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value - * is `true`. - * makeIdsUnique: If set to `true` the ID of elements in the `` element that can be references by - * property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a - * running number which increases with each injection. This is done to avoid duplicate IDs in the DOM. - * beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns - * a string it is used as the URL instead of the `img` element's `src` attribute. - * afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a - * parameters. If caching is active this hook will only get called once for injected SVGs with the - * same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs - * with the same absolute path. It's also possible to return an `svg` string or `svg` element which - * will then be used for the injection. - * beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If - * any html element is returned it gets injected instead of applying the default SVG injection. - * afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters. - * onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or - * failed to inject. - * onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter. - * The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG), - * `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed). - * - * @param {HTMLImageElement} img - an img element or an array of img elements - * @param {Object} [options] - optional parameter with [options](#options) for this injection. - */ - function SVGInject(img, options) { - options = mergeOptions(defaultOptions, options); - - var run = function(resolve) { - var allFinish = function() { - var onAllFinish = options.onAllFinish; - if (onAllFinish) { - onAllFinish(); - } - resolve && resolve(); - }; - - if (img && typeof img[_LENGTH_] != _UNDEFINED_) { - // an array like structure of img elements - var injectIndex = 0; - var injectCount = img[_LENGTH_]; - - if (injectCount == 0) { - allFinish(); - } else { - var finish = function() { - if (++injectIndex == injectCount) { - allFinish(); - } - }; - - for (var i = 0; i < injectCount; i++) { - SVGInjectElement(img[i], options, finish); - } - } - } else { - // only one img element - SVGInjectElement(img, options, allFinish); - } - }; - - // return a Promise object if globally available - return typeof Promise == _UNDEFINED_ ? run() : new Promise(run); - } - - - // Injects a single svg element. Options must be already merged with the default options. - function SVGInjectElement(imgElem, options, callback) { - if (imgElem) { - var svgInjectAttributeValue = imgElem[__SVGINJECT]; - if (!svgInjectAttributeValue) { - removeEventListeners(imgElem); - - if (!IS_SVG_SUPPORTED) { - svgNotSupported(imgElem, options); - callback(); - return; - } - // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load - // URL path. Else use the imgElem's src attribute value. - var beforeLoad = options.beforeLoad; - var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src'); - - if (!src) { - // If no image src attribute is set do no injection. This can only be reached by using javascript - // because if no src attribute is set the onload and onerror events do not get called - if (src === '') { - loadFail(imgElem, options); - } - callback(); - return; - } - - // set array so later calls can register callbacks - var onFinishCallbacks = []; - imgElem[__SVGINJECT] = onFinishCallbacks; - - var onFinish = function() { - callback(); - onFinishCallbacks.forEach(function(onFinishCallback) { - onFinishCallback(); - }); - }; - - var absUrl = getAbsoluteUrl(src); - var useCacheOption = options.useCache; - var makeIdsUniqueOption = options.makeIdsUnique; - - var setSvgLoadCacheValue = function(val) { - if (useCacheOption) { - svgLoadCache[absUrl].forEach(function(svgLoad) { - svgLoad(val); - }); - svgLoadCache[absUrl] = val; - } - }; - - if (useCacheOption) { - var svgLoad = svgLoadCache[absUrl]; - - var handleLoadValue = function(loadValue) { - if (loadValue === LOAD_FAIL) { - loadFail(imgElem, options); - } else if (loadValue === SVG_INVALID) { - svgInvalid(imgElem, options); - } else { - var hasUniqueIds = loadValue[0]; - var svgString = loadValue[1]; - var uniqueIdsSvgString = loadValue[2]; - var svgElem; - - if (makeIdsUniqueOption) { - if (hasUniqueIds === NULL) { - // IDs for the SVG string have not been made unique before. This may happen if previous - // injection of a cached SVG have been run with the option makedIdsUnique set to false - svgElem = buildSvgElement(svgString, false); - hasUniqueIds = makeIdsUnique(svgElem, false); - - loadValue[0] = hasUniqueIds; - loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem); - } else if (hasUniqueIds) { - // Make IDs unique for already cached SVGs with better performance - svgString = makeIdsUniqueCached(uniqueIdsSvgString); - } - } - - svgElem = svgElem || buildSvgElement(svgString, false); - - inject(imgElem, svgElem, absUrl, options); - } - onFinish(); - }; - - if (typeof svgLoad != _UNDEFINED_) { - // Value for url exists in cache - if (svgLoad.isCallbackQueue) { - // Same url has been cached, but value has not been loaded yet, so add to callbacks - svgLoad.push(handleLoadValue); - } else { - handleLoadValue(svgLoad); - } - return; - } else { - var svgLoad = []; - // set property isCallbackQueue to Array to differentiate from array with cached loaded values - svgLoad.isCallbackQueue = true; - svgLoadCache[absUrl] = svgLoad; - } - } - - // Load the SVG because it is not cached or caching is disabled - loadSvg(absUrl, function(svgXml, svgString) { - // Use the XML from the XHR request if it is an instance of Document. Otherwise - // (for example of IE9), create the svg document from the svg string. - var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true); - - var afterLoad = options.afterLoad; - if (afterLoad) { - // Invoke afterLoad hook which may modify the SVG element. After load may also return a new - // svg element or svg string - var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem; - if (svgElemOrSvgString) { - // Update svgElem and svgString because of modifications to the SVG element or SVG string in - // the afterLoad hook, so the modified SVG is also used for all later cached injections - var isString = typeof svgElemOrSvgString == 'string'; - svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem); - svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString; - } - } - - if (svgElem instanceof SVGElement) { - var hasUniqueIds = NULL; - if (makeIdsUniqueOption) { - hasUniqueIds = makeIdsUnique(svgElem, false); - } - - if (useCacheOption) { - var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem); - // set an array with three entries to the load cache - setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]); - } - - inject(imgElem, svgElem, absUrl, options); - } else { - svgInvalid(imgElem, options); - setSvgLoadCacheValue(SVG_INVALID); - } - onFinish(); - }, function() { - loadFail(imgElem, options); - setSvgLoadCacheValue(LOAD_FAIL); - onFinish(); - }); - } else { - if (Array.isArray(svgInjectAttributeValue)) { - // svgInjectAttributeValue is an array. Injection is not complete so register callback - svgInjectAttributeValue.push(callback); - } else { - callback(); - } - } - } else { - imgNotSet(); - } - } - - - /** - * Sets the default [options](#options) for SVGInject. - * - * @param {Object} [options] - default [options](#options) for an injection. - */ - SVGInject.setOptions = function(options) { - defaultOptions = mergeOptions(defaultOptions, options); - }; - - - // Create a new instance of SVGInject - SVGInject.create = createSVGInject; - - - /** - * Used in onerror Event of an `` element to handle cases when the loading the original src fails - * (for example if file is not found or if the browser does not support SVG). This triggers a call to the - * options onFail hook if available. The optional second parameter will be set as the new src attribute - * for the img element. - * - * @param {HTMLImageElement} img - an img element - * @param {String} [fallbackSrc] - optional parameter fallback src - */ - SVGInject.err = function(img, fallbackSrc) { - if (img) { - if (img[__SVGINJECT] != FAIL) { - removeEventListeners(img); - - if (!IS_SVG_SUPPORTED) { - svgNotSupported(img, defaultOptions); - } else { - removeOnLoadAttribute(img); - loadFail(img, defaultOptions); - } - if (fallbackSrc) { - removeOnLoadAttribute(img); - img.src = fallbackSrc; - } - } - } else { - imgNotSet(); - } - }; - - window[globalName] = SVGInject; - - return SVGInject; - } - - var SVGInjectInstance = createSVGInject('SVGInject'); - - if (typeof module == 'object' && typeof module.exports == 'object') { - module.exports = SVGInjectInstance; - } -})(window, document); \ No newline at end of file diff --git a/client/public/stylesheets/layout/layout.css b/client/public/stylesheets/layout/layout.css index dfa23432..91926b4b 100644 --- a/client/public/stylesheets/layout/layout.css +++ b/client/public/stylesheets/layout/layout.css @@ -7,240 +7,69 @@ #primary-toolbar { align-items: center; - display:flex; - position: absolute; + display: flex; left: 10px; + position: absolute; top: 10px; z-index: 1000; } -#olympus-toolbar-summary { +#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; - text-indent: 60px; padding: 20px; + text-indent: 60px; } -dl.ol-data-grid { - align-items: center; - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: 0; - row-gap: 4px; -} - -dl.ol-data-grid dt { - width: 60%; -} - -dl.ol-data-grid dd { - width: 40%; -} - - -dl.ol-data-grid dt.icon { - text-indent: 10px; -} - -dl.ol-data-grid dt.icon::before { - content: url(/images/icons/speed.svg ); - display: inline-block; - filter: invert(100%); - width: 20px; - translate: -20px 2px; -} - - -dl.ol-data-grid dt.icon-speed::before { - content: url(/images/icons/speed.svg ); -} - - -dl.ol-data-grid dt.icon-altitude::before { - content: url(/images/icons/altitude.svg ); -} - -dl.ol-data-grid dd { - display: flex; - justify-content: flex-end; - margin-left: auto; -} - -.br-info::after { - content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units); -} - -.br-info[data-message]::after { - content: attr(data-message); -} - -.coordinates::after { - content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label); -} - -.ol-button-box { - column-gap: 6px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: 5px 0; - row-gap: 5px; -} - -.ol-button-box button { - background-repeat: no-repeat; - ; - border: 1px solid var(--accent-light-blue); - color: var(--accent-light-blue); -} - -.ol-dialog { - align-self: center; - background-color: var(--background-slate-blue); - color: white; - justify-self: center; +#connection-status-panel { + bottom: 20px; + font-size: 12px; position: absolute; + right: 10px; + width: 160px; z-index: 1000; } -.ol-panel.ol-dialog { - padding: 24px 30px; -} - -.ol-dialog-close { - cursor: pointer; - font-size: 16px; - font-weight: var(--font-weight-bolder); +#mouse-info-panel { + bottom: 60px; + display: flex; + flex-direction: column; + height: fit-content; position: absolute; - right: 20px; - top: 10px; -} - -.ol-dialog-close::before { - content: "\d7"; -} - -.ol-dialog-header { - border-bottom: 1px solid var(--background-grey); - padding-bottom: 10px; -} - -.ol-dialog-content { - margin:4px 0; -} - -.ol-dialog-footer { - border-top: 1px solid var(--background-grey); - padding-top: 15px; - display: flex; + right: 10px; row-gap: 10px; + width: 160px; + z-index: 1000; } -.ol-dialog.scrollable .ol-dialog-content { - overflow-y: auto; +#unit-control-panel { + height: fit-content; + left: 10px; + position: absolute; + top: 80px; + width: 240px; + z-index: 1000; } -.ol-checkbox label { - align-items: center; - cursor: pointer; - display: flex; - flex-wrap: nowrap; - white-space: nowrap; +#unit-info-panel { + bottom: 20px; + font-size: 12px; + left: 10px; + position: absolute; + width: fit-content; + z-index: 1000; } -.ol-checkbox input[type="checkbox"] { - appearance: none; - background-color: transparent; - border: none; - margin: 0; -} - -.ol-checkbox input[type="checkbox"]::before { - align-self: center; - background-image: url("/images/icons/square-regular.svg"); - background-repeat: no-repeat; - content: ""; - filter: invert(100%); - display: flex; - height: 16px; - margin-right: 10px; - width: 16px; -} - -.ol-checkbox input[type="checkbox"]:checked::before { - background-image: url("/images/icons/square-check-solid.svg"); -} - -.ol-text-input input { - height: 32px; - border-radius: 5px; - color: var(--background-offwhite); - background-color: var(--background-grey); - border: 1px solid var(--background-grey); - border-radius: var(--border-radius-sm); - text-align: center; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); -} - -input[type=number] { - -moz-appearance: textfield; - appearance: textfield; - margin: 0; -} - -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - -[class|="ol-button"] { - align-items: center; - background-repeat: no-repeat; - display: flex; - font-weight: normal; - padding: 8px 10px; - white-space: nowrap; -} - -[class|="ol-button"]::before { - margin-right: 8px; -} - -.ol-button-close { - background: transparent; - border: 1px solid white; -} - -.ol-button-close::before { - content: "\d7"; -} - -.ol-button-apply { - background: transparent; - border: 1px solid white; -} - -.ol-button-apply::before { - content: "\2713"; -} - -.ol-button-settings { - background-color: var(--background-slate-blue); -} - -.ol-button-settings::before { - background-image: url("/images/icons/gears-solid.svg"); - background-position: 0 50%; - background-size: 24px 24px; - content: ""; - display: flex; - filter: invert(100%); - height: 24px; - width: 24px; +#info-popup { + position: absolute; + width: fit-content; + height: fit-content; + top: 100px; + left: 50%; + translate: -50% 0%; + z-index: 9999; } \ No newline at end of file diff --git a/client/public/stylesheets/markers/airbase.css b/client/public/stylesheets/markers/airbase.css index fac5497b..a9640cf3 100644 --- a/client/public/stylesheets/markers/airbase.css +++ b/client/public/stylesheets/markers/airbase.css @@ -1,9 +1,4 @@ -:root { - --airbase-marker-height: 40px; - --airbase-marker-width: 40px; -} - -[data-object|="airbase"] { +.airbase-icon { align-items: center; cursor: pointer; display: flex; @@ -11,35 +6,15 @@ position: relative; } -[data-hide-airbase] #map-container [data-object|="airbase"] { - display: none; +.airbase-icon[data-coalition="red"] svg * { + stroke: var(--unit-background-red); } -/****************************** -Marker -******************************/ - -[data-object|="airbase"] .airbase { - background-color: transparent; - background-repeat: no-repeat; - background-size: cover; - position: absolute; - transform-origin: center; - z-index: 3; +.airbase-icon[data-coalition="blue"] svg * { + stroke: var(--unit-background-blue); } -/* Airbase */ -[data-object|="airbase"] .airbase-marker { - background-image: var(--airbase-marker-neutral-url); - background-size: contain; - height: var(--airbase-marker-height); - width: var(--airbase-marker-width); +.airbase-icon[data-coalition="neutral"] svg * { + stroke: var(--unit-background-neutral); } -[data-object|="airbase"][data-coalition="red"] .airbase-marker { - background-image: var(--airbase-marker-red-url); -} - -[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/markers/bullseye.css b/client/public/stylesheets/markers/bullseye.css new file mode 100644 index 00000000..81663b09 --- /dev/null +++ b/client/public/stylesheets/markers/bullseye.css @@ -0,0 +1,22 @@ +.bullseye-icon { + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + position: relative; +} + +.bullseye-icon[data-coalition="red"] svg * { + stroke: var(--unit-background-red); + fill: var(--unit-background-red); +} + +.bullseye-icon[data-coalition="blue"] svg * { + stroke: var(--unit-background-blue); + fill: var(--unit-background-blue); +} + +.bullseye-icon[data-coalition="neutral"] svg * { + stroke: var(--unit-background-neutral); + fill: var(--unit-background-neutral); +} diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 121a1059..056a92d3 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -9,9 +9,9 @@ align-items: center; cursor: pointer; display: flex; + height: 100%; justify-content: center; position: relative; - height: 100%; width: 100%; } @@ -20,10 +20,10 @@ background: var(--secondary-gunmetal-grey); display: flex; justify-self: center; - transform-origin: bottom; - translate: 0 -50%; padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width)); position: absolute; + transform-origin: bottom; + translate: 0 -50%; width: var(--unit-aircraft-vvi-width); } @@ -49,41 +49,41 @@ translate: -1px 1px; } -.unit-marker { +.unit-icon { + height: var(--unit-height); position: absolute; transform-origin: center; - height: var(--unit-height); width: var(--unit-width); } -[data-is-selected] .unit-marker::before { - content: ""; - height: 100%; - width: 100%; +[data-is-selected] .unit-icon::before { background-color: var(--unit-spotlight-fill); border-radius: 50%; + content: ""; + height: 100%; position: absolute; + width: 100%; z-index: -1; } /*** Basic colours ***/ -[data-coalition="blue"] .unit-marker svg>*:first-child { +[data-coalition="blue"] .unit-icon svg>*:first-child { fill: var(--unit-background-blue); } -[data-coalition="red"] .unit-marker svg>*:first-child { +[data-coalition="red"] .unit-icon svg>*:first-child { fill: var(--unit-background-red); } -[data-coalition="neutral"] .unit-marker svg>*:first-child { +[data-coalition="neutral"] .unit-icon svg>*:first-child { fill: var(--unit-background-neutral); } -[data-is-selected] .unit-marker svg>*:first-child { +[data-is-selected] .unit-icon svg>*:first-child { fill: white; } -[data-is-highlighted] .unit-marker svg>*:first-child { +[data-is-highlighted] .unit-icon svg>*:first-child { stroke: white; } @@ -140,15 +140,15 @@ /*** Unit summary ***/ [data-object|="unit"] .unit-summary { - pointer-events: none; - column-gap: 6px; color: white; + column-gap: 6px; display: flex; flex-wrap: wrap; font-size: 11px; font-weight: bold; justify-content: right; line-height: 12px; + pointer-events: none; position: absolute; row-gap: 1px; text-shadow: @@ -247,41 +247,41 @@ /*** Unit state ***/ [data-object|="unit"] .unit-state { background-repeat: no-repeat; - position: absolute; height: 20px; + position: absolute; width: 20px; } [data-object|="unit"][data-state="rtb"] .unit-state { - background-image: url("/theme/images/states/rtb.svg"); + background-image: url("/resources/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="land"] .unit-state { - background-image: url("/theme/images/states/rtb.svg"); + background-image: url("/resources/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="idle"] .unit-state { - background-image: url("/theme/images/states/idle.svg"); + background-image: url("/resources/theme/images/states/idle.svg"); } [data-object|="unit"][data-state="attack"] .unit-state { - background-image: url("/theme/images/states/attack.svg"); + background-image: url("/resources/theme/images/states/attack.svg"); } [data-object|="unit"][data-state="follow"] .unit-state { - background-image: url("/theme/images/states/follow.svg"); + background-image: url("/resources/theme/images/states/follow.svg"); } [data-object|="unit"][data-state="refuel"] .unit-state { - background-image: url("/theme/images/states/refuel.svg"); + background-image: url("/resources/theme/images/states/refuel.svg"); } [data-object|="unit"][data-state="human"] .unit-state { - background-image: url("/theme/images/states/human.svg"); + background-image: url("/resources/theme/images/states/human.svg"); } [data-object|="unit"][data-state="dcs"] .unit-state { - background-image: url("/theme/images/states/dcs.svg"); + background-image: url("/resources/theme/images/states/dcs.svg"); } /*** Dead unit ***/ diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 413afbc1..d32ccc42 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -9,6 +9,7 @@ @import url("other/contextmenus.css"); @import url("other/popup.css"); @import url("markers/airbase.css"); +@import url("markers/bullseye.css"); @import url("markers/units.css"); * { @@ -88,8 +89,8 @@ form>div { .ol-scrollable::-webkit-scrollbar-track { background-color: transparent; - border-top-right-radius: 10px; border-bottom-right-radius: 10px; + border-top-right-radius: 10px; margin-top: 0px; } @@ -104,8 +105,8 @@ form>div { .ol-scrollable::-webkit-scrollbar-thumb { background-color: white; border-radius: 100px; - opacity: 0.8; margin-top: 10px; + opacity: 0.8; } .ol-panel { @@ -137,15 +138,15 @@ form>div { .ol-ellipsed { display: inline-block; - width: calc(100%); overflow: hidden; - text-overflow: ellipsis; text-align: left; + text-overflow: ellipsis; + width: calc(100%); } .ol-select { - position: relative; color: var(--nav-text); + position: relative; } .ol-select>.ol-select-value { @@ -154,10 +155,10 @@ form>div { cursor: pointer; display: flex; justify-content: left; + min-width: 0; text-align: center; white-space: nowrap; width: 100%; - min-width: 0; } .ol-select:not(.ol-select-image)>.ol-select-value { @@ -165,12 +166,12 @@ form>div { background-color: var(--background-grey); border-radius: var(--border-radius-sm); height: 32px; - padding-right: 30px; - padding-left: 20px; - - width: calc(100%); overflow: hidden; + padding-left: 20px; + padding-right: 30px; text-overflow: ellipsis; + + width: calc(100%); } .ol-select.narrow:not(.ol-select-image)>.ol-select-value { @@ -183,22 +184,22 @@ form>div { } .ol-select:not(.ol-select-image)>.ol-select-value:after { + content: url("/resources/theme/images/icons/chevron-down.svg"); position: absolute; - content: url("/themes/olympus/images/chevron-down.svg"); right: 10px; } .ol-select>.ol-select-options { border-radius: var(--border-radius-md); + max-height: 0; overflow: hidden; position: absolute; - max-height: 0; z-index: 1000; } .ol-select-options.scrollbar-visible { - border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; + border-top-right-radius: 0px !important; } .ol-select.ol-select-image>.ol-select-options { @@ -207,11 +208,11 @@ form>div { .ol-select.is-open>.ol-select-options { max-height: 382px; + min-width: 100%; overflow: visible; overflow-y: auto; - min-width: 100%; - z-index: 9999; translate: 0px 5px; + z-index: 9999; } .ol-select.is-open[data-position="top"]>.ol-select-options { @@ -247,16 +248,16 @@ form>div { background-color: transparent; border: none; border-radius: 0; + border-radius: var(--border-radius-sm); color: white; display: block; font-size: 12px; font-weight: normal; padding: 6px 2px; + padding: 5px; text-align: left; white-space: nowrap; width: 100%; - padding: 5px; - border-radius: var(--border-radius-sm); } .ol-select>.ol-select-options>div a:hover, @@ -368,13 +369,13 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group { + align-items: center; border-radius: var(--border-radius-sm); column-gap: 10px; display: flex; flex-direction: row; flex-wrap: nowrap; row-gap: 4px; - align-items: center; } .ol-group-header { @@ -405,7 +406,7 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group-button-toggle button::before { - background-image: url("/images/icons/square-check-solid.svg"); + background-image: url("/resources/theme/images/icons/square-check-solid.svg"); background-repeat: no-repeat; content: ""; filter: invert(100%); @@ -415,7 +416,7 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group-button-toggle button.off::before { - background-image: url("/images/icons/square-regular.svg"); + background-image: url("/resources/theme/images/icons/square-regular.svg"); } .highlight-primary { @@ -470,9 +471,9 @@ nav.ol-panel> :last-child { } .icon-small { - width: 20px; - padding: 2px; filter: invert(100%); + padding: 2px; + width: 20px; } .ol-data-grid { @@ -485,17 +486,17 @@ nav.ol-panel> :last-child { } .slider { - width: 100%; -webkit-appearance: none; appearance: none; - height: 2px; background: #d3d3d3; - outline: none; + height: 2px; + margin-bottom: 10px; + margin-top: 10px; opacity: 0.7; + outline: none; -webkit-transition: .2s; transition: opacity .2s; - margin-top: 10px; - margin-bottom: 10px; + width: 100%; } .slider:hover { @@ -505,11 +506,11 @@ nav.ol-panel> :last-child { .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; - width: 20px; - height: 20px; background: gray; - cursor: pointer; border-radius: 999px; + cursor: pointer; + height: 20px; + width: 20px; } .active .slider::-webkit-slider-thumb { @@ -517,11 +518,11 @@ nav.ol-panel> :last-child { } .slider::-moz-range-thumb { - width: 20px; - height: 20px; background: gray; - cursor: pointer; border-radius: 999px; + cursor: pointer; + height: 20px; + width: 20px; } .active .slider::-moz-range-thumb { @@ -529,25 +530,25 @@ nav.ol-panel> :last-child { } .main-logo { - width: 40px; height: 40px; + width: 40px; } .ol-measure-box { - position: absolute; + background-color: var(--background-steel); + border-radius: 999px; + color: var(--primary-neutral); + font-size: 12px; + font-weight: var(--font-weight-bolder); + height: fit-content; + padding-bottom: 0.2em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.2em; - padding-bottom: 0.2em; - background-color: var(--background-steel); - border-radius: 999px; - width: fit-content; - height: fit-content; + position: absolute; text-align: center; - color: var(--primary-neutral); - font-size: 12px; + width: fit-content; z-index: 2000; - font-weight: var(--font-weight-bolder); } .ol-sortable .handle { @@ -575,7 +576,7 @@ nav.ol-panel> :last-child { width: 28px; } -#unit-selection #unit-identification [data-object|="unit"] .unit-marker { +#unit-selection #unit-identification [data-object|="unit"] .unit-icon { background-size: 28px 28px; height: 28px; width: 28px; @@ -588,13 +589,13 @@ nav.ol-panel> :last-child { #unit-visibility-control button { border: none; height: 32px; - width: 32px; padding: 0px; + width: 32px; } #unit-visibility-control button svg { - pointer-events: none; height: 16px; + pointer-events: none; width: 16px; } @@ -633,31 +634,27 @@ nav.ol-panel> :last-child { #roe-buttons-container button, #reaction-to-threat-buttons-container button, #emissions-countermeasures-buttons-container button { - display: flex; + align-items: center; background-color: transparent; border: 1px solid var(--accent-light-blue); + display: flex; height: 30px; - width: 30px; - align-items: center; justify-content: center; + width: 30px; } -#roe-buttons-container button.selected, -#reaction-to-threat-buttons-container button.selected, -#emissions-countermeasures-buttons-container button.selected { +#unit-control-panel .ol-option-button button.selected { background-color: white; border-color: white; } -#roe-buttons-container button.selected svg .foreground, -#reaction-to-threat-buttons-container button.selected svg .foreground, -#emissions-countermeasures-buttons-container button.selected svg .foreground { +#unit-control-panel .ol-option-button button.selected svg * { fill: var(--background-steel); } /****************************************************************************************/ #splash-screen { - background-image: url("/images/splash/splash_pic_ship.png"); + background-image: url("/resources/theme/images/splash/1.png"); background-position: 100% 50%; background-size: 60%; border-radius: var(--border-radius-lg); @@ -707,9 +704,9 @@ nav.ol-panel> :last-child { #splash-content #app-summary>* { height: fit-content; line-height: 25px; + padding: 2px; white-space: nowrap; width: fit-content; - padding: 2px; } #splash-content .app-version { @@ -721,8 +718,8 @@ nav.ol-panel> :last-child { } #splash-content #legal-stuff p { + color: #FFF7; font-size: 10px; - color:#FFF7; width: 120%; } @@ -735,34 +732,34 @@ nav.ol-panel> :last-child { } #gray-out { - position: fixed; - height: 100%; - width: 100%; - left: 0px; - top: 0px; - z-index: 9999; background-color: #000A; + height: 100%; + left: 0px; + position: fixed; + top: 0px; + width: 100%; + z-index: 9999; } #authentication-form { - display: flex; align-items: end; column-gap: 10px; - margin: 10px 0px; + display: flex; flex-direction: row; + margin: 10px 0px; } #authentication-form>div { - display: flex; align-items: start; - row-gap: 4px; + display: flex; flex-direction: column; + row-gap: 4px; } #authentication-form>div>input { - height: 35px; - border-radius: var(--border-radius-sm); border: 0px solid transparent; + border-radius: var(--border-radius-sm); + height: 35px; width: 200px; } @@ -776,13 +773,13 @@ nav.ol-panel> :last-child { } #connection-status[data-status="connecting"]::before { - content: "Connecting..."; animation: blinker 1s linear infinite; + content: "Connecting..."; } #connection-status[data-status="failed"]::before { - content: "Incorrect username/password!"; color: var(--primary-red); + content: "Incorrect username/password!"; } @keyframes blinker { @@ -792,31 +789,31 @@ nav.ol-panel> :last-child { } #hotgroup-panel { - position: absolute; bottom: 40px; + column-gap: 10px; + display: flex; left: 50%; + position: absolute; translate: -50%; z-index: 9999; - display: flex; - column-gap: 10px; } #hotgroup-panel>div { - width: 50px; - height: 50px; + align-items: center; background-color: var(--background-steel); + border: 0px solid transparent; border-radius: var(--border-radius-sm); color: white; display: flex; - align-items: center; - justify-content: center; font-weight: bold; - border: 0px solid transparent; + height: 50px; + justify-content: center; + width: 50px; } #hotgroup-panel>div:hover { - cursor: pointer; border: 2px solid white; + cursor: pointer; } .hotgroup-selector>.unit-hotgroup { @@ -825,13 +822,230 @@ nav.ol-panel> :last-child { } .ol-destination-preview-icon { - width: 52px; - height: 52px; background-color: var(--secondary-yellow); - pointer-events: none; border-radius: 999px; + height: 52px; + pointer-events: none; + width: 52px; } .ol-destination-preview { pointer-events: none; +} + +dl.ol-data-grid { + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + row-gap: 4px; +} + +dl.ol-data-grid dt { + width: 60%; +} + +dl.ol-data-grid dd { + width: 40%; +} + +dl.ol-data-grid dt.icon { + text-indent: 10px; +} + +dl.ol-data-grid dt.icon::before { + content: url("/resources/theme/images/icons/speed.svg"); + display: inline-block; + filter: invert(100%); + translate: -20px 2px; + width: 20px; +} + +dl.ol-data-grid dt.icon-speed::before { + content: url("/images/icons/speed.svg"); +} + +dl.ol-data-grid dt.icon-altitude::before { + content: url("/images/icons/altitude.svg"); +} + +dl.ol-data-grid dd { + display: flex; + justify-content: flex-end; + margin-left: auto; +} + +.br-info::after { + content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units); +} + +.br-info[data-message]::after { + content: attr(data-message); +} + +.coordinates::after { + content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label); +} + +.ol-button-box { + column-gap: 6px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 5px 0; + row-gap: 5px; +} + +.ol-button-box button { + background-repeat: no-repeat; + ; + border: 1px solid var(--accent-light-blue); + color: var(--accent-light-blue); +} + +.ol-dialog { + align-self: center; + background-color: var(--background-slate-blue); + color: white; + justify-self: center; + position: absolute; + z-index: 1000; +} + +.ol-panel.ol-dialog { + padding: 24px 30px; +} + +.ol-dialog-close { + cursor: pointer; + font-size: 16px; + font-weight: var(--font-weight-bolder); + position: absolute; + right: 20px; + top: 10px; +} + +.ol-dialog-close::before { + content: "\d7"; +} + +.ol-dialog-header { + border-bottom: 1px solid var(--background-grey); + padding-bottom: 10px; +} + +.ol-dialog-content { + margin: 4px 0; +} + +.ol-dialog-footer { + border-top: 1px solid var(--background-grey); + display: flex; + padding-top: 15px; + row-gap: 10px; +} + +.ol-dialog.scrollable .ol-dialog-content { + overflow-y: auto; +} + +.ol-checkbox label { + align-items: center; + cursor: pointer; + display: flex; + flex-wrap: nowrap; + white-space: nowrap; +} + +.ol-checkbox input[type="checkbox"] { + appearance: none; + background-color: transparent; + border: none; + margin: 0; +} + +.ol-checkbox input[type="checkbox"]::before { + align-self: center; + background-image: url("/resources/theme/images/icons/square-regular.svg"); + background-repeat: no-repeat; + content: ""; + display: flex; + filter: invert(100%); + height: 16px; + margin-right: 10px; + width: 16px; +} + +.ol-checkbox input[type="checkbox"]:checked::before { + background-image: url("/resources/theme/images/icons/square-check-solid.svg"); +} + +.ol-text-input input { + background-color: var(--background-grey); + border: 1px solid var(--background-grey); + border-radius: 5px; + border-radius: var(--border-radius-sm); + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + color: var(--background-offwhite); + height: 32px; + text-align: center; +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + margin: 0; +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +[class|="ol-button"] { + align-items: center; + background-repeat: no-repeat; + display: flex; + font-weight: normal; + padding: 8px 10px; + white-space: nowrap; +} + +[class|="ol-button"]::before { + margin-right: 8px; +} + +.ol-button-close { + background: transparent; + border: 1px solid white; +} + +.ol-button-close::before { + content: "\d7"; +} + +.ol-button-apply { + background: transparent; + border: 1px solid white; +} + +.ol-button-apply::before { + content: "\2713"; +} + +.ol-button-settings { + background-color: var(--background-slate-blue); +} + +.ol-button-settings::before { + background-image: url("/resources/theme/images/icons/gears-solid.svg"); + background-position: 0 50%; + background-size: 24px 24px; + content: ""; + display: flex; + filter: invert(100%); + height: 24px; + width: 24px; } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 573bba4b..361a6a0b 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -17,23 +17,23 @@ } #active-coalition-label { - position: absolute; - top: -28px; border-radius: 999px; - width: fit-content; + color: var(--nav-text); + font-size: 12px; + font-weight: 600; height: fit-content; padding: 3px 10px; padding-bottom: 3px; - font-weight: 600; - color: var(--nav-text); - font-size: 12px; + position: absolute; + top: -28px; + width: fit-content; } #context-menu-switch { margin-right: 10px; } -#map-contextmenu>div:nth-child(2){ +#map-contextmenu>div:nth-child(2) { align-items: center; display: flex; flex-direction: row; @@ -41,22 +41,22 @@ padding-right: 0px; } -#map-contextmenu>ul{ +#map-contextmenu>ul { max-height: 200px; overflow-x: hidden; overflow-y: auto; } #map-contextmenu .ol-panel { - width: 100%; border-radius: var(--border-radius-sm); + width: 100%; } #map-contextmenu ul { margin: 0px; } -#map-contextmenu>div:nth-child(n+3){ +#map-contextmenu>div:nth-child(n+3) { align-items: center; display: flex; flex-direction: column; @@ -64,10 +64,10 @@ row-gap: 5px; } -#map-contextmenu .ol-select-container{ - width: 100%; - flex:0 0 auto; +#map-contextmenu .ol-select-container { align-self: stretch; + flex: 0 0 auto; + width: 100%; } #aircraft-spawn-menu .ol-select.is-open .ol-select-options { @@ -76,28 +76,28 @@ #aircraft-spawn-menu>button, #ground-unit-spawn-menu>button { - width: 100%; text-align: center; + width: 100%; } #aircraft-spawn-button { - background-image: var( --spawn-aircraft-url ); + background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); background-size: 48px; } #ground-unit-spawn-button { - background-image: var( --spawn-groundunit-url ); + background-image: url("/resources/theme/images/buttons/spawn/ground.svg"); background-size: 48px; } #smoke-spawn-button { - background-image: var( --spawn-smoke-url ); + background-image: url("/resources/theme/images/buttons/spawn/smoke.svg"); background-size: 48px; } .unit-spawn-button { - border-radius: 0px; border: none; + border-radius: 0px; height: 48px; margin-bottom: -10px; margin-top: -10px; @@ -105,37 +105,34 @@ } .unit-spawn-button:last-of-type { - border-top-right-radius: var(--border-radius-sm); border-bottom-right-radius: var(--border-radius-sm); + border-top-right-radius: var(--border-radius-sm); } [data-active-coalition="blue"].toggle-fill, -[data-active-coalition="blue"].unit-spawn-button:hover, -[data-active-coalition="blue"].unit-spawn-button.is-open, +[data-active-coalition="blue"].unit-spawn-button:hover, +[data-active-coalition="blue"].unit-spawn-button.is-open, [data-active-coalition="blue"]#active-coalition-label, [data-active-coalition="blue"].deploy-unit-button, -[data-active-coalition="blue"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="blue"]#spawn-airbase-aircraft-button { background-color: var(--primary-blue) } [data-active-coalition="red"].toggle-fill, -[data-active-coalition="red"].unit-spawn-button:hover, -[data-active-coalition="red"].unit-spawn-button.is-open, +[data-active-coalition="red"].unit-spawn-button:hover, +[data-active-coalition="red"].unit-spawn-button.is-open, [data-active-coalition="red"]#active-coalition-label, [data-active-coalition="red"].deploy-unit-button, -[data-active-coalition="red"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="red"]#spawn-airbase-aircraft-button { background-color: var(--primary-red) } [data-active-coalition="neutral"].toggle-fill, -[data-active-coalition="neutral"].unit-spawn-button:hover, -[data-active-coalition="neutral"].unit-spawn-button.is-open, +[data-active-coalition="neutral"].unit-spawn-button:hover, +[data-active-coalition="neutral"].unit-spawn-button.is-open, [data-active-coalition="neutral"]#active-coalition-label, [data-active-coalition="neutral"].deploy-unit-button, -[data-active-coalition="neutral"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="neutral"]#spawn-airbase-aircraft-button { background-color: var(--primary-neutral) } @@ -144,11 +141,13 @@ border: 1px solid var(--primary-blue); cursor: default; } + [data-active-coalition="red"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-red); cursor: default; } + [data-active-coalition="neutral"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-neutral); @@ -158,9 +157,11 @@ [data-active-coalition="blue"].toggle-fill::after { transform: translateX(0); } + [data-active-coalition="red"].toggle-fill::after { transform: translateX(var(--height)); } + [data-active-coalition="neutral"].toggle-fill::after { transform: translateX(calc(var(--height) / 2)); } @@ -168,182 +169,198 @@ [data-active-coalition="blue"]#active-coalition-label::after { content: "Create blue unit"; } + [data-active-coalition="red"]#active-coalition-label::after { content: "Create red unit"; } + [data-active-coalition="neutral"]#active-coalition-label::after { content: "Create neutral unit"; } #loadout-preview { - display: flex; - flex-direction: row; align-content: space-between; align-items: center; - width: 100%; column-gap: 20px; + display: flex; + flex-direction: row; + width: 100%; } #loadout-list { + align-content: center; display: flex; flex-direction: column; - align-content: center; height: 100%; } #unit-image { - width: 100px; - height: 100px; filter: invert(100%); - margin-top: 10px; + height: 100px; margin-bottom: 10px; + margin-top: 10px; + width: 100px; } #smoke-spawn-menu { + align-items: center; display: flex; flex-direction: column; - align-items: center; text-align: center; } #smoke-spawn-menu>button { - width: 100%; - text-align: left; - display: flex; - flex-wrap: wrap; align-items: center; column-gap: 10px; + display: flex; + flex-wrap: wrap; + text-align: left; + width: 100%; } #smoke-spawn-menu>button::before { - display: block; - width: 10px; - height: 10px; border-radius: 999px; content: ""; + display: block; + height: 10px; + width: 10px; } -[data-smoke-color="red"]::before{ background-color: red; } -[data-smoke-color="white"]::before{ background-color: white; } -[data-smoke-color="blue"]::before{ background-color: blue; } -[data-smoke-color="green"]::before{ background-color: green; } -[data-smoke-color="orange"]::before{ background-color: orange; } +[data-smoke-color="red"]::before { + background-color: red; +} + +[data-smoke-color="white"]::before { + background-color: white; +} + +[data-smoke-color="blue"]::before { + background-color: blue; +} + +[data-smoke-color="green"]::before { + background-color: green; +} + +[data-smoke-color="orange"]::before { + background-color: orange; +} /* Unit context menu */ #unit-contextmenu { display: flex; flex-direction: column; height: fit-content; - width: fit-content; + padding: 15px; position: absolute; row-gap: 5px; + width: fit-content; z-index: 1000; - padding: 15px; } #unit-contextmenu button { border: 1px solid var(--background-offwhite); border-radius: var(--border-radius-sm); - padding: 12px; font-weight: normal; + padding: 12px; } #unit-contextmenu div { + align-content: center; display: flex; flex-direction: row; - align-content: center; } #unit-contextmenu div:before { - display: inline-block; - filter: invert(100%); - height: 16px; - margin-right: 15px; - width: 16px; + display: inline-block; + filter: invert(100%); + height: 16px; + margin-right: 15px; + width: 16px; } #center-map::before { - content: url( /images/icons/arrows-to-eye-solid.svg ); + content: url("/resources/theme/images/icons/arrows-to-eye-solid.svg"); } #refuel::before { - content: url( /images/icons/fuel.svg ); + content: url("/resources/theme/images/icons/fuel.svg"); } #attack::before { - content: url( /images/icons/sword.svg ); + content: url("/resources/theme/images/icons/sword.svg"); } #follow::before { - content: url( /images/icons/follow.svg ); + content: url("/resources/theme/images/icons/follow.svg"); } #trail::before { - content: url( /images/icons/trail.svg ); + content: url("/resources/theme/images/icons/trail.svg"); } #echelon-lh::before { - content: url( /images/icons/echelon-lh.svg ); + content: url("/resources/theme/images/icons/echelon-lh.svg"); } #echelon-rh::before { - content: url( /images/icons/echelon-rh.svg ); + content: url("/resources/theme/images/icons/echelon-rh.svg"); } #line-abreast::before { - content: url( /images/icons/line-abreast.svg ); + content: url("/resources/theme/images/icons/line-abreast.svg"); } #front::before { - content: url( /images/icons/front.svg ); + content: url("/resources/theme/images/icons/front.svg"); } #custom::before { - content: url( /images/icons/custom.svg ); + content: url("/resources/theme/images/icons/custom.svg"); } #custom-formation-dialog { - width: 250px; + width: 250px; } -#custom-formation-dialog > .ol-dialog-content { - margin-top: 10px; - margin-bottom: 10px; - display: flex; - flex-direction: column; - flex-wrap: nowrap; - row-gap: 10px; +#custom-formation-dialog>.ol-dialog-content { align-items: center; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + margin-bottom: 10px; + margin-top: 10px; + row-gap: 10px; } -#custom-formation-dialog > .ol-dialog-content > .ol-group { +#custom-formation-dialog>.ol-dialog-content>.ol-group { + justify-content: space-between; width: 100%; - justify-content: space-between; } #reference-system { - content: url( /images/reference-system.svg ); + content: url("/images/reference-system.svg"); display: inline-block; - filter: invert(100%); - width: 50px; - transform: translate(-50%, -50%); + filter: invert(100%); position: absolute; + transform: translate(-50%, -50%); + width: 50px; } .formation-position-clock { - position: relative; height: 100px; - width: 100px; margin: 15px; + position: relative; + width: 100px; } -.formation-position-clock > .clock-hand { - transform: translate(-50%, -50%); - display: flex; - position: absolute; - align-items: center; - justify-content: center; - height: 20px; +.formation-position-clock>.clock-hand { + align-items: center; + display: flex; + height: 20px; + justify-content: center; + position: absolute; + transform: translate(-50%, -50%); width: 20px; } @@ -362,9 +379,9 @@ --width: 40px; --height: calc(var(--width) / 2); --border-radius: calc(var(--height) / 2); + cursor: pointer; display: inline-block; - cursor: pointer; } .toggle-input { @@ -372,23 +389,22 @@ } .toggle-fill { - position: relative; - width: var(--width); - height: var(--height); border-radius: var(--border-radius); + height: var(--height); + position: relative; transition: background-color 0.2s; + width: var(--width); } .toggle-fill::after { + background-color: #ffffff; + border-radius: var(--border-radius); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); content: ""; + height: calc(var(--height) - 4px); + left: 2; position: absolute; top: 2; - left: 2; - height: calc(var(--height) - 4px); - width: calc(var(--height) - 4px); - background-color: #ffffff; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); - border-radius: var(--border-radius); transition: transform 0.2s; -} - + width: calc(var(--height) - 4px); +} \ No newline at end of file diff --git a/client/public/stylesheets/other/popup.css b/client/public/stylesheets/other/popup.css index fb514460..521e4cde 100644 --- a/client/public/stylesheets/other/popup.css +++ b/client/public/stylesheets/other/popup.css @@ -1,13 +1,3 @@ -#info-popup { - position: absolute; - width: fit-content; - height: fit-content; - top: 100px; - left: 50%; - translate: -50% 0%; - z-index: 9999; -} - .ol-popup > div { padding-left: 15px; padding-right: 15px; diff --git a/client/public/stylesheets/panels/connectionstatus.css b/client/public/stylesheets/panels/connectionstatus.css index 362657cf..2b7dcec4 100644 --- a/client/public/stylesheets/panels/connectionstatus.css +++ b/client/public/stylesheets/panels/connectionstatus.css @@ -1,12 +1,3 @@ -#connection-status-panel { - bottom: 20px; - font-size:12px; - position: absolute; - right: 10px; - width: 160px; - z-index: 1000; -} - #connection-status-panel dt::before { content: "No connection"; } @@ -15,15 +6,14 @@ border-radius: 50%; background: red; content: " "; - height:12px; - width:12px; + height: 12px; + width: 12px; } - #connection-status-panel[data-is-connected] dt::before { content: "Connected"; } #connection-status-panel[data-is-connected] dd::after { - background:var( --accent-green ); + background: var(--accent-green); } \ No newline at end of file diff --git a/client/public/stylesheets/panels/mouseinfo.css b/client/public/stylesheets/panels/mouseinfo.css index fe8030a3..cfbb70c6 100644 --- a/client/public/stylesheets/panels/mouseinfo.css +++ b/client/public/stylesheets/panels/mouseinfo.css @@ -1,91 +1,75 @@ -#mouse-info-panel { - bottom: 60px; - display:flex; - flex-direction: column; - height: fit-content; - position: absolute; - right: 10px; - row-gap: 10px; - width: 160px; - z-index: 1000; +#mouse-info-panel>* { + background-color: var(--background-grey); + border-radius: var(--border-radius-sm); + padding: 6px; } - -#mouse-info-panel > * { - background-color: var( --background-grey ); - border-radius: var( --border-radius-sm ); - padding:6px; -} - - #mouse-info-panel dl { - margin-bottom:4px; + margin-bottom: 4px; row-gap: 8px; } #mouse-info-panel dt { - height:20px; - width:30%; + height: 20px; + width: 30%; } #mouse-info-panel dt::after { align-items: center; background-color: white; - border-radius: var( --border-radius-sm ); - color: var( --background-steel ); - display:flex; - font-size:15.6px; + border-radius: var(--border-radius-sm); + color: var(--background-steel); + display: flex; + font-size: 15.6px; font-weight: bolder; - height:16px; + height: 16px; justify-content: center; line-height: 16px; - padding:4px; + padding: 4px; text-transform: uppercase; - width:16px; + width: 16px; } #mouse-info-panel dt#ref-unit-position::after { - background-image: url( "/images/icons/ruler.svg" ); + background-image: url("/resources/theme/images/icons/ruler.svg"); background-position: 50% 50%; background-repeat: no-repeat; - background-size:16px 16px; + background-size: 16px 16px; content: ""; } #mouse-info-panel dt#ref-measure-position::after { - background-image: url( "/images/pin.png" ); + background-image: url("/resources/theme/images/pin.png"); background-position: 50% 50%; background-repeat: no-repeat; - background-size:16px 16px; + background-size: 16px 16px; content: ""; } - #mouse-info-panel dt[data-label]::after { - content: attr( data-label ); + content: attr(data-label); } #mouse-info-panel dt[data-coalition="blue"]::after { - background-color: var( --primary-blue ); + background-color: var(--primary-blue); } #mouse-info-panel dt[data-coalition="red"]::after { - background-color: var( --primary-red ); + background-color: var(--primary-red); } - #mouse-info-panel dt[data-tooltip]:hover::before { - background-color: var( --background-grey ); + background-color: var(--background-grey); border-radius: 5px; - content: attr( data-tooltip ); - display:flex; + content: attr(data-tooltip); + display: flex; flex-wrap: nowrap; padding: 5px; position: absolute; - translate: calc( -100% - 15px ) 0; + translate: calc(-100% - 15px) 0; white-space: nowrap; } #mouse-info-panel dd { - width:70%; + width: 70%; } \ No newline at end of file diff --git a/client/public/stylesheets/panels/unitcontrol.css b/client/public/stylesheets/panels/unitcontrol.css index cb314504..521ec15f 100644 --- a/client/public/stylesheets/panels/unitcontrol.css +++ b/client/public/stylesheets/panels/unitcontrol.css @@ -2,16 +2,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: block !important; } - -#unit-control-panel { - height: fit-content; - left: 10px; - position: absolute; - top: 80px; - width: 240px; - z-index: 1000; -} - #unit-control-panel h3 { margin-bottom: 8px; } @@ -33,31 +23,31 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: flex; font-size: 11px; height: 32px; + margin-right: 5px; padding: 8px 0; position: relative; width: calc(100% - 5px); - margin-right: 5px; } #unit-control-panel #selected-units-container button::before { - background-color: var( --primary-neutral ); + background-color: var(--primary-neutral); border-radius: 999px; content: attr(data-short-label); + font-size: 10px; margin: 2px 4px; + max-width: 30px; + min-width: 20px; + overflow: hidden; padding: 4px 6px; + text-overflow: ellipsis; white-space: nowrap; width: fit-content; - min-width: 20px; - max-width: 30px; - text-overflow: ellipsis; - overflow: hidden; - font-size: 10px; } #unit-control-panel #selected-units-container button:hover::before { + background-color: black; max-width: 100%; text-overflow: unset; - background-color: black; } #unit-control-panel #selected-units-container button[data-coalition="blue"]::before { @@ -120,13 +110,12 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: none; } - #advanced-settings-dialog>.ol-dialog-content { - margin-top: 10px; - margin-bottom: 10px; display: flex; flex-direction: column; flex-wrap: nowrap; + margin-bottom: 10px; + margin-top: 10px; row-gap: 10px; } @@ -146,8 +135,8 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } #advanced-settings-dialog hr { - margin-top: 15px; margin-bottom: 10px; + margin-top: 15px; } #general-settings-grid { diff --git a/client/public/stylesheets/panels/unitinfo.css b/client/public/stylesheets/panels/unitinfo.css index 1fa1e3ee..8e20e07d 100644 --- a/client/public/stylesheets/panels/unitinfo.css +++ b/client/public/stylesheets/panels/unitinfo.css @@ -1,119 +1,106 @@ - -#unit-info-panel { - bottom: 20px; - font-size:12px; - position: absolute; - left: 10px; - width: fit-content; - z-index: 1000; -} - - #unit-info-panel #unit-name { - padding:0px 0; - margin-bottom:4px; + padding: 0px 0; + margin-bottom: 4px; } #unit-info-panel #current-task { - border-radius: var( --border-radius-lg ); - margin-top:8px; - padding:6px 16px; + border-radius: var(--border-radius-lg); + margin-top: 8px; + padding: 6px 16px; } #unit-info-panel #current-task::after { - content: attr( data-current-task ); - display:block; + content: attr(data-current-task); + display: block; } - #loadout { - display:flex; + display: flex; overflow: visible; } #loadout-silhouette { align-items: center; - display:flex; + display: flex; justify-content: center; - width:100px; + width: 100px; } #loadout-silhouette::before { - background-image: var( --loadout-background-image ); + background-image: var(--loadout-background-image); background-repeat: no-repeat; - background-size:75px 75px; - content:""; - display:block; - filter: invert( 100% ); - height:75px; - translate:-10px 0; - width:75px; + background-size: 75px 75px; + content: ""; + display: block; + filter: invert(100%); + height: 75px; + translate: -10px 0; + width: 75px; } #loadout-items { align-self: center; - display:flex; + display: flex; flex-flow: column nowrap; row-gap: 8px; } -#loadout-items > * { +#loadout-items>* { align-items: center; column-gap: 8px; - display:flex; + display: flex; justify-content: flex-end; white-space: nowrap; } -#loadout-items > *::before { +#loadout-items>*::before { align-items: center; - background-color: var( --secondary-light-grey ); - border-radius: var( --border-radius-sm ); - content: attr( data-qty ); - display:flex; - font-weight: var( --font-weight-bolder ); - padding:1px 4px; + background-color: var(--secondary-light-grey); + border-radius: var(--border-radius-sm); + content: attr(data-qty); + display: flex; + font-weight: var(--font-weight-bolder); + padding: 1px 4px; } -#loadout-items > *::after { - content: attr( data-item ); +#loadout-items>*::after { + content: attr(data-item); overflow: hidden; - position:relative; + position: relative; text-overflow: ellipsis; - width:80px; + width: 80px; } #fuel-percentage { align-items: center; - display:flex; + display: flex; } #fuel-percentage::before { - content: url( /images/icons/fuel.svg ); - display:inline-block; - filter:invert(100%); - height:16px; - margin-right:6px; - width:16px; + content: url("/resources/theme/images/icons/fuel.svg"); + display: inline-block; + filter: invert(100%); + height: 16px; + margin-right: 6px; + width: 16px; } #fuel-percentage::after { - content: attr( data-percentage ) "%"; + content: attr(data-percentage) "%"; } #fuel-display { - background-color: var( --background-grey ); - border-radius: var( --border-radius-md ); - height:6px; - margin-top:4px; + background-color: var(--background-grey); + border-radius: var(--border-radius-md); + height: 6px; + margin-top: 4px; overflow: hidden; } #fuel-display #fuel-bar { - border-radius: var( --border-radius-md ); - height:100%; -} - + border-radius: var(--border-radius-md); + height: 100%; +} \ No newline at end of file diff --git a/client/public/themes/olympus/images/actions/180.svg b/client/public/themes/olympus/images/actions/180.svg deleted file mode 100644 index 2db2cb63..00000000 --- a/client/public/themes/olympus/images/actions/180.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/actions/cancel.svg b/client/public/themes/olympus/images/actions/cancel.svg deleted file mode 100644 index dc3335a8..00000000 --- a/client/public/themes/olympus/images/actions/cancel.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/gas.svg b/client/public/themes/olympus/images/actions/gas.svg deleted file mode 100644 index 14bbef19..00000000 --- a/client/public/themes/olympus/images/actions/gas.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/nothing.svg b/client/public/themes/olympus/images/actions/nothing.svg deleted file mode 100644 index 1ea95812..00000000 --- a/client/public/themes/olympus/images/actions/nothing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/rtb.svg b/client/public/themes/olympus/images/actions/rtb.svg deleted file mode 100644 index 9147bcc5..00000000 --- a/client/public/themes/olympus/images/actions/rtb.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/search.svg b/client/public/themes/olympus/images/actions/search.svg deleted file mode 100644 index 2ca59cbb..00000000 --- a/client/public/themes/olympus/images/actions/search.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/buttons/emissions/attack.svg b/client/public/themes/olympus/images/buttons/emissions/attack.svg new file mode 100644 index 00000000..0846e4e6 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/attack.svg @@ -0,0 +1,44 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/defend.svg b/client/public/themes/olympus/images/buttons/emissions/defend.svg new file mode 100644 index 00000000..5c884188 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/defend.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/free.svg b/client/public/themes/olympus/images/buttons/emissions/free.svg new file mode 100644 index 00000000..f07b2db6 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/free.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/silent.svg b/client/public/themes/olympus/images/buttons/emissions/silent.svg new file mode 100644 index 00000000..f735bc21 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/silent.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg b/client/public/themes/olympus/images/buttons/spawn/aircraft.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg rename to client/public/themes/olympus/images/buttons/spawn/aircraft.svg diff --git a/client/public/themes/olympus/images/buttons/other/spawn_ground.svg b/client/public/themes/olympus/images/buttons/spawn/ground.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_ground.svg rename to client/public/themes/olympus/images/buttons/spawn/ground.svg diff --git a/client/public/themes/olympus/images/buttons/other/spawn_smoke.svg b/client/public/themes/olympus/images/buttons/spawn/smoke.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_smoke.svg rename to client/public/themes/olympus/images/buttons/spawn/smoke.svg diff --git a/client/public/themes/olympus/images/buttons/threat/evade.svg b/client/public/themes/olympus/images/buttons/threat/evade.svg index 687f852e..54d7b49f 100644 --- a/client/public/themes/olympus/images/buttons/threat/evade.svg +++ b/client/public/themes/olympus/images/buttons/threat/evade.svg @@ -45,7 +45,8 @@ stroke="#5ca7ff" stroke-linecap="round" stroke-dasharray="2, 2" - id="path4" /> + id="path4" + style="fill:none" /> + + + + + + + diff --git a/client/public/images/marker-icon.png b/client/public/themes/olympus/images/markers/marker-icon.png similarity index 100% rename from client/public/images/marker-icon.png rename to client/public/themes/olympus/images/markers/marker-icon.png diff --git a/client/public/images/marker-shadow.png b/client/public/themes/olympus/images/markers/marker-shadow.png similarity index 100% rename from client/public/images/marker-shadow.png rename to client/public/themes/olympus/images/markers/marker-shadow.png diff --git a/client/public/images/icon-temporary.png b/client/public/themes/olympus/images/markers/temporary-icon.png similarity index 100% rename from client/public/images/icon-temporary.png rename to client/public/themes/olympus/images/markers/temporary-icon.png diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg b/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg deleted file mode 100644 index 7ecf9816..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg b/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg deleted file mode 100644 index ed925a5a..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_light.svg b/client/public/themes/olympus/images/other/icons_misc_brush_light.svg deleted file mode 100644 index 74d2a691..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg b/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg deleted file mode 100644 index 13afd5e5..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg b/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg deleted file mode 100644 index 53bb4a3f..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_light.svg b/client/public/themes/olympus/images/other/icons_misc_gas_light.svg deleted file mode 100644 index 8530905f..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_blue.svg b/client/public/themes/olympus/images/other/icons_misc_map_blue.svg deleted file mode 100644 index 3e233196..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_dark.svg b/client/public/themes/olympus/images/other/icons_misc_map_dark.svg deleted file mode 100644 index 6b9a5219..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_light.svg b/client/public/themes/olympus/images/other/icons_misc_map_light.svg deleted file mode 100644 index 8bec4777..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg b/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg deleted file mode 100644 index 7bf3bcd8..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg b/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg deleted file mode 100644 index cec512c5..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_light.svg b/client/public/themes/olympus/images/other/icons_misc_plane_light.svg deleted file mode 100644 index cf34b9c8..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg b/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg deleted file mode 100644 index 2d7004c6..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg b/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg deleted file mode 100644 index 04b375e4..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_light.svg b/client/public/themes/olympus/images/other/icons_misc_settings_light.svg deleted file mode 100644 index 19270b5c..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg b/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg deleted file mode 100644 index 66f1ae9a..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg b/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg deleted file mode 100644 index 0023c2c1..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_light.svg b/client/public/themes/olympus/images/other/icons_misc_visible_light.svg deleted file mode 100644 index 46f1b080..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/images/splash/splash_pic_ship.png b/client/public/themes/olympus/images/splash/1.png similarity index 100% rename from client/public/images/splash/splash_pic_ship.png rename to client/public/themes/olympus/images/splash/1.png diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts index e46d5f8a..e1a9c3e9 100644 --- a/client/src/@types/server.d.ts +++ b/client/src/@types/server.d.ts @@ -8,7 +8,7 @@ interface AirbasesData { } interface BullseyesData { - bullseyes: {[key: string]: any}, + bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}}, } interface LogData { diff --git a/client/src/controls/airbasecontextmenu.ts b/client/src/controls/airbasecontextmenu.ts index d94f17db..bb5e7f29 100644 --- a/client/src/controls/airbasecontextmenu.ts +++ b/client/src/controls/airbasecontextmenu.ts @@ -5,8 +5,7 @@ import { ContextMenu } from "./contextmenu"; export class AirbaseContextMenu extends ContextMenu { #airbase: Airbase | null = null; - constructor(id: string) - { + constructor(id: string) { super(id); document.addEventListener("contextMenuSpawnAirbase", (e: any) => { this.showSpawnMenu(); @@ -19,8 +18,7 @@ export class AirbaseContextMenu extends ContextMenu { }) } - setAirbase(airbase: Airbase) - { + setAirbase(airbase: Airbase) { this.#airbase = airbase; this.setName(airbase.getName()); this.setProperties(airbase.getProperties()); @@ -29,24 +27,21 @@ export class AirbaseContextMenu extends ContextMenu { this.enableLandButton(getUnitsManager().getSelectedUnitsType() === "Aircraft" && (getUnitsManager().getSelectedUnitsCoalition() === airbase.getCoalition() || airbase.getCoalition() === "neutral")) } - setName(airbaseName: string) - { + setName(airbaseName: string) { var nameDiv = this.getContainer()?.querySelector("#airbase-name"); if (nameDiv != null) - nameDiv.innerText = airbaseName; + nameDiv.innerText = airbaseName; } - setProperties(airbaseProperties: string[]) - { + setProperties(airbaseProperties: string[]) { this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => { var div = document.createElement("div"); div.innerText = property; return div; - }), ); + }),); } - setParkings(airbaseParkings: string[]) - { + setParkings(airbaseParkings: string[]) { this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => { var div = document.createElement("div"); div.innerText = parking; @@ -54,22 +49,18 @@ export class AirbaseContextMenu extends ContextMenu { })); } - setCoalition(coalition: string) - { + setCoalition(coalition: string) { (this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.activeCoalition = coalition; } - enableLandButton(enableLandButton: boolean) - { + enableLandButton(enableLandButton: boolean) { this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton); } - showSpawnMenu() - { - if (this.#airbase != null) - { + showSpawnMenu() { + if (this.#airbase != null) { setActiveCoalition(this.#airbase.getCoalition()); - getMap().showMapContextMenu({originalEvent: {x: this.getX(), y: this.getY(), latlng: this.getLatLng()}}); + getMap().showMapContextMenu({ originalEvent: { x: this.getX(), y: this.getY(), latlng: this.getLatLng() } }); getMap().getMapContextMenu().hideUpperBar(); getMap().getMapContextMenu().showSubMenu("aircraft"); getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName()); diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index 9a19562f..58cf371f 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -23,28 +23,23 @@ export class ContextMenu { this.#container?.classList.toggle("hide", true); } - getContainer() - { + getContainer() { return this.#container; } - getLatLng() - { + getLatLng() { return this.#latlng; } - getX() - { + getX() { return this.#x; } - getY() - { + getY() { return this.#y; } - clip() - { + clip() { if (this.#container != null) { if (this.#x + this.#container.offsetWidth < window.innerWidth) this.#container.style.left = this.#x + "px"; diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index cb96e4c0..641c405f 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -7,17 +7,16 @@ export class Dropdown { #optionsList: string[] = []; #index: number = 0; - constructor(ID: string, callback: CallableFunction, options: string[] | null = null) - { - this.#element = document.getElementById(ID); - this.#options = this.#element.querySelector(".ol-select-options"); - this.#value = this.#element.querySelector(".ol-select-value"); + constructor(ID: string, callback: CallableFunction, options: string[] | null = null) { + this.#element = document.getElementById(ID); + this.#options = this.#element.querySelector(".ol-select-options"); + this.#value = this.#element.querySelector(".ol-select-value"); this.#defaultValue = this.#value.innerText; - this.#callback = callback; + this.#callback = callback; if (options != null) { this.setOptions(options); - } + } this.#value.addEventListener("click", (ev) => { this.#element.classList.toggle("is-open"); @@ -31,11 +30,10 @@ export class Dropdown { } }); - this.#options.classList.add( "ol-scrollable" ); + this.#options.classList.add("ol-scrollable"); } - setOptions(optionsList: string[]) - { + setOptions(optionsList: string[]) { this.#optionsList = optionsList; this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => { var div = document.createElement("div"); @@ -48,7 +46,9 @@ export class Dropdown { button.addEventListener("click", (e: MouseEvent) => { e.stopPropagation(); - this.#value.innerHTML = `

${option}
`; + this.#value = document.createElement("div"); + this.#value.classList.add("ol-ellipsed"); + this.#value.innerText = option; this.#close(); this.#callback(option, e); this.#index = idx; @@ -57,19 +57,15 @@ export class Dropdown { })); } - selectText( text:string ) { - - const index = [].slice.call( this.#options.children ).findIndex( ( opt:Element ) => opt.querySelector( "button" )?.innerText === text ); - if ( index > -1 ) { - this.selectValue( index ); + selectText(text: string) { + const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text); + if (index > -1) { + this.selectValue(index); } - } - selectValue(idx: number) - { - if (idx < this.#optionsList.length) - { + selectValue(idx: number) { + if (idx < this.#optionsList.length) { var option = this.#optionsList[idx]; this.#value.innerHTML = `
${option}
`; this.#index = idx; @@ -91,8 +87,8 @@ export class Dropdown { } setValue(value: string) { - var index = this.#optionsList.findIndex((option) => {return option === value}); - if (index > -1) + var index = this.#optionsList.findIndex((option) => { return option === value }); + if (index > -1) this.selectValue(index); } @@ -102,21 +98,21 @@ export class Dropdown { #clip() { const options = this.#options; - const bounds = options.getBoundingClientRect(); - this.#element.dataset.position = ( bounds.bottom > window.innerHeight ) ? "top" : ""; + const bounds = options.getBoundingClientRect(); + this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : ""; } #close() { - this.#element.classList.remove( "is-open" ); + this.#element.classList.remove("is-open"); this.#element.dataset.position = ""; } #open() { - this.#element.classList.add( "is-open" ); + this.#element.classList.add("is-open"); } #toggle() { - if ( this.#element.classList.contains( "is-open" ) ) { + if (this.#element.classList.contains("is-open")) { this.#close(); } else { this.#open(); diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 929dad05..c8c98b40 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -41,8 +41,7 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("contextMenuDeployAircraft", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - { + if (this.#spawnOptions) { getMap().addTemporaryMarker(this.#spawnOptions.latlng); spawnAircraft(this.#spawnOptions); } @@ -51,8 +50,7 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("contextMenuDeployGroundUnit", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - { + if (this.#spawnOptions) { getMap().addTemporaryMarker(this.#spawnOptions.latlng); spawnGroundUnit(this.#spawnOptions); } @@ -189,10 +187,10 @@ export class MapContextMenu extends ContextMenu { this.#spawnOptions.role = role; this.#resetGroundUnitType(); - const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label } ); + const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label }); types.sort(); - this.#groundUnitTypeDropdown.setOptions( types ); + this.#groundUnitTypeDropdown.setOptions(types); this.#groundUnitTypeDropdown.selectValue(0); this.clip(); } @@ -205,8 +203,8 @@ export class MapContextMenu extends ContextMenu { const roles = groundUnitsDatabase.getRoles(); roles.sort(); - - this.#groundUnitRoleDropdown.setOptions( roles ); + + this.#groundUnitRoleDropdown.setOptions(roles); this.clip(); } diff --git a/client/src/controls/slider.ts b/client/src/controls/slider.ts index 7cf185cc..7fab7782 100644 --- a/client/src/controls/slider.ts +++ b/client/src/controls/slider.ts @@ -23,8 +23,7 @@ export class Slider { if (this.#container != null) { this.#display = this.#container.style.display; this.#slider = this.#container.querySelector("input"); - if (this.#slider != null) - { + if (this.#slider != null) { this.#slider.addEventListener("input", (e: any) => this.#onInput()); this.#slider.addEventListener("mousedown", (e: any) => this.#onStart()); this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize()); @@ -33,93 +32,77 @@ export class Slider { } } - show() - { + show() { if (this.#container != null) this.#container.style.display = this.#display; } - hide() - { + hide() { if (this.#container != null) this.#container.style.display = 'none'; } - setActive(newActive: boolean) - { - if (this.#container && !this.#dragged) - { + setActive(newActive: boolean) { + if (this.#container && !this.#dragged) { this.#container.classList.toggle("active", newActive); if (!newActive && this.#valueText != null) this.#valueText.innerText = "Mixed values"; } } - setMinMax(newMinValue: number, newMaxValue: number) - { + setMinMax(newMinValue: number, newMaxValue: number) { this.#minValue = newMinValue; this.#maxValue = newMaxValue; this.#updateMax(); } - setIncrement(newIncrement: number) - { + setIncrement(newIncrement: number) { this.#increment = newIncrement; this.#updateMax(); } - setValue(newValue: number) - { + setValue(newValue: number) { // Disable value setting if the user is dragging the element - if (!this.#dragged) - { + if (!this.#dragged) { this.#value = newValue; if (this.#slider != null) - this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max)); + this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max)); this.#onValue() } } - getValue() - { + getValue() { return this.#value; } - getDragged() - { + getDragged() { return this.#dragged; } - #updateMax() - { + #updateMax() { var oldValue = this.getValue(); if (this.#slider != null) this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment); this.setValue(oldValue); } - #onValue() - { + #onValue() { if (this.#valueText != null && this.#slider != null) - this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit + this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit this.setActive(true); } - #onInput() - { + #onInput() { this.#onValue(); } - #onStart() - { + #onStart() { this.#dragged = true; } - #onFinalize() - { + #onFinalize() { this.#dragged = false; - if (this.#slider != null) - { + if (this.#slider != null) { this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue); this.#callback(this.getValue()); } diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index aaa1b205..86250e6d 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -1,4 +1,3 @@ -import { getUnitsManager } from ".."; import { deg2rad } from "../other/utils"; import { ContextMenu } from "./contextmenu"; @@ -10,21 +9,19 @@ export class UnitContextMenu extends ContextMenu { document.addEventListener("applyCustomFormation", () => { var dialog = document.getElementById("custom-formation-dialog"); - if (dialog) - { + if (dialog) { dialog.classList.add("hide"); var clock = 1; - while (clock < 8) - { - if (( dialog.querySelector(`#formation-${clock}`)).checked) + while (clock < 8) { + if ((dialog.querySelector(`#formation-${clock}`)).checked) break clock++; } var angleDeg = 360 - (clock - 1) * 45; var angleRad = deg2rad(angleDeg); - var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; - var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; - + var distance = parseInt((dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; + var upDown = parseInt((dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; + // X: front-rear, positive front // Y: top-bottom, positive top // Z: left-right, positive right @@ -34,7 +31,7 @@ export class UnitContextMenu extends ContextMenu { var z = distance * Math.sin(angleRad); if (this.#customFormationCallback) - this.#customFormationCallback({"x": x, "y": y, "z": z}) + this.#customFormationCallback({ "x": x, "y": y, "z": z }) } }) } @@ -43,10 +40,8 @@ export class UnitContextMenu extends ContextMenu { this.#customFormationCallback = callback; } - setOptions(options: {[key: string]: string}, callback: CallableFunction) - { - this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => - { + setOptions(options: { [key: string]: string }, callback: CallableFunction) { + this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => { var button = document.createElement("button"); button.innerHTML = options[option]; button.addEventListener("click", () => callback(option)); diff --git a/client/src/features/featureswitches.ts b/client/src/features/featureswitches.ts index c5776683..ee237b01 100644 --- a/client/src/features/featureswitches.ts +++ b/client/src/features/featureswitches.ts @@ -23,13 +23,13 @@ class FeatureSwitch { userPreference; - constructor( config:FeatureSwitchInterface ) { + constructor(config: FeatureSwitchInterface) { this.defaultEnabled = config.defaultEnabled; - this.label = config.label; - this.masterSwitch = config.masterSwitch; - this.name = config.name; - this.onEnabled = config.onEnabled; + this.label = config.label; + this.masterSwitch = config.masterSwitch; + this.name = config.name; + this.onEnabled = config.onEnabled; this.userPreference = this.getUserPreference(); @@ -38,16 +38,16 @@ class FeatureSwitch { getUserPreference() { - let preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" ); + let preferences = JSON.parse(localStorage.getItem("featureSwitches") || "{}"); - return ( preferences.hasOwnProperty( this.name ) ) ? preferences[ this.name ] : this.defaultEnabled; + return (preferences.hasOwnProperty(this.name)) ? preferences[this.name] : this.defaultEnabled; } isEnabled() { - if ( !this.masterSwitch ) { + if (!this.masterSwitch) { return false; } @@ -58,7 +58,7 @@ class FeatureSwitch { export class FeatureSwitches { - #featureSwitches:FeatureSwitch[] = [ + #featureSwitches: FeatureSwitch[] = [ new FeatureSwitch({ "defaultEnabled": false, @@ -66,7 +66,7 @@ export class FeatureSwitches { "masterSwitch": true, "name": "aic" }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "AI Formations", @@ -74,21 +74,21 @@ export class FeatureSwitches { "name": "ai-formations", "removeArtifactsIfDisabled": false }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "ATC", "masterSwitch": true, "name": "atc" }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "Force show unit control panel", "masterSwitch": true, "name": "forceShowUnitControlPanel" }), - + new FeatureSwitch({ "defaultEnabled": true, "label": "Show splash screen", @@ -108,41 +108,41 @@ export class FeatureSwitches { } - getSwitch( switchName:string ) { + getSwitch(switchName: string) { - return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName ); + return this.#featureSwitches.find(featureSwitch => featureSwitch.name === switchName); } #testSwitches() { - for ( const featureSwitch of this.#featureSwitches ) { - if ( featureSwitch.isEnabled() ) { - if ( typeof featureSwitch.onEnabled === "function" ) { + for (const featureSwitch of this.#featureSwitches) { + if (featureSwitch.isEnabled()) { + if (typeof featureSwitch.onEnabled === "function") { featureSwitch.onEnabled(); } } else { - document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => { - if ( featureSwitch.removeArtifactsIfDisabled === false ) { + document.querySelectorAll("[data-feature-switch='" + featureSwitch.name + "']").forEach(el => { + if (featureSwitch.removeArtifactsIfDisabled === false) { el.remove(); } else { - el.classList.add( "hide" ); + el.classList.add("hide"); } }); } - document.body.classList.toggle( "feature-" + featureSwitch.name, featureSwitch.isEnabled() ); + document.body.classList.toggle("feature-" + featureSwitch.name, featureSwitch.isEnabled()); } } savePreferences() { - let preferences:any = {}; + let preferences: any = {}; - for ( const featureSwitch of this.#featureSwitches ) { - preferences[ featureSwitch.name ] = featureSwitch.isEnabled(); + for (const featureSwitch of this.#featureSwitches) { + preferences[featureSwitch.name] = featureSwitch.isEnabled(); } - localStorage.setItem( "featureSwitches", JSON.stringify( preferences ) ); + localStorage.setItem("featureSwitches", JSON.stringify(preferences)); } diff --git a/client/src/features/toggleablefeature.ts b/client/src/features/toggleablefeature.ts index 5873fb6e..09f1723e 100644 --- a/client/src/features/toggleablefeature.ts +++ b/client/src/features/toggleablefeature.ts @@ -1,9 +1,9 @@ export abstract class ToggleableFeature { - #status:boolean = false; + #status: boolean = false; - constructor( defaultStatus:boolean ) { + constructor(defaultStatus: boolean) { this.#status = defaultStatus; @@ -12,17 +12,17 @@ export abstract class ToggleableFeature { } - getStatus() : boolean { + getStatus(): boolean { return this.#status; } - protected onStatusUpdate() {} + protected onStatusUpdate() { } - toggleStatus( force?:boolean ) : void { + toggleStatus(force?: boolean): void { - if ( force ) { + if (force) { this.#status = force; } else { this.#status = !this.#status; diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts index b285c10a..2ea92fd8 100644 --- a/client/src/map/boxselect.ts +++ b/client/src/map/boxselect.ts @@ -1,10 +1,10 @@ import { Map } from 'leaflet'; -import { Handler} from 'leaflet'; +import { Handler } from 'leaflet'; import { Util } from 'leaflet'; import { DomUtil } from 'leaflet'; import { DomEvent } from 'leaflet'; import { LatLngBounds } from 'leaflet'; -import { Bounds } from 'leaflet'; +import { Bounds } from 'leaflet'; export var BoxSelect = Handler.extend({ initialize: function (map: Map) { @@ -82,12 +82,12 @@ export var BoxSelect = Handler.extend({ this._point = this._map.mouseEventToContainerPoint(e); var bounds = new Bounds(this._point, this._startPoint), - size = bounds.getSize(); + size = bounds.getSize(); if (bounds.min != undefined) DomUtil.setPosition(this._box, bounds.min); - this._box.style.width = size.x + 'px'; + this._box.style.width = size.x + 'px'; this._box.style.height = size.y + 'px'; }, @@ -113,7 +113,7 @@ export var BoxSelect = Handler.extend({ if ((e.which !== 1) && (e.button !== 0)) { return; } this._finish(); - + if (!this._moved) { return; } // Postpone to next JS tick so internal click event handling // still see it as "moved". @@ -121,8 +121,8 @@ export var BoxSelect = Handler.extend({ var bounds = new LatLngBounds( this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); - - this._map.fire('selectionend', {selectionBounds: bounds}); + + this._map.fire('selectionend', { selectionBounds: bounds }); }, _onKeyDown: function (e: any) { diff --git a/client/src/map/clickableminimap.ts b/client/src/map/clickableminimap.ts new file mode 100644 index 00000000..00104f7e --- /dev/null +++ b/client/src/map/clickableminimap.ts @@ -0,0 +1,12 @@ +import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; + +export class ClickableMiniMap extends MiniMap { + constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { + super(layer, options); + } + + getMap() { + //@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me + return this._miniMap; + } +} \ No newline at end of file diff --git a/client/src/map/custommarker.ts b/client/src/map/custommarker.ts new file mode 100644 index 00000000..41d79c14 --- /dev/null +++ b/client/src/map/custommarker.ts @@ -0,0 +1,24 @@ +import { Map, Marker } from "leaflet"; +import { MarkerOptions } from "leaflet"; +import { LatLngExpression } from "leaflet"; + +export class CustomMarker extends Marker { + constructor(latlng: LatLngExpression, options?: MarkerOptions) { + super(latlng, options); + } + + onAdd(map: Map): this { + super.onAdd(map); + this.createIcon(); + return this; + } + + onRemove(map: Map): this { + super.onRemove(map); + return this; + } + + createIcon() { + /* Overloaded by child classes */ + } +} \ No newline at end of file diff --git a/client/src/map/destinationpreviewmarker.ts b/client/src/map/destinationpreviewmarker.ts new file mode 100644 index 00000000..1146992d --- /dev/null +++ b/client/src/map/destinationpreviewmarker.ts @@ -0,0 +1,15 @@ +import { DivIcon } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class DestinationPreviewMarker extends CustomMarker { + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [52, 52], + iconAnchor: [26, 26], + className: "leaflet-destination-preview" + })); + var el = document.createElement("div"); + el.classList.add("ol-destination-preview-icon"); + this.getElement()?.appendChild(el); + } +} diff --git a/client/src/map/map.ts b/client/src/map/map.ts index f98a58c9..a36ae834 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -1,6 +1,4 @@ import * as L from "leaflet" -import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; - import { getUnitsManager } from ".."; import { BoxSelect } from "./boxselect"; import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu"; @@ -10,40 +8,20 @@ import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../missionhandler/airbase"; import { Unit } from "../units/unit"; import { bearing } from "../other/utils"; - -// TODO a bit of a hack, this module is provided as pure javascript only -require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js") - -export const IDLE = "IDLE"; -export const MOVE_UNIT = "MOVE_UNIT"; +import { DestinationPreviewMarker } from "./destinationpreviewmarker"; +import { TemporaryUnitMarker } from "./temporaryunitmarker"; +import { ClickableMiniMap } from "./clickableminimap"; +import { SVGInjector } from '@tanem/svg-injector' L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); -var temporaryIcon = new L.Icon({ - iconUrl: 'images/icon-temporary.png', - iconSize: [52, 52], - iconAnchor: [26, 26] -}); +// TODO would be nice to convert to ts +require("../../public/javascripts/leaflet.nauticscale.js") -var destinationPreviewIcon = new L.DivIcon({ - html: `
`, - iconSize: [52, 52], - iconAnchor: [26, 26], - className: "ol-destination-preview" -}) - -const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; - -export class ClickableMiniMap extends MiniMap { - constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { - super(layer, options); - } - - getMap() { - //@ts-ignore needed to access not exported member. A bit of a hack, required to access click events - return this._miniMap; - } -} +/* Map constants */ +export const IDLE = "IDLE"; +export const MOVE_UNIT = "MOVE_UNIT"; +export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export class Map extends L.Map { #state: string; @@ -107,19 +85,21 @@ export class Map extends L.Map { this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e)); - this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); - this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); - + this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); + this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); + /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - ev.detail._element.classList.toggle("off"); - document.body.toggleAttribute("data-hide-" + ev.detail.coalition); + const el = ev.detail._element; + el?.classList.toggle("off"); + getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off")); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { - ev.detail._element.classList.toggle("off"); - document.body.toggleAttribute("data-hide-" + ev.detail.category); + const el = ev.detail._element; + el?.classList.toggle("off"); + getUnitsManager().setHiddenType(ev.detail.type, !el?.classList.contains("off")); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); @@ -129,17 +109,14 @@ export class Map extends L.Map { }); /* Pan interval */ - this.#panInterval = window.setInterval(() => { - this.panBy(new L.Point( ((this.#panLeft? -1: 0) + (this.#panRight? 1: 0)) * this.#deafultPanDelta, - ((this.#panUp? -1: 0) + (this.#panDown? 1: 0)) * this.#deafultPanDelta)); + this.#panInterval = window.setInterval(() => { + this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta, + ((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta)); }, 20); /* Option buttons */ this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { - return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", (e: any) => { - getUnitsManager().setHiddenType(option, (e?.currentTarget as HTMLElement)?.classList.contains("off")); - (e?.currentTarget as HTMLElement)?.classList.toggle("off"); - }); + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", "toggleUnitVisibility", `{"type": "${option}"}`); }); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); } @@ -217,10 +194,10 @@ export class Map extends L.Map { }) this.#destinationPreviewMarkers = []; - if (getUnitsManager().getSelectedUnits({excludeHumans: true}).length < 20) { + if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { /* Create the unit destination preview markers */ - this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({excludeHumans: true}).map((unit: Unit) => { - var marker = new L.Marker(this.getMouseCoordinates(), {icon: destinationPreviewIcon, interactive: false}); + this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates()); marker.addTo(this); return marker; }) @@ -352,7 +329,7 @@ export class Map extends L.Map { } handleMapPanning(e: any) { - if (e.type === "keyup"){ + if (e.type === "keyup") { switch (e.code) { case "KeyA": case "ArrowLeft": @@ -372,9 +349,8 @@ export class Map extends L.Map { break; } } - else { - switch (e.code) - { + else { + switch (e.code) { case 'KeyA': case 'ArrowLeft': this.#panLeft = true; @@ -396,7 +372,7 @@ export class Map extends L.Map { } addTemporaryMarker(latlng: L.LatLng) { - var marker = new L.Marker(latlng, {icon: temporaryIcon}); + var marker = new TemporaryUnitMarker(latlng); marker.addTo(this); this.#temporaryMarkers.push(marker); } @@ -413,8 +389,7 @@ export class Map extends L.Map { i = idx; } }); - if (closest) - { + if (closest) { this.removeLayer(closest); delete this.#temporaryMarkers[i]; } @@ -449,7 +424,7 @@ export class Map extends L.Map { if (!e.originalEvent.ctrlKey) { getUnitsManager().selectedUnitsClearDestinations(); } - getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null? this.#destinationRotationCenter: e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation) + getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation) } } @@ -465,16 +440,14 @@ export class Map extends L.Map { #onMouseDown(e: any) { this.hideAllContextMenus(); - if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) - { + if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) { this.#computeDestinationRotation = true; this.#destinationRotationCenter = this.getMouseCoordinates(); } } #onMouseUp(e: any) { - if (this.#state == MOVE_UNIT) - { + if (this.#state == MOVE_UNIT) { this.#computeDestinationRotation = false; this.#destinationRotationCenter = null; this.#destinationGroupRotation = 0; @@ -488,7 +461,7 @@ export class Map extends L.Map { if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); - this.#updateDestinationPreview(e); + this.#updateDestinationPreview(e); } #onZoom(e: any) { @@ -542,18 +515,23 @@ export class Map extends L.Map { } #updateDestinationPreview(e: any) { - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null? this.#destinationRotationCenter: this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { if (idx < this.#destinationPreviewMarkers.length) - this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey? latlng: this.getMouseCoordinates()); - }) + this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); + }) } - #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { + #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { var button = document.createElement("button"); + const img = document.createElement("img"); + img.src = `/resources/theme/images/buttons/${url}`; + img.onload = () => SVGInjector(img); button.title = title; button.value = value; - button.innerHTML = `` - button.addEventListener("click", callback); + button.appendChild(img); + button.setAttribute("data-on-click", callback); + button.setAttribute("data-on-click-params", argument); return button; } } + diff --git a/client/src/map/temporaryunitmarker.ts b/client/src/map/temporaryunitmarker.ts new file mode 100644 index 00000000..904ee83d --- /dev/null +++ b/client/src/map/temporaryunitmarker.ts @@ -0,0 +1,13 @@ +import { Icon } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class TemporaryUnitMarker extends CustomMarker { + createIcon() { + var icon = new Icon({ + iconUrl: '/resources/theme/images/markers/temporary-icon.png', + iconSize: [52, 52], + iconAnchor: [26, 26] + }); + this.setIcon(icon); + } +} \ No newline at end of file diff --git a/client/src/missionhandler/airbase.ts b/client/src/missionhandler/airbase.ts index a3ca42dc..4128001b 100644 --- a/client/src/missionhandler/airbase.ts +++ b/client/src/missionhandler/airbase.ts @@ -1,13 +1,14 @@ -import * as L from 'leaflet' +import { DivIcon } from 'leaflet'; +import { CustomMarker } from '../map/custommarker'; +import { SVGInjector } from '@tanem/svg-injector'; export interface AirbaseOptions { name: string, - position: L.LatLng, - src: string + position: L.LatLng } -export class Airbase extends L.Marker +export class Airbase extends CustomMarker { #name: string = ""; #coalition: string = ""; @@ -19,21 +20,30 @@ export class Airbase extends L.Marker super(options.position, { riseOnHover: true }); this.#name = options.name; - var icon = new L.DivIcon({ - html: `
-
-
`, + } + + createIcon() { + var icon = new DivIcon({ className: 'leaflet-airbase-marker', iconSize: [40, 40], iconAnchor: [20, 20] }); // Set the marker, className must be set to avoid white square this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("airbase-icon"); + el.setAttribute("data-object", "airbase"); + var img = document.createElement("img"); + img.src = "/resources/theme/images/markers/airbase.svg"; + img.onload = () => SVGInjector(img); + el.appendChild(img); + this.getElement()?.appendChild(el); } setCoalition(coalition: string) { this.#coalition = coalition; - ( this.getElement()?.querySelector(".airbase")).dataset.coalition = this.#coalition; + ( this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition; } getCoalition() diff --git a/client/src/missionhandler/bullseye.ts b/client/src/missionhandler/bullseye.ts new file mode 100644 index 00000000..b50b1a7b --- /dev/null +++ b/client/src/missionhandler/bullseye.ts @@ -0,0 +1,36 @@ +import { DivIcon } from "leaflet"; +import { CustomMarker } from "../map/custommarker"; +import { SVGInjector } from "@tanem/svg-injector"; + +export class Bullseye extends CustomMarker { + #coalition: string = ""; + + createIcon() { + var icon = new DivIcon({ + className: 'leaflet-bullseye-marker', + iconSize: [40, 40], + iconAnchor: [20, 20] + }); // Set the marker, className must be set to avoid white square + this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("bullseye-icon"); + el.setAttribute("data-object", "bullseye"); + var img = document.createElement("img"); + img.src = "/resources/theme/images/markers/bullseye.svg"; + img.onload = () => SVGInjector(img); + el.appendChild(img); + this.getElement()?.appendChild(el); + } + + setCoalition(coalition: string) + { + this.#coalition = coalition; + ( this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition; + } + + getCoalition() + { + return this.#coalition; + } +} \ No newline at end of file diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 66233f0a..93b13192 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -1,44 +1,58 @@ -import { Marker, LatLng, Icon } from "leaflet"; +import { LatLng } from "leaflet"; import { getInfoPopup, getMap } from ".."; import { Airbase } from "./airbase"; - -var bullseyeIcons = [ - new Icon({ iconUrl: 'images/bullseye0.png', iconAnchor: [30, 30]}), - new Icon({ iconUrl: 'images/bullseye1.png', iconAnchor: [30, 30]}), - new Icon({ iconUrl: 'images/bullseye2.png', iconAnchor: [30, 30]}) -] +import { Bullseye } from "./bullseye"; export class MissionHandler { - #bullseyes : any; //TODO declare interface - #bullseyeMarkers: any; - #airbases : any; //TODO declare interface - #airbasesMarkers: {[name: string]: Airbase}; + #bullseyes : {[name: string]: Bullseye} = {}; + #airbases : {[name: string]: Airbase} = {}; #theatre : string = ""; constructor() { - this.#bullseyes = undefined; - this.#bullseyeMarkers = [ - new Marker([0, 0], {icon: bullseyeIcons[0]}).addTo(getMap()), - new Marker([0, 0], {icon: bullseyeIcons[1]}).addTo(getMap()), - new Marker([0, 0], {icon: bullseyeIcons[2]}).addTo(getMap()) - ] - this.#airbasesMarkers = {}; + } update(data: BullseyesData | AirbasesData | any) { if ("bullseyes" in data) { - this.#bullseyes = data.bullseyes; - this.#drawBullseyes(); + for (let idx in data.bullseyes) + { + const bullseye = data.bullseyes[idx]; + if (!(idx in this.#bullseyes)) + this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap()); + + if (bullseye.latitude && bullseye.longitude && bullseye.coalition) + { + this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); + this.#bullseyes[idx].setCoalition(bullseye.coalition); + } + } } if ("airbases" in data) { - this.#airbases = data.airbases; - this.#drawAirbases(); + for (let idx in data.airbases) + { + var airbase = data.airbases[idx] + if (this.#airbases[idx] === undefined) + { + this.#airbases[idx] = new Airbase({ + position: new LatLng(airbase.latitude, airbase.longitude), + name: airbase.callsign + }).addTo(getMap()); + this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e)); + } + if (airbase.latitude && airbase.longitude && airbase.coalition) + { + this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); + this.#airbases[idx].setCoalition(airbase.coalition); + } + //this.#airbases[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); + //this.#airbases[idx].setParkings(["2x big", "5x small"]); + } } if ("mission" in data) @@ -58,38 +72,6 @@ export class MissionHandler return this.#bullseyes; } - #drawBullseyes() - { - for (let idx in this.#bullseyes) - { - var bullseye = this.#bullseyes[idx]; - this.#bullseyeMarkers[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); - } - } - - #drawAirbases() - { - for (let idx in this.#airbases) - { - var airbase = this.#airbases[idx] - if (this.#airbasesMarkers[idx] === undefined) - { - this.#airbasesMarkers[idx] = new Airbase({ - position: new LatLng(airbase.latitude, airbase.longitude), - name: airbase.callsign, - src: "images/airbase.png"}).addTo(getMap()); - this.#airbasesMarkers[idx].on('contextmenu', (e) => this.#onAirbaseClick(e)); - } - else - { - this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); - this.#airbasesMarkers[idx].setCoalition(airbase.coalition); - //this.#airbasesMarkers[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); - //this.#airbasesMarkers[idx].setParkings(["2x big", "5x small"]); - } - } - } - #onAirbaseClick(e: any) { getMap().showAirbaseContextMenu(e, e.sourceTarget); diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index b3899d41..ce1ecc1f 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -154,4 +154,10 @@ export function mercatorToLatLng(x: number, y: number) { lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); return { lng: lng, lat: lat }; +} + +export function createDivWithClass(className: string) { + var el = document.createElement("div"); + el.classList.add(className); + return el; } \ No newline at end of file diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index 7cfdd710..bdd38fb3 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -37,8 +37,8 @@ export class MouseInfoPanel extends Panel { if ( el != null ) { - var dist = distance(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng); - var bear = bearing(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng); + var dist = distance(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng); + var bear = bearing(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng); let bng = zeroAppend(Math.floor(bear), 3); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 9f6ef22e..7b1967d4 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -346,7 +346,7 @@ export class UnitControlPanel extends Panel { var button = document.createElement("button"); button.title = title; button.value = value; - button.innerHTML = `` + button.innerHTML = `` button.addEventListener("click", callback); return button; } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index a7a7ec09..3338c095 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -4,14 +4,16 @@ import { rad2deg } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server'; import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; +import { CustomMarker } from '../map/custommarker'; +import { SVGInjector } from '@tanem/svg-injector'; var pathIcon = new Icon({ - iconUrl: 'images/marker-icon.png', - shadowUrl: 'images/marker-shadow.png', + iconUrl: '/resources/theme/images/markers/marker-icon.png', + shadowUrl: '/resources/theme/images/markers/marker-shadow.png', iconAnchor: [13, 41] }); -export class Unit extends Marker { +export class Unit extends CustomMarker { ID: number; #data: UnitData = { @@ -114,22 +116,7 @@ export class Unit extends Marker { /* Set the unit data */ this.setData(data); - /* Set the icon */ - var icon = new DivIcon({ - html: this.getMarkerHTML(), - className: 'leaflet-unit-marker', - iconAnchor: [25, 25], - iconSize: [50, 50], - }); - this.setIcon(icon); - } - - getMarkerHTML() { - return `
-
-
-
-
` + } getMarkerCategory() { @@ -137,6 +124,20 @@ export class Unit extends Marker { return ""; } + getActiveMarkerElements() { + // Default values + return { + state: false, + vvi: false, + hotgroup: false, + unitIcon: true, + shortLabel: false, + fuel: false, + ammo: false, + summary: false + } + } + setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { @@ -285,6 +286,104 @@ export class Unit extends Marker { return this.getData().optionsData; } + /********************** Icon *************************/ + createIcon(): void { + /* Set the icon */ + var icon = new DivIcon({ + className: 'leaflet-unit-icon', + iconAnchor: [25, 25], + iconSize: [50, 50], + }); + this.setIcon(icon); + + var el = document.createElement("div"); + el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); + el.setAttribute("data-coalition", this.getMissionData().coalition); + + // Generate and append elements depending on active options + // State icon + if (this.getActiveMarkerElements().state){ + var state = document.createElement("div"); + state.classList.add("unit-state"); + el.appendChild(state); + } + + // Velocity vector + if (this.getActiveMarkerElements().vvi) { + var vvi = document.createElement("div"); + vvi.classList.add("unit-vvi"); + vvi.toggleAttribute("data-rotate-to-heading"); + el.append(vvi); + } + + // Hotgroup indicator + if (this.getActiveMarkerElements().hotgroup) { + var hotgroup = document.createElement("div"); + hotgroup.classList.add("unit-hotgroup"); + var hotgroupId = document.createElement("div"); + hotgroupId.classList.add("unit-hotgroup-id"); + hotgroup.appendChild(hotgroupId); + el.append(hotgroup); + } + + // Main icon + if (this.getActiveMarkerElements().unitIcon) { + var unitIcon = document.createElement("div"); + unitIcon.classList.add("unit-icon"); + var img = document.createElement("img"); + img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; + img.onload = () => SVGInjector(img); + unitIcon.appendChild(img); + el.append(unitIcon); + } + + // Short label + if (this.getActiveMarkerElements().shortLabel) { + var shortLabel = document.createElement("div"); + shortLabel.classList.add("unit-short-label"); + shortLabel.innerText = aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""; //TODO: fix, use correct database + el.append(shortLabel); + } + + // Fuel indicator + if (this.getActiveMarkerElements().fuel) { + var fuelIndicator = document.createElement("div"); + fuelIndicator.classList.add("unit-fuel"); + var fuelLevel = document.createElement("div"); + fuelLevel.classList.add("unit-fuel-level"); + fuelIndicator.appendChild(fuelLevel); + el.append(fuelIndicator); + } + + // Ammo indicator + if (this.getActiveMarkerElements().ammo){ + var ammoIndicator = document.createElement("div"); + ammoIndicator.classList.add("unit-ammo"); + for (let i = 0; i <= 3; i++) + ammoIndicator.appendChild(document.createElement("div")); + el.append(ammoIndicator); + } + + // Unit summary + if (this.getActiveMarkerElements().summary) { + var summary = document.createElement("div"); + summary.classList.add("unit-summary"); + var callsign = document.createElement("div"); + callsign.classList.add("unit-callsign"); + callsign.innerText = this.getBaseData().unitName; + var altitude = document.createElement("div"); + altitude.classList.add("unit-altitude"); + var speed = document.createElement("div"); + speed.classList.add("unit-speed"); + summary.appendChild(callsign); + summary.appendChild(altitude); + summary.appendChild(speed); + el.appendChild(summary); + } + + this.getElement()?.appendChild(el); + } + /********************** Visibility *************************/ updateVisibility() { var hidden = false; @@ -295,7 +394,9 @@ export class Unit extends Marker { hidden = true; else if (hiddenUnits.includes(this.getMarkerCategory())) hidden = true; - this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || hidden || !this.getBaseData().alive); + else if (hiddenUnits.includes(this.getMissionData().coalition)) + hidden = true; + this.setHidden(hidden || !this.getBaseData().alive); } setHidden(hidden: boolean) { @@ -576,7 +677,7 @@ export class Unit extends Marker { el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); - /* Turn on ordnance indicators */ + /* Turn on ammo indicators */ var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1"); var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2"); var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3"); @@ -690,7 +791,18 @@ export class Unit extends Marker { } export class AirUnit extends Unit { - + getActiveMarkerElements() { + return { + state: true, + vvi: true, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: true, + ammo: true, + summary: true + }; + } } export class Aircraft extends AirUnit { @@ -698,30 +810,6 @@ export class Aircraft extends AirUnit { super(ID, data); } - getMarkerHTML() { - return `
-
-
-
-
-
${aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""}
-
-
-
-
-
-
-
-
-
-
-
${this.getBaseData().unitName}
-
-
-
-
` - } - getMarkerCategory() { return "aircraft"; } @@ -732,7 +820,7 @@ export class Helicopter extends AirUnit { super(ID, data); } - getVisibilityCategory() { + getMarkerCategory() { return "helicopter"; } } @@ -742,15 +830,17 @@ export class GroundUnit extends Unit { super(ID, data); } - getMarkerHTML() { - var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - return `
-
-
${role?.substring(0, 1)?.toUpperCase() || ""}
-
-
-
-
` + getActiveMarkerElements() { + return { + state: true, + vvi: false, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: false, + ammo: false, + summary: false + }; } getMarkerCategory() { @@ -766,6 +856,19 @@ export class NavyUnit extends Unit { super(ID, data); } + getActiveMarkerElements() { + return { + state: true, + vvi: false, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: false, + ammo: false, + summary: false + }; + } + getMarkerCategory() { return "navyunit"; } @@ -776,14 +879,6 @@ export class Weapon extends Unit { super(ID, data); this.setSelectable(false); } - - getMarkerHTML(): string { - return `
-
-
-
` - } - } export class Missile extends Weapon { diff --git a/client/views/index.ejs b/client/views/index.ejs index 863572aa..558d6b67 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -9,9 +9,7 @@ - - - + diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 06aeeeb9..3ae2453a 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -4,7 +4,7 @@
-
+

DCS Olympus

version v0.2.1
diff --git a/client/views/panels/unitcontrol.ejs b/client/views/panels/unitcontrol.ejs index d27223f5..fe67082c 100644 --- a/client/views/panels/unitcontrol.ejs +++ b/client/views/panels/unitcontrol.ejs @@ -40,21 +40,21 @@

Rules of engagement

-
+

Reaction to threat

-
+

Radar & ECM

-
+
diff --git a/client/views/uikit/uikit.ejs b/client/views/uikit/uikit.ejs index 9bcc7e2c..e4c0a2f4 100644 --- a/client/views/uikit/uikit.ejs +++ b/client/views/uikit/uikit.ejs @@ -2,7 +2,7 @@ Olympus UI Kit - + @@ -172,7 +172,7 @@
-
+
Z
@@ -184,7 +184,7 @@
-
+
Y
@@ -196,7 +196,7 @@
-
+
X
@@ -218,7 +218,7 @@
-
+
Z
@@ -230,7 +230,7 @@
-
+
Y
@@ -242,7 +242,7 @@
-
+
X
@@ -263,7 +263,7 @@
-
+
Z
@@ -275,7 +275,7 @@
-
+
Y
@@ -287,7 +287,7 @@
-
+
X
@@ -308,7 +308,7 @@
-
+
J
@@ -320,7 +320,7 @@
-
+
K
@@ -332,7 +332,7 @@
-
+
L
@@ -360,7 +360,7 @@
4
-
+
18
@@ -384,7 +384,7 @@
4
-
+
18
@@ -407,7 +407,7 @@
4
-
+
18
@@ -430,7 +430,7 @@
4
-
+
18
@@ -453,7 +453,7 @@
4
-
+
18
@@ -476,7 +476,7 @@
4
-
+
18
@@ -509,7 +509,7 @@
4
-
+
18
@@ -539,7 +539,7 @@
4
-
+
18
@@ -568,7 +568,7 @@
4
-
+
18
@@ -598,7 +598,7 @@
4
-
+
18
@@ -628,7 +628,7 @@
4
-
+
18
@@ -657,7 +657,7 @@
4
-
+
18
@@ -695,7 +695,7 @@
4
-
+
18
@@ -725,7 +725,7 @@
4
-
+
18
@@ -754,7 +754,7 @@
4
-
+
18
@@ -791,7 +791,7 @@
-
+
@@ -800,7 +800,7 @@
-
+
@@ -808,7 +808,7 @@
-
+
@@ -1183,19 +1183,19 @@
Actions
- + icons_actions_gas
- + icons_actions_nothing
- + icons_actions_rtb
- + icons_actions_search
@@ -1205,19 +1205,19 @@
RoE
- + icons_roe_free
- + icons_roe_return
- + icons_roe_stop
- + icons_roe_target
@@ -1227,11 +1227,11 @@
Threat
- + icons_threat_protect
- + icons_threat_retreat
From 3009a73a665556d6e6eac754d76a2ea692856fe2 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 24 May 2023 11:07:41 +0200 Subject: [PATCH 10/10] Completed transition to injected svgs --- client/package-lock.json | 35 ---- client/public/stylesheets/markers/units.css | 26 +-- .../public/stylesheets/other/contextmenus.css | 7 +- .../public/stylesheets/panels/mouseinfo.css | 2 +- .../formations/icons_form_abreast_dark.svg | 5 - .../formations/icons_form_abreast_light.svg | 5 - .../formations/icons_form_admin_dark.svg | 4 - .../formations/icons_form_admin_light.svg | 4 - .../formations/icons_form_echelon_dark.svg | 5 - .../formations/icons_form_echelon_light.svg | 5 - .../formations/icons_form_trail_dark.svg | 5 - .../formations/icons_form_trail_light.svg | 5 - .../themes/olympus/images/icons/diamond.svg | 73 +++++++ client/src/@types/unit.d.ts | 12 ++ client/src/controls/dropdown.ts | 5 +- client/src/controls/slider.ts | 2 +- client/src/controls/unitcontextmenu.ts | 13 +- client/src/index.ts | 35 +--- client/src/map/custommarker.ts | 3 +- client/src/map/map.ts | 24 ++- client/src/panels/hotgrouppanel.ts | 20 +- client/src/panels/unitcontrolpanel.ts | 6 +- client/src/units/unit.ts | 189 +++++++++++------- client/src/units/unitdatabase.ts | 53 ++--- client/src/units/unitsmanager.ts | 83 ++++---- scripts/OlympusCommand.lua | 10 +- 26 files changed, 333 insertions(+), 303 deletions(-) delete mode 100644 client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_abreast_light.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_admin_dark.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_admin_light.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_echelon_light.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_trail_dark.svg delete mode 100644 client/public/themes/olympus/images/formations/icons_form_trail_light.svg create mode 100644 client/public/themes/olympus/images/icons/diamond.svg diff --git a/client/package-lock.json b/client/package-lock.json index 44990dba..05df666f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,17 +14,14 @@ "debug": "~2.6.9", "ejs": "^3.1.8", "express": "~4.16.1", - "geomag": "^1.0.0", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", "leaflet.nauticscale": "^1.1.0", - "milsymbol": "^2.0.0", "morgan": "~1.9.1", "save": "^2.9.0" }, "devDependencies": { "@babel/preset-env": "^7.21.4", - "@iconfu/svg-inject": "^1.2.3", "@tanem/svg-injector": "^10.1.55", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", @@ -1772,12 +1769,6 @@ "node": ">=6.9.0" } }, - "node_modules/@iconfu/svg-inject": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", - "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==", - "dev": true - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -3454,11 +3445,6 @@ "node": ">=6.9.0" } }, - "node_modules/geomag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/geomag/-/geomag-1.0.0.tgz", - "integrity": "sha512-mEblE1vO7HojL7nk2R2s670s1nc/u0jtQaP/8EvZxPZ7XlfkwEJ+TWpjgkNy2402Yj2/VaxaefGQgIzbUzUwHg==" - }, "node_modules/get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -4161,11 +4147,6 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/milsymbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/milsymbol/-/milsymbol-2.0.0.tgz", - "integrity": "sha512-GcBFrcIUr8jScaZqZb0SI2W6AbnUrPCTHu2kqHxduQjN2DIN8q5pY6ksSWfnJ4HlcIAWQhyotbdPIr1bBxFbwQ==" - }, "node_modules/mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", @@ -6919,12 +6900,6 @@ "to-fast-properties": "^2.0.0" } }, - "@iconfu/svg-inject": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@iconfu/svg-inject/-/svg-inject-1.2.3.tgz", - "integrity": "sha512-3v1MUAJqmJS4jmhHoCkSxt+EdJrjPHlLXrWocCT25kCxnxJto8028Z6CC406EL11KA53SDZgI/QQA5GEJAoiRw==", - "dev": true - }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -8320,11 +8295,6 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, - "geomag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/geomag/-/geomag-1.0.0.tgz", - "integrity": "sha512-mEblE1vO7HojL7nk2R2s670s1nc/u0jtQaP/8EvZxPZ7XlfkwEJ+TWpjgkNy2402Yj2/VaxaefGQgIzbUzUwHg==" - }, "get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -8856,11 +8826,6 @@ } } }, - "milsymbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/milsymbol/-/milsymbol-2.0.0.tgz", - "integrity": "sha512-GcBFrcIUr8jScaZqZb0SI2W6AbnUrPCTHu2kqHxduQjN2DIN8q5pY6ksSWfnJ4HlcIAWQhyotbdPIr1bBxFbwQ==" - }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 056a92d3..43bc53a0 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -205,10 +205,10 @@ display: flex; } -[data-object|="unit"][data-has-fox-1] .unit-ammo-fox-1, -[data-object|="unit"][data-has-fox-2] .unit-ammo-fox-2, -[data-object|="unit"][data-has-fox-3] .unit-ammo-fox-3, -[data-object|="unit"][data-has-other-ammo] .unit-ammo-other { +[data-object|="unit"][data-has-fox-1] .unit-ammo>div:nth-child(1), +[data-object|="unit"][data-has-fox-2] .unit-ammo>div:nth-child(2), +[data-object|="unit"][data-has-fox-3] .unit-ammo>div:nth-child(3), +[data-object|="unit"][data-has-other-ammo] .unit-ammo>div:nth-child(4) { background-color: var(--secondary-gunmetal-grey); } @@ -217,10 +217,10 @@ } [data-object|="unit"][data-coalition="blue"] .unit-fuel-level, -[data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo-fox-1, -[data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo-fox-2, -[data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo-fox-3, -[data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo-other { +[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), +[data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo>div:nth-child(4) { background-color: var(--primary-blue); } @@ -233,10 +233,10 @@ } [data-object|="unit"][data-coalition="red"] .unit-fuel-level, -[data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo-fox-1, -[data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo-fox-2, -[data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo-fox-3, -[data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo-other { +[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), +[data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo>div:nth-child(4) { background-color: var(--primary-red); } @@ -250,6 +250,8 @@ height: 20px; position: absolute; width: 20px; + left: 0px; + top: 0px; } [data-object|="unit"][data-state="rtb"] .unit-state { diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 361a6a0b..9e325678 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -307,7 +307,8 @@ content: url("/resources/theme/images/icons/echelon-rh.svg"); } -#line-abreast::before { +#line-abreast-rh::before, +#line-abreast-lh::before { content: url("/resources/theme/images/icons/line-abreast.svg"); } @@ -315,6 +316,10 @@ content: url("/resources/theme/images/icons/front.svg"); } +#diamond::before { + content: url("/resources/theme/images/icons/diamond.svg"); +} + #custom::before { content: url("/resources/theme/images/icons/custom.svg"); } diff --git a/client/public/stylesheets/panels/mouseinfo.css b/client/public/stylesheets/panels/mouseinfo.css index cfbb70c6..1c3d5e2e 100644 --- a/client/public/stylesheets/panels/mouseinfo.css +++ b/client/public/stylesheets/panels/mouseinfo.css @@ -39,7 +39,7 @@ } #mouse-info-panel dt#ref-measure-position::after { - background-image: url("/resources/theme/images/pin.png"); + background-image: url("/resources/theme/images/icons/pin.png"); background-position: 50% 50%; background-repeat: no-repeat; background-size: 16px 16px; diff --git a/client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg b/client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg deleted file mode 100644 index 513d6b25..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_abreast_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_abreast_light.svg b/client/public/themes/olympus/images/formations/icons_form_abreast_light.svg deleted file mode 100644 index 0b302a04..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_abreast_light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_admin_dark.svg b/client/public/themes/olympus/images/formations/icons_form_admin_dark.svg deleted file mode 100644 index 44e42f62..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_admin_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_admin_light.svg b/client/public/themes/olympus/images/formations/icons_form_admin_light.svg deleted file mode 100644 index 532c523f..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_admin_light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg b/client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg deleted file mode 100644 index dc4b4b60..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_echelon_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_echelon_light.svg b/client/public/themes/olympus/images/formations/icons_form_echelon_light.svg deleted file mode 100644 index a788953d..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_echelon_light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_trail_dark.svg b/client/public/themes/olympus/images/formations/icons_form_trail_dark.svg deleted file mode 100644 index 555ea0dd..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_trail_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/formations/icons_form_trail_light.svg b/client/public/themes/olympus/images/formations/icons_form_trail_light.svg deleted file mode 100644 index 0ac432f7..00000000 --- a/client/public/themes/olympus/images/formations/icons_form_trail_light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/icons/diamond.svg b/client/public/themes/olympus/images/icons/diamond.svg new file mode 100644 index 00000000..eef3c6f2 --- /dev/null +++ b/client/public/themes/olympus/images/icons/diamond.svg @@ -0,0 +1,73 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index f3741e27..e6b1b345 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -79,4 +79,16 @@ interface GeneralSettings { prohibitAG: boolean; prohibitAfterburner: boolean; prohibitAirWpn: boolean; +} + +interface UnitIconOptions { + showState: boolean, + showVvi: boolean, + showHotgroup: boolean, + showUnitIcon: boolean, + showShortLabel: boolean, + showFuel: boolean, + showAmmo: boolean, + showSummary: boolean, + rotateToHeading: boolean } \ No newline at end of file diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 641c405f..01aae36e 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -67,7 +67,10 @@ export class Dropdown { selectValue(idx: number) { if (idx < this.#optionsList.length) { var option = this.#optionsList[idx]; - this.#value.innerHTML = `
${option}
`; + var el = document.createElement("div"); + el.classList.add("ol-ellipsed"); + el.innerText = option; + this.#value.appendChild(el); this.#index = idx; this.#close(); this.#callback(option); diff --git a/client/src/controls/slider.ts b/client/src/controls/slider.ts index 7fab7782..e8070d2e 100644 --- a/client/src/controls/slider.ts +++ b/client/src/controls/slider.ts @@ -88,7 +88,7 @@ export class Slider { #onValue() { if (this.#valueText != null && this.#slider != null) - this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit + this.#valueText.innerText = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit this.setActive(true); } diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index 86250e6d..4acbaf3e 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -40,11 +40,16 @@ export class UnitContextMenu extends ContextMenu { this.#customFormationCallback = callback; } - setOptions(options: { [key: string]: string }, callback: CallableFunction) { - this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => { + setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) { + this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => { + const option = options[key]; var button = document.createElement("button"); - button.innerHTML = options[option]; - button.addEventListener("click", () => callback(option)); + var el = document.createElement("div"); + el.title = option.tooltip; + el.innerText = option.text; + el.id = key; + button.addEventListener("click", () => callback(key)); + button.appendChild(el); return (button); })); } diff --git a/client/src/index.ts b/client/src/index.ts index 16d9a8b2..c2f8a1b6 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -140,28 +140,14 @@ function setupEvents() { case "Space": setPaused(!getPaused()); break; - case "KeyW": - case "KeyA": - case "KeyS": - case "KeyD": - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": - case "ArrowDown": + case "KeyW": case "KeyA": case "KeyS": case "KeyD": + case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown": getMap().handleMapPanning(ev); break; - case "Digit1": - case "Digit2": - case "Digit3": - case "Digit4": - case "Digit5": - case "Digit6": - case "Digit7": - case "Digit8": - case "Digit9": + case "Digit1": case "Digit2": case "Digit3": case "Digit4": case "Digit5": case "Digit6": case "Digit7": case "Digit8": case "Digit9": // Using the substring because the key will be invalid when pressing the Shift key if (ev.ctrlKey && ev.shiftKey) - getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); + getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); else if (ev.ctrlKey && !ev.shiftKey) getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5))); else @@ -176,14 +162,7 @@ function setupEvents() { return; } switch (ev.code) { - case "KeyW": - case "KeyA": - case "KeyS": - case "KeyD": - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": - case "ArrowDown": + case "KeyW": case "KeyA": case "KeyS": case "KeyD": case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown": getMap().handleMapPanning(ev); break; } @@ -201,8 +180,8 @@ function setupEvents() { document.addEventListener("tryConnection", () => { const form = document.querySelector("#splash-content")?.querySelector("#authentication-form"); - const username = ( (form?.querySelector("#username"))).value; - const password = ( (form?.querySelector("#password"))).value; + const username = ((form?.querySelector("#username"))).value; + const password = ((form?.querySelector("#password"))).value; setCredentials(username, btoa("admin" + ":" + password)); /* Start periodically requesting updates */ diff --git a/client/src/map/custommarker.ts b/client/src/map/custommarker.ts index 41d79c14..a6ca5998 100644 --- a/client/src/map/custommarker.ts +++ b/client/src/map/custommarker.ts @@ -1,4 +1,4 @@ -import { Map, Marker } from "leaflet"; +import { DivIcon, Map, Marker } from "leaflet"; import { MarkerOptions } from "leaflet"; import { LatLngExpression } from "leaflet"; @@ -8,6 +8,7 @@ export class CustomMarker extends Marker { } onAdd(map: Map): this { + this.setIcon(new DivIcon()); // Default empty icon super.onAdd(map); this.createIcon(); return this; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index a36ae834..477b8640 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -22,6 +22,7 @@ require("../../public/javascripts/leaflet.nauticscale.js") export const IDLE = "IDLE"; export const MOVE_UNIT = "MOVE_UNIT"; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; +export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; export class Map extends L.Map { #state: string; @@ -116,7 +117,7 @@ export class Map extends L.Map { /* Option buttons */ this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { - return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", "toggleUnitVisibility", `{"type": "${option}"}`); + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`); }); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); } @@ -194,7 +195,7 @@ export class Map extends L.Map { }) this.#destinationPreviewMarkers = []; - if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { + if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { /* Create the unit destination preview markers */ this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => { var marker = new DestinationPreviewMarker(this.getMouseCoordinates()); @@ -425,6 +426,9 @@ export class Map extends L.Map { getUnitsManager().selectedUnitsClearDestinations(); } getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation) + this.#destinationGroupRotation = 0; + this.#destinationRotationCenter = null; + this.#computeDestinationRotation = false; } } @@ -440,18 +444,18 @@ export class Map extends L.Map { #onMouseDown(e: any) { this.hideAllContextMenus(); - if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) { - this.#computeDestinationRotation = true; - this.#destinationRotationCenter = this.getMouseCoordinates(); + if (this.#state == MOVE_UNIT) { + this.#destinationGroupRotation = 0; + this.#destinationRotationCenter = null; + this.#computeDestinationRotation = false; + if (e.originalEvent.button == 2) { + this.#computeDestinationRotation = true; + this.#destinationRotationCenter = this.getMouseCoordinates(); + } } } #onMouseUp(e: any) { - if (this.#state == MOVE_UNIT) { - this.#computeDestinationRotation = false; - this.#destinationRotationCenter = null; - this.#destinationGroupRotation = 0; - } } #onMouseMove(e: any) { diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts index 8431c2ab..f0abdb85 100644 --- a/client/src/panels/hotgrouppanel.ts +++ b/client/src/panels/hotgrouppanel.ts @@ -18,14 +18,24 @@ export class HotgroupPanel extends Panel { } addHotgroup(hotgroup: number) { - const hotgroupHtml = `
-
${hotgroup}
-
- x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}` + // Hotgroup number + var hotgroupDiv = document.createElement("div"); + hotgroupDiv.classList.add("unit-hotgroup"); + var idDiv = document.createElement("div"); + idDiv.classList.add("unit-hotgroup-id"); + idDiv.innerText = String(hotgroup); + hotgroupDiv.appendChild(idDiv); + + // Hotgroup unit count + var countDiv = document.createElement("div"); + countDiv.innerText = `x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`; + var el = document.createElement("div"); + el.appendChild(hotgroupDiv); + el.appendChild(countDiv); el.classList.add("hotgroup-selector"); - el.innerHTML = hotgroupHtml; el.toggleAttribute(`data-hotgroup-${hotgroup}`, true) + this.getElement().appendChild(el); el.addEventListener("click", () => { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 7b1967d4..ea90161d 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -1,3 +1,4 @@ +import { SVGInjector } from "@tanem/svg-injector"; import { getUnitsManager } from ".."; import { Dropdown } from "../controls/dropdown"; import { Slider } from "../controls/slider"; @@ -346,7 +347,10 @@ export class UnitControlPanel extends Panel { var button = document.createElement("button"); button.title = title; button.value = value; - button.innerHTML = `` + var img = document.createElement("img"); + img.src = `/resources/theme/images/buttons/${url}`; + img.onload = () => SVGInjector(img); + button.appendChild(img); button.addEventListener("click", callback); return button; } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 3338c095..d54034a7 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -6,6 +6,7 @@ import { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; +import { UnitDatabase } from './unitdatabase'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -124,17 +125,23 @@ export class Unit extends CustomMarker { return ""; } - getActiveMarkerElements() { - // Default values + getDatabase(): UnitDatabase | null { + // Overloaded by child classes + return null; + } + + getIconOptions(): UnitIconOptions { + // Default values, overloaded by child classes if needed return { - state: false, - vvi: false, - hotgroup: false, - unitIcon: true, - shortLabel: false, - fuel: false, - ammo: false, - summary: false + showState: false, + showVvi: false, + showHotgroup: false, + showUnitIcon: true, + showShortLabel: false, + showFuel: false, + showAmmo: false, + showSummary: false, + rotateToHeading: false } } @@ -165,6 +172,7 @@ export class Unit extends CustomMarker { setHotgroup(hotgroup: number | null) { this.#hotgroup = hotgroup; + this.#updateMarker(); } getHotgroup() { @@ -172,7 +180,7 @@ export class Unit extends CustomMarker { } setHighlighted(highlighted: boolean) { - if (this.#highlighted != highlighted) { + if (this.getSelectable() && this.#highlighted != highlighted) { this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); this.#highlighted = highlighted; this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted)); @@ -297,19 +305,13 @@ export class Unit extends CustomMarker { this.setIcon(icon); var el = document.createElement("div"); + el.classList.add("unit"); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); el.setAttribute("data-coalition", this.getMissionData().coalition); - // Generate and append elements depending on active options - // State icon - if (this.getActiveMarkerElements().state){ - var state = document.createElement("div"); - state.classList.add("unit-state"); - el.appendChild(state); - } - + // Generate and append elements depending on active options // Velocity vector - if (this.getActiveMarkerElements().vvi) { + if (this.getIconOptions().showVvi) { var vvi = document.createElement("div"); vvi.classList.add("unit-vvi"); vvi.toggleAttribute("data-rotate-to-heading"); @@ -317,7 +319,7 @@ export class Unit extends CustomMarker { } // Hotgroup indicator - if (this.getActiveMarkerElements().hotgroup) { + if (this.getIconOptions().showHotgroup) { var hotgroup = document.createElement("div"); hotgroup.classList.add("unit-hotgroup"); var hotgroupId = document.createElement("div"); @@ -327,26 +329,34 @@ export class Unit extends CustomMarker { } // Main icon - if (this.getActiveMarkerElements().unitIcon) { + if (this.getIconOptions().showUnitIcon) { var unitIcon = document.createElement("div"); unitIcon.classList.add("unit-icon"); var img = document.createElement("img"); img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; img.onload = () => SVGInjector(img); unitIcon.appendChild(img); + unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading); el.append(unitIcon); } + // State icon + if (this.getIconOptions().showState){ + var state = document.createElement("div"); + state.classList.add("unit-state"); + el.appendChild(state); + } + // Short label - if (this.getActiveMarkerElements().shortLabel) { + if (this.getIconOptions().showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""; //TODO: fix, use correct database + shortLabel.innerText = this.getDatabase()?.getByName(this.getBaseData().name)?.shortLabel || ""; el.append(shortLabel); } // Fuel indicator - if (this.getActiveMarkerElements().fuel) { + if (this.getIconOptions().showFuel) { var fuelIndicator = document.createElement("div"); fuelIndicator.classList.add("unit-fuel"); var fuelLevel = document.createElement("div"); @@ -356,7 +366,7 @@ export class Unit extends CustomMarker { } // Ammo indicator - if (this.getActiveMarkerElements().ammo){ + if (this.getIconOptions().showAmmo){ var ammoIndicator = document.createElement("div"); ammoIndicator.classList.add("unit-ammo"); for (let i = 0; i <= 3; i++) @@ -365,7 +375,7 @@ export class Unit extends CustomMarker { } // Unit summary - if (this.getActiveMarkerElements().summary) { + if (this.getIconOptions().showSummary) { var summary = document.createElement("div"); summary.classList.add("unit-summary"); var callsign = document.createElement("div"); @@ -544,18 +554,18 @@ export class Unit extends CustomMarker { } #onContextMenu(e: any) { - var options: { [key: string]: string } = {}; + var options: {[key: string]: {text: string, tooltip: string}} = {}; - options["Center"] = `
Center map
`; + options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"}; if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) { - options['Attack'] = `
Attack
`; + options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"}; if (getUnitsManager().getSelectedUnitsType() === "Aircraft") - options['Follow'] = `
Follow
`; + options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; } else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { if (this.getBaseData().category == "Aircraft") { - options["Refuel"] = `
Refuel
`; // TODO Add some way of knowing which aircraft can AAR + options["refuel"] = {text: "AAR Refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR } } @@ -569,28 +579,28 @@ export class Unit extends CustomMarker { } #executeAction(e: any, action: string) { - if (action === "Center") + if (action === "center-map") getMap().centerOnUnit(this.ID); - if (action === "Attack") + if (action === "attack") getUnitsManager().selectedUnitsAttackUnit(this.ID); - else if (action === "Refuel") + else if (action === "refuel") getUnitsManager().selectedUnitsRefuel(); - else if (action === "Follow") + else if (action === "follow") this.#showFollowOptions(e); } #showFollowOptions(e: any) { - var options: { [key: string]: string } = {}; + var options: {[key: string]: {text: string, tooltip: string}} = {}; options = { - 'Trail': `
Trail
`, - 'Echelon (LH)': `
Echelon (left)
`, - 'Echelon (RH)': `
Echelon (right)
`, - 'Line abreast (LH)': `
Line abreast (left)
`, - 'Line abreast (RH)': `
Line abreast (right)
`, - 'Front': `
In front
`, - 'Diamond': `
Diamond
`, - 'Custom': `
Custom
` + 'trail': {text: "Trail", tooltip: "Follow unit in trail formation"}, + 'echelon-lh': {text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation"}, + 'echelon-rh': {text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation"}, + 'line-abreast-lh': {text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation"}, + 'line-abreast-rh': {text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation"}, + 'front': {text: "Front", tooltip: "Fly in front of unit"}, + 'diamond': {text: "Diamond", tooltip: "Follow unit in diamond formation"}, + 'custom': {text: "Custom", tooltip: "Set a custom formation position"}, } getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -601,7 +611,7 @@ export class Unit extends CustomMarker { } #applyFollowOptions(action: string) { - if (action === "Custom") { + if (action === "custom") { document.getElementById("custom-formation-dialog")?.classList.remove("hide"); getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => { getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); @@ -657,18 +667,18 @@ export class Unit extends CustomMarker { element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); /* Set current unit state */ - if (this.getMissionData().flags.Human) // Unit is human + if (this.getMissionData().flags.Human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus) + else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); - else // Unit is under Olympus control + else // Unit is under Olympus control element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100)); if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384)); + (element.querySelector(".unit-speed")).innerText = String(Math.floor(this.getFlightData().speed * 1.94384)); /* Rotate elements according to heading */ element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { @@ -791,16 +801,17 @@ export class Unit extends CustomMarker { } export class AirUnit extends Unit { - getActiveMarkerElements() { + getIconOptions() { return { - state: true, - vvi: true, - hotgroup: true, - unitIcon: true, - shortLabel: true, - fuel: true, - ammo: true, - summary: true + showState: true, + showVvi: true, + showHotgroup: true, + showUnitIcon: true, + showShortLabel: true, + showFuel: true, + showAmmo: true, + showSummary: true, + rotateToHeading: false }; } } @@ -813,6 +824,10 @@ export class Aircraft extends AirUnit { getMarkerCategory() { return "aircraft"; } + + getDatabase(): UnitDatabase | null { + return aircraftDatabase; + } } export class Helicopter extends AirUnit { @@ -830,16 +845,17 @@ export class GroundUnit extends Unit { super(ID, data); } - getActiveMarkerElements() { + getIconOptions() { return { - state: true, - vvi: false, - hotgroup: true, - unitIcon: true, - shortLabel: true, - fuel: false, - ammo: false, - summary: false + showState: true, + showVvi: false, + showHotgroup: true, + showUnitIcon: true, + showShortLabel: true, + showFuel: false, + showAmmo: false, + showSummary: false, + rotateToHeading: false }; } @@ -849,6 +865,10 @@ export class GroundUnit extends Unit { var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other"; return markerCategory; } + + getDatabase(): UnitDatabase | null { + return groundUnitsDatabase; + } } export class NavyUnit extends Unit { @@ -856,16 +876,17 @@ export class NavyUnit extends Unit { super(ID, data); } - getActiveMarkerElements() { + getIconOptions() { return { - state: true, - vvi: false, - hotgroup: true, - unitIcon: true, - shortLabel: true, - fuel: false, - ammo: false, - summary: false + showState: true, + showVvi: false, + showHotgroup: true, + showUnitIcon: true, + showShortLabel: true, + showFuel: false, + showAmmo: false, + showSummary: false, + rotateToHeading: false }; } @@ -879,6 +900,20 @@ export class Weapon extends Unit { super(ID, data); this.setSelectable(false); } + + getIconOptions() { + return { + showState: false, + showVvi: false, + showHotgroup: false, + showUnitIcon: true, + showShortLabel: false, + showFuel: false, + showAmmo: false, + showSummary: false, + rotateToHeading: true + }; + } } export class Missile extends Weapon { diff --git a/client/src/units/unitdatabase.ts b/client/src/units/unitdatabase.ts index 9bfd0095..90d87863 100644 --- a/client/src/units/unitdatabase.ts +++ b/client/src/units/unitdatabase.ts @@ -1,21 +1,16 @@ export class UnitDatabase { - blueprints: {[key: string]: UnitBlueprint} = {}; + blueprints: { [key: string]: UnitBlueprint } = {}; - constructor() - { + constructor() { } /* Returns a list of all possible roles in a database */ - getRoles() - { + getRoles() { var roles: string[] = []; - for (let unit in this.blueprints) - { - for (let loadout of this.blueprints[unit].loadouts) - { - for (let role of loadout.roles) - { + for (let unit in this.blueprints) { + for (let loadout of this.blueprints[unit].loadouts) { + for (let role of loadout.roles) { if (role !== "" && !roles.includes(role)) roles.push(role); } @@ -25,18 +20,15 @@ export class UnitDatabase { } /* Gets a specific blueprint by name */ - getByName(name: string) - { + getByName(name: string) { if (name in this.blueprints) return this.blueprints[name]; return null; } /* Gets a specific blueprint by label */ - getByLabel(label: string) - { - for (let unit in this.blueprints) - { + getByLabel(label: string) { + for (let unit in this.blueprints) { if (this.blueprints[unit].label === label) return this.blueprints[unit]; } @@ -44,15 +36,11 @@ export class UnitDatabase { } /* Get all blueprints by role */ - getByRole(role: string) - { + getByRole(role: string) { var units = []; - for (let unit in this.blueprints) - { - for (let loadout of this.blueprints[unit].loadouts) - { - if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) - { + for (let unit in this.blueprints) { + for (let loadout of this.blueprints[unit].loadouts) { + if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) { units.push(this.blueprints[unit]) break; } @@ -62,13 +50,10 @@ export class UnitDatabase { } /* Get the names of all the loadouts for a specific unit and for a specific role */ - getLoadoutNamesByRole(name: string, role: string) - { + getLoadoutNamesByRole(name: string, role: string) { var loadouts = []; - for (let loadout of this.blueprints[name].loadouts) - { - if (loadout.roles.includes(role) || loadout.roles.includes("")) - { + for (let loadout of this.blueprints[name].loadouts) { + if (loadout.roles.includes(role) || loadout.roles.includes("")) { loadouts.push(loadout.name) } } @@ -76,10 +61,8 @@ export class UnitDatabase { } /* Get the loadout content from the unit name and loadout name */ - getLoadoutByName(name: string, loadoutName: string) - { - for (let loadout of this.blueprints[name].loadouts) - { + getLoadoutByName(name: string, loadoutName: string) { + for (let loadout of this.blueprints[name].loadouts) { if (loadout.name === loadoutName) return loadout; } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 07964d7a..07f5ac9c 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -47,7 +47,7 @@ export class UnitsManager { } getUnitsByHotgroup(hotgroup: number) { - return Object.values(this.#units).filter((unit: Unit) => {return unit.getBaseData().alive && unit.getHotgroup() == hotgroup}); + return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup }); } addUnit(ID: number, data: UnitData) { @@ -88,10 +88,8 @@ export class UnitsManager { }); } - setHiddenType(key: string, value: boolean) - { - if (value) - { + setHiddenType(key: string, value: boolean) { + if (value) { if (this.#hiddenTypes.includes(key)) delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)]; } @@ -100,8 +98,7 @@ export class UnitsManager { Object.values(this.getUnits()).forEach((unit: Unit) => unit.updateVisibility()); } - getHiddenTypes() - { + getHiddenTypes() { return this.#hiddenTypes; } @@ -123,7 +120,7 @@ export class UnitsManager { } } - getSelectedUnits(options?: {excludeHumans?: boolean}) { + getSelectedUnits(options?: { excludeHumans?: boolean }) { var selectedUnits = []; for (let ID in this.#units) { if (this.#units[ID].getSelected()) { @@ -132,7 +129,7 @@ export class UnitsManager { } if (options) { if (options.excludeHumans) - selectedUnits = selectedUnits.filter((unit: Unit) => {return !unit.getMissionData().flags.Human}); + selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human }); } return selectedUnits; } @@ -190,14 +187,14 @@ export class UnitsManager { /*********************** Actions on selected units ************************/ selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); /* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative distances */ - var unitDestinations: {[key: number]: LatLng} = {}; + var unitDestinations: { [key: number]: LatLng } = {}; if (mantainRelativePosition) unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation); else - selectedUnits.forEach((unit: Unit) => {unitDestinations[unit.ID] = latlng}); + selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng }); for (let idx in selectedUnits) { const unit = selectedUnits[idx]; @@ -206,7 +203,7 @@ export class UnitsManager { const leader = this.getUnitByID(unit.getFormationData().leaderID) if (leader && leader.getSelected()) leader.addDestination(latlng); - else + else unit.addDestination(latlng); } else { @@ -219,7 +216,7 @@ export class UnitsManager { } selectedUnitsClearDestinations() { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { const unit = selectedUnits[idx]; if (unit.getTaskData().currentState === "Follow") { @@ -235,7 +232,7 @@ export class UnitsManager { } selectedUnitsLandAt(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].landAt(latlng); } @@ -243,21 +240,21 @@ export class UnitsManager { } selectedUnitsChangeSpeed(speedChange: string) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeSpeed(speedChange); } } selectedUnitsChangeAltitude(altitudeChange: string) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeAltitude(altitudeChange); } } selectedUnitsSetSpeed(speed: number) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeed(speed); } @@ -265,7 +262,7 @@ export class UnitsManager { } selectedUnitsSetAltitude(altitude: number) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitude(altitude); } @@ -273,7 +270,7 @@ export class UnitsManager { } selectedUnitsSetROE(ROE: string) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].setROE(ROE); } @@ -281,7 +278,7 @@ export class UnitsManager { } selectedUnitsSetReactionToThreat(reactionToThreat: string) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].setReactionToThreat(reactionToThreat); } @@ -289,7 +286,7 @@ export class UnitsManager { } selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure); } @@ -298,7 +295,7 @@ export class UnitsManager { selectedUnitsAttackUnit(ID: number) { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } @@ -314,7 +311,7 @@ export class UnitsManager { } selectedUnitsRefuel() { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); for (let idx in selectedUnits) { selectedUnits[idx].refuel(); } @@ -328,15 +325,15 @@ export class UnitsManager { // Y: top-bottom, positive top // Z: left-right, positive right offset = { "x": 0, "y": 0, "z": 0 }; - if (formation === "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; } - else if (formation === "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; } - else if (formation === "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; } - else if (formation === "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; } - else if (formation === "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; } - else if (formation === "Front") { offset.x = 100; offset.y = 0; offset.z = 0; } + if (formation === "trail") { offset.x = -50; offset.y = -30; offset.z = 0; } + else if (formation === "echelon-lh") { offset.x = -50; offset.y = -10; offset.z = -50; } + else if (formation === "echelon-rh") { offset.x = -50; offset.y = -10; offset.z = 50; } + else if (formation === "line-abreast-rh") { offset.x = 0; offset.y = 0; offset.z = 50; } + else if (formation === "line-abreast-lh") { offset.x = 0; offset.y = 0; offset.z = -50; } + else if (formation === "front") { offset.x = 100; offset.y = 0; offset.z = 0; } else offset = undefined; } - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); var count = 1; var xr = 0; var yr = 1; var zr = -1; var layer = 1; @@ -347,7 +344,7 @@ export class UnitsManager { unit.followUnit(ID, { "x": offset.x * count, "y": offset.y * count, "z": offset.z * count }); else { /* More complex formations with variable offsets */ - if (formation === "Diamond") { + if (formation === "diamond") { var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4); var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4); unit.followUnit(ID, { "x": -yl * 50, "y": zr * 10, "z": xl * 50 }); @@ -364,14 +361,12 @@ export class UnitsManager { this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } - selectedUnitsSetHotgroup(hotgroup: number) - { + selectedUnitsSetHotgroup(hotgroup: number) { this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null)); this.selectedUnitsAddToHotgroup(hotgroup); } - selectedUnitsAddToHotgroup(hotgroup: number) - { + selectedUnitsAddToHotgroup(hotgroup: number) { var selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { selectedUnits[idx].setHotgroup(hotgroup); @@ -380,11 +375,10 @@ export class UnitsManager { getHotgroupPanel().refreshHotgroups(); } - selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) - { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) { + var selectedUnits = this.getSelectedUnits({ excludeHumans: true }); /* Compute the center of the group */ - var center = {x: 0, y: 0}; + var center = { x: 0, y: 0 }; selectedUnits.forEach((unit: Unit) => { var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); center.x += mercator.x / selectedUnits.length; @@ -392,20 +386,19 @@ export class UnitsManager { }); /* Compute the distances from the center of the group */ - - var unitDestinations: {[key: number]: LatLng} = {}; + var unitDestinations: { [key: number]: LatLng } = {}; selectedUnits.forEach((unit: Unit) => { var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); - var distancesFromCenter = {dx: mercator.x - center.x, dy: mercator.y - center.y}; + var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y }; /* Rotate the distance according to the group rotation */ - var rotatedDistancesFromCenter: {dx: number, dy: number} = {dx: 0, dy: 0}; + var rotatedDistancesFromCenter: { dx: number, dy: number } = { dx: 0, dy: 0 }; rotatedDistancesFromCenter.dx = distancesFromCenter.dx * Math.cos(deg2rad(rotation)) - distancesFromCenter.dy * Math.sin(deg2rad(rotation)); rotatedDistancesFromCenter.dy = distancesFromCenter.dx * Math.sin(deg2rad(rotation)) + distancesFromCenter.dy * Math.cos(deg2rad(rotation)); /* Compute the final position of the unit */ var destMercator = latLngToMercator(latlng.lat, latlng.lng); // Convert destination point to mercator - var unitMercator = {x: destMercator.x + rotatedDistancesFromCenter.dx, y: destMercator.y + rotatedDistancesFromCenter.dy}; // Compute final position of this unit in mercator coordinates + var unitMercator = { x: destMercator.x + rotatedDistancesFromCenter.dx, y: destMercator.y + rotatedDistancesFromCenter.dy }; // Compute final position of this unit in mercator coordinates var unitLatLng = mercatorToLatLng(unitMercator.x, unitMercator.y); unitDestinations[unit.ID] = new LatLng(unitLatLng.lat, unitLatLng.lng); }); diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 68adfc0f..bd61f8d2 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -524,6 +524,7 @@ function Olympus.setMissionData(arg, time) bullseyes[i] = {} bullseyes[i]["latitude"] = bullseyeLatitude bullseyes[i]["longitude"] = bullseyeLongitude + bullseyes[i]["coalition"] = Olympus.getCoalitionByCoalitionID(i) end -- Units tactical data @@ -589,14 +590,7 @@ function Olympus.setMissionData(arg, time) local info = {} local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i])) info["callsign"] = Airbase.getCallsign(base[i]) - local coalitionID = Airbase.getCoalition(base[i]) - if coalitionID == 0 then - info["coalition"] = "neutral" - elseif coalitionID == 1 then - info["coalition"] = "red" - else - info["coalition"] = "blue" - end + info["coalition"] = Olympus.getCoalitionByCoalitionID(Airbase.getCoalition(base[i])) info["latitude"] = latitude info["longitude"] = longitude if Airbase.getUnit(base[i]) then