From 10d250e3a55f8445dd4bcd57f39853d1940f80ac Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 18 May 2023 09:09:20 +0200 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 32cb147a02ac7e7954b64f9a356d833d1339f2c3 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 22 May 2023 08:22:50 +0200 Subject: [PATCH 6/9] 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 7/9] 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 8/9] 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 z!E~q_Vqet!^s|$0QpLD>Ub5 zUbVwtSAact6}3lFU62xmq?5c#iUOTnA+17ka}K0kaSh2mi1CjZF8!asbz%YvSYFFB1;o?May^;u|2i+hlu5F_X7!PH zjG{@|e*&PjBJI=7Dy88!9??o7Yv5JK5Ag6#kjt8|qZtysth&m5CKF&!XS8ohT7Z-N z015(y4;O*NISG`1{><;rxJahowj$5JKc6OSGOHtIndS4IQX}zKZuJuuNI*Y%fE$G7x~8O~$>FgMF(qQ@040xBiSa8n z^7_$HxM5M|)?^i>qQdaaVj)*J-5(H*oxxJX5UpG36xw2CByhNEixZVp^U5|?AL75D z8xPSIE`)_*IRO%}2v77)%@7@x2qPp;Ht+avH^WO2);=>+jM%ZcaY<42LIxyKuwjI& zp%W^wVcsC!iNv47UB3yyYzkv;29*K8hZx4)0C=_UBfEnow@Y<0+B2VM{>TdT2A-+l z4iwZ7{OX_67Cw@>!dJD$t%Srb*YiY{uA7z+AV>_iPpp6&L+rZ$jAFVy9Xy_ z=SNr@wzG+nV(4XC5djKTzW(zHaESK>;|F8m{g_C2G=LhI;9*(xP_2Zk*Su#|DcH=> zj0Q44cr!A8lNqYdKL@LPNcA=aK0WCb%i#41oPJ9!yh=J3G2IYn~(!ig(0gf;I>oD##C<6D$S2o#V)>kTF&fiA#hAsaG}ps)Kz?bj%m z^&CRd&*RGP$U5<_I1$(qjkdZBlEQ=iI(dSkdFGeM%;Hd zv(<~-eKSL}8DgfK{*dk=fX?4~2nv8x;XdGqPKv0g2^I$q+rhFq>zUE!GZ#kOgzt*2 zFwwb2`zrH%0ub_3(p1?qj=p3gTd+@>AaNnvuZw@CfFxl}=lK+epM@M1Z6t{xtvVdl z>rru)jmtK`%ifDmm#vLXA5wXUb#6gM7Zl?an)HW4%N6G_qgw9cn>BIZeD8S>OIT2C z=$f+lBIdcbIU~G==A@G>ti4$?H99!=T7S*T@Kix6wFB46s&q96KXhmFq<2~yS+;jk z49427Cy~vREZ>)$#~Id7{eJUBVHd+V3`in9jw{1;6P4FjuAG_n8AV!gg(LZU>dIq~ zaYL8Hav^@UKuF#VJ_yHSG|pC z1P$t6rab|m7@G#vt}-PVz79e;55;p`s1%1E8xo+!mxr zAoKY#P%(#SaV=0VjX~9gDbDE&U8j`tDP%*nFsdlz^_!M}DTw-;XjQLj+mZx(tFg^Vc9Ez>RZv~@H%-ur;&gdvKK-IH zCYYkomehwT3gf$Uh2IaIK*qXn6s!A1i-noe&Kkkq(QUyF?L;U!oJh5xD;ezLXxkij z?EI_N{Z|IwB9d?@BcH}#0sER{4Qq$3G-P#ylDneVz>YBTRPG#d4uo27xmim=C}NJ` z(`;@^Icp9jujwEs2Bk;7%1!9AXgMWW@NMWK>7mCoc02UoqiG(Fn~9~C46KRnR^M6a zWCVmOwQW>_akd>?n`WIpWCB7Mxge)JKJ_o2KVZGsN9v8$9w$qG^`@Yev4>8q8WrCq z_Ug#t@OxTvm3wE3wtkqMJ^eur;>J#Ih)!`s#ty06GthVO?-Z`)wwAQD-zn5snMdFt zwf0CT30r~_7|KO54b#8=v}G@YR_rX-WIL^Lr9Q+r;xC>R1dn zS*Z*f-<;IFiK|uTY$!a?eDy<t{08}XU9$a}slVJ2<4Yz3 z5ao)8=m?2%NHy=X$CD>NV(JKO+VjY^(fhSgr3*ZYbSL1@cg&~ZeYCTH@JF!LgcGRj z`n)5Xja_z57)L@m0>OvWOKTVxsp_pSpn3rXtgw=Z4pwzIaae} zTtAfJ1rBFxyY6~LAtWkhS|agtUoX2XALS|qN8IGq_W!anb~DuEXbSZivS@A zr)@f~*K|SGVabq2nIa8tbs(>t(lkOjhC3%pAJVwwk4fbO?R91f%lYutoD?vR z=li3a;wyCIm(JyPWj@u8E!C247OH#DFL}PKK`S&w$U+N3 zWcih8IyBM2MXnOpPU5b-fA>Vr`heT1 zDXSE^Uf7Hc!hkdo4DqcX`R+k>(r>JfxMi)engIzZ$LzKAJS-W?2%*h3jO`hkK5K=J zPSOipYiNZ%k%ls)td7?P0W)q!Lc3^AH7Hfv#R7D~)=qXRr_iS=t*opOZ4ACk01C6_ z79%BcQO5A*aYj8y040kylLu`Gu$Fh8Nfn|pLKkkSz z8|YmzVMC-(+Aq?d3bj7*G>cB?;tn4b=vaQ?QTUm{U87J)P^G?*1xR|lRbnZ`4Wxm# zhR*SsQ1wdum_sBbT|uVlm=>6mW*7y%2rnF&P)Nt~MS<@Y!Arys)5O0&8NwRVBTt?5@nwA2+vPhbl&?vMP%qF;52?CN(qiS3M zJzaQyz271!p25$y+R260My1gRq@6HZXC^js*0^l}8}XF{qj#>j7n-VW8Cr}0)F8G7!V~SH~`&@7|hU$)9q*#qE;9Esz04`=|i&unMunA^dNP*WTyQ4q` z8>fu=Tbe05rbncYijOc}t>Z;g1g#h*$?sj!-L^FMR*j9+Iv1gd!ItGdqQq(idMFyP z$HSDqJxbfkW&b%@I%F%cx%rlCSqFk!f^wmy+y?ZIDjbquIphQjP<$k&C90~8AS%&*y} z79^kxmgcu-IM2R9pvd-eP3!Gb?rB(|A9~7GV+=JGb&<46u|i-7Ui&cJuaY@1VvZPp zKIJ(p*UElwVPo%Yu2yNJY27`QsXmFIDu zEC+ZH2HBCKk_2fi=ynf{RZ#xs7Mn*d{jGY_oH*qtKN|X`FOo$G&6x2AJVFNGQjpOCc7!K|`v zqfz^{`8p@_apXd8`kGFbY%t`J-d%~<0t(4{OC-3vl`ukzT*?_@M9A6oF0jiLCxX8% z2@E|MTktbh^fqRStRGB@SP70e`E1hWl@|s2$irY5UPr0g=T@BL;8EWnT$9}2m(3W? zdi6~#b?2U(C~U_%9k?3NUT-LsY4!)X1)sb11qHqT4i6Gu)7-NDRG-e8FtI$r&R>bO z6D)+EoC0E_>xxGI#w&~cg_BE7L11kX8OLI_>Em%FOGy0??yq)!e$&F@%c2MDm}g!+ z5@NX&U1U^_J6$8-FDy0;`azEZQzVw#tE>(gF=5ZbEf~x{A+wZ_=ZIz(vZv+uM9Q#?kJF&)NX zdC`WG&mrE(ZDKj*5TC)cny-Lab`5&I3abULex&}vR3LPYN$&h6dWr(%AQ)|J11wrQNeTxf_$0Z2IX+N4D&p3 z1s4N;GAfj@IVm*5oVA}=%P552A&3rpM|?wJ(0j5$Z0tzgPfq@l3hvI7^cbUY8;6c? z&JmiGij$zhcmLB-@y%Z>jmq-Rx9U$lhKc;OOfqCi4kUM?Svn!V;>eg zuk%UYA9t~~4DfbmZuVR`xPrz^)0(ElbdyG5?_4s%U|td$Uqf)1c&q7ZOXm9LYHTGm z*}p^)=vtX7H9|XNP%^=C4?ryw^?hFY66MG?Ihfl5SsXy2>oLdtvDjZyBt=;rc?mk$ z#1t)4@`D)~WP5SB4=P~=x%&usCt<~qfy_IL+N&V832-EepD$)p*>%o^oDwKT9`8y; zY|`rOyspojwjvJooJ#+o$J=n`1F!uy(NIkyv?V6lWhk<@E=kGGHt*G=Bo z*~rsjpA8XDzqNeHISVfc%@eq@wG4?_Hj?P&F&<2eno+?;_gdppsLjKs?EMF`(XQO~ z1ek(o%AXL?R@_2KX(KPD5(-cHrE(wo;6Ml?^$+5}%oX7%am} zJv=uHHTk36#^?>gc~<1NYt#3sNTy?suT3t27+Yb2GH{TaxGF&d*9m;81i{N@?#ok$ zQyy+sczdF5W_gJbQNL%I*$2Ar>hKu>TAM*Mrj3hwEI?`$XmX&rP8T@kA#r@p;Cyn~ z0k2Gz_>*55)ED=>`B~+?kZ+9CWRuR;NSAs*1B1)Y_W3_XOBDHW>clLX7WTgv8GLWv zpARHYQk6e$y)Lk@uFci|s-=VLK%lY0AQH$w{t_MXwx~wi? zXqlPuINPJ;P0mb54bc}6FNVGUN$w|W%YPzQ0?DH5FaRhVG~*5<<_TsmMmGhOG0YJ@b zd2B(dePm6biy~qQ$b%QkPQmv*$(rp>6W4Qxf*P2qN&+y^iBtLaGv1WrSUGURM1Iw- zZhUO-ou2X}JYx=z|J6a}JV4^pfDk!^@+}zk(q^@zKU`ubU*&5$&Ty`>{p|pkbv_B)hDE1qA6gne>WL}vnw%;g%9e(qYjao&Ag)ds7t2bJh0%zYX*4dTGj4p+?-}jV>qtgTqJ(P3paBQ6@nL(NUIx zOe`#u`vVJC`#}m}jam`FmBPGfaJWPFR)8(=>f1;>-x2A$$MVHXTADKA@)c9lFaHcW z^!=G;G$obMEMhNdat{*n0@tRi;ZS}67w2JuwXK)RCt^iGt|uwI=<2L5+5y)NAx|x7 zVaHz(NGvj(&)~=)3KCpF%Q!J_({c$PB5SBSKyj1S@W|B&l%^%zD#Qf|F+~Zu!Nin( z1{d?~D=wpUc#nd*f?r&_j~{!wP-UkjRottl!g&NFz(fvqdw_J^@$O|(q}VZ)svX9y zC6`EI@lO-#oIeUC_2Hn_p?!kUwxXMj;2=EkR;BbgDh~uBzwN7_SEguw z7As@)=!1V4(zM}h1!rVpRYUfr6M_%%Y*TLP_shc}zmWWFS9h^+@dd#=tFo9Rhkwm6WC@OE&T*ZFWUB)JadJ6oMADV9A7T0w_G(IFPph}zUG!c zWAiFcX-1Q@y2v<_;(d&Z@@&x^w>b zW{PgHmGi&1k2QD@DrCXLe@q~XmRw`KKL{=lIBSVc*%3`lT0)0e47ypi&d+HeP5~^C zu!*lI7fXJ2)IaKK?=&t~u$CXR7-GB2$jz+>Tlq9HnMI=Ko_2F5tBvbKi7r8N-O~*o zm)wVgWLk9mo2hMQPE;8J%Y7;hYR5=*4nc0auP1Dx_%SKL5HNO|ZygKs0c*z$H)0Uo z4p+9*A7ea|HKSA|AvU%fCriUy#RyeS#A4CQvG=soxC_bJm@A5?n_+PCT8>tO?xt~J zi;Ybk#(GQI6RTKznw9}~Th)zzus2?_%q@jVwVqm^TdvJJ+x29H`KTLfor-;32b#wg z2PHfHBUI4|^hLZX zDL4uTgzoYgj^X=#8K>V+k|%(6xfhit)nkz{bj_c~&n4h1=g{c1(o@Z^M!7S;e{+Cc zwszh-?UiZ#mXu!5E38k6fH(`gmRt0b%byr>;J0->ww354ygDN*sR3<|j+4^wlF73TTJBX~#SdgaFnYBO0^d zWTq&W>Lg^>H8H>b_~!Lq)M6#ggU?ggXUnP=C+%ET8mrmbd@T2c|Ebhh~ zp~E$?TOkO@z(={uR@eoqQWtps?${8nGbbYvhU+k?c%~#6dtT2UZiZYcB_}O_hO`rv z1%H-1|2it07wm&{Kx!2tX?XCHkXoG2ucr4?+l8E|dFK_CBOrFUnQt@A#401@c{|>X zOFg+xHsoUB?XWPwlZ=e5>1*<~aLH>Y;jhd%;Wms$x~xIBH7m)qeV9d*HA5&X>#35R9ly zUy81TV5@*Nb)Gqc6f2+m7abwFb#st1Rh~t=2}UiJvXd_VLBKXT0}%0w?QJ?L`GER>iPRjO(MWr>^ZIsmT2-}=@@t>5p`Mak z?mX+Zwj?AlGC_3^_D3Chi2>!HE!(Athru zsZUe-40B zZa0^W+byHUMg%_*1gN7P_Q14jt+jDYxI6;Ptu^I&z`f+Gy2Ynl@m^j{6K5NF`_F-Z z>JP5n#mt?-@2xiYHt#Jp+KLMNW=;;wCgx727R=rb&hITZ0Dz!~x3h_vorOD@sfCq| zqY%YKM-K&=jkyqo7MCKcqO*jBwT-N=tA)C+l7^YDof)4wg@`bspf~?JfP;m*37NNp zy`vkyw-CiYxcu+ee}`Ep$o>&=w-ch!R#YLAaB{UE<7DP!W@VD{w((@A5Jn^ubTzl+ zSCf?fH^loVAqs1EcV~VU7B4R^W-ktACs!*LHag3@rL_zUBPxc@FIXEjS{wKVn+rL?O=Yz%D#F>SS znU%%Cf#ttyxVcMtzJvVRq5rFfo5p)*5{sIJo0EsDnT3?6g`+#=e}ynN`%ism4_Ett z!Z9~vv9Pyrco%hhx61b4Ov(TiRsK`sF9lXM4$l8*y_5alB;9Q+{|{OJ&9}cZ|Ag~j z9eG#(Pu%||{U5RaBm6FKTaI=}SGqIbR zu`zM7nOHEH^0JyRnVa*mo3NU3aG6lqnX8DsCRH?n|B;uZXQ++b~A1! zRt`=cCQc4sOC~-J6Eh|gQ*$0R4sJd(9uv0z0-@|`^PZI^_W#wZzfk7yP$s5aylmX> zK5+4HzI(%E#>vEM!NbkOX3oaS$6;y8YRYZ;50tqXzqFI9gUS1H+Ble4S+F=eTKzNe zmvDYD6`&9WJ2UJ58Bwt}akqR|c;5pyj^<8YZvSUWJ*LzYD|v9?m_h|SfTrEu8om@4X zoa}`t{z8!b)%+*H$prtkEV4Fk?-D+LbN=61uWsS;uTTHl1ok%nOp%fOleqjQX8$tc zX5wjK{*R$|y?;%aS(`XoS-kJ?f2Y)cjNANQmdlcpot>S_%#6v>gvatP#~e(ieC8HR z9DL^NW}KELylk92{~g`U$OIo;Xx>xkpJ>SF{!Qk`|1R%kZSj{WR(1|1 zRyHPf9t}1weoh{K4h{;Ie+SF*cTfLEw1O=EFFpkS5%@2Mz`NeR#@-K?_p^%SKM$*a z^Yxd){~y2py$=6BT6l;4UnBn`e*c%Q|E24H#K8YZ_)CpM0@{F)+|E zSe3%V!BBOnH>NC%E(cB9k%=D4%7-Vg1501-;_oxjcDygfa{VT{?sMGoHxArHf_Ky0 z)~|(o?vIqgTjgw2To4VxYal<+umt=|H#@4#Yq*GP7Wt7~QkrbL!gO%bvcHFIJgyO> zOzadYF+>hP0jd;A5$p<#4@cjX--X^B*%%dp#Ya1Z$U(I?duv4SCe%LbyLcZjyL5sf zMm=?Ce#;XD_<~MARiM;vwRZzyK{>_u@{ifYC`rCIi0A1yG{Et0wQmr-zo{R_!J1%m zV?ARx1b zM2qL+hgXN*N)xu(cj0WV|DZ=j!g!aQ>* z?jL%vK$H@F8xT3r!~g|=AzaK*BHpQ6awJd`HG-0|Z-eouDanyrIdFp2q}x=R;ix02 z`3}c}_q*ANasuau>2tQm2VbferRnqTN>mw7c;lpE4yK{Eq#wgGnh#k1pVr_uM?PP+ zK-9wgg0W-=I~DK^@W=%aAK^UPh%!vgyqzP4lNMe((bv;Yasg-oGGD@j z+%}O-4?aM^Y0*8S69q9s2OgC5^d}l_ky0LM0MRmM9E*~A9W?27;o{z-OF<06VyieX z7-(Ei3%eWk75IY}j{bb(1?gOTwM*-HAVlE(sqd+fNZZj8)Qcm3Y|p_Ayk;oU3_UBpH8yCHXk) z0Fo`|@TH}}y)o&dygjpeUNO361Vl@TbK0(CXDt^JA2 zklszjE1wKjKOUizK5o%~C=L30ur7rQmOuGSKuv_9cb;#&yN0=yub^<_BUE3d+;j=) zEL1nEwe~)Yy5&U8eJL1s2&hi83S_#J-#kX|(>3yWci|V7fABYrU z;_-(71n+b(0a$0iFtFy+b1oM+7TiS+{pY&?1eJ>~43WUx_L}(*-5yla&Nq1J1#km# zBUbVk&zsZrtMj0Lu7o$3Fp*)LsrCNy+Yti2S<@z|uTWpQYfOvFis~x?p=+ zAY1VtLTO)lA*h0tHoFE;xk8mw=D<4x+6_glKFxq9hy%dN>?SP_L=Pa4MEvkK?IyI! z5$rh`cZnb(KF4l;3t%4$ONSUnBgi!Bf=`08%=Rl@D@5yTBOn4xbg=Sm3v5#ggWPnT zPwAO%lh~N4obdu@50Rsov!sQj_l%(aLh3vxl5Gu4yo3{2+kmh)3d>nOH5bTOv^!@9 zDr2lDqBQ0pl83E&=GA-H65ZI+eBO8j6-8BalW?`BG=VL^vE*Tj%yLSAR8zuBrA;to z<=f`HzxCu%GilvGV~AZ8Q#n7CkX`dpX(uldQ|X4d`!NaPDdJeO`dcj+3Va0C)aZel zklqYBYZ@A)>wy2bw0bsRo~n%5F%yT&f`z2QjoC2y4K5G%9FX_@$sU2+ex&@eC3Bu5 zIb-}D;dQ-t!H)X@yE6M2slIk2|BUebM?>=-v`Y5)nko0mAMk6cKTiFvdvqoo3q%iG zpuIR7OlvqkQ2U+AU+55HP~C{9W>hU6xp}!DnP3)xh*xb-HUPNT;1(5E)rCdY5KMSq zPeVlub$u#K3)KQO0pO8q?+vR+*{bVxRP=*qJ6%LR1*{_8$hAk{$1cKmYW$qiOjGkK$JkBb zzepC|6n@WiHawzx2r83%Qoyr-3-%}7?LPtFWH3Kvs9!9rCPu^BITxvTiw)k|94o;X z=Q7p<|vQK!e~Q2naqk|kB}orJooA1$M7w90%WKEko(?R;a()9;JS_)b7*q`@O^C{MM7ZGm4&Rs33{4-!*1wi4i$CM0-rsRxTlb@k}jW^PJsY}A<) zge7X*7;FLF1NUSA>$0@iFNsID;|Dx#g}Sl-NSA5|x}mQdzXsDr!f>o~RA$k&M|~V; zentU}ReC4U5JKcAou=~qBDLE4DR-Lv;IgU70&qnk8(#+hObSGPj$M4Z7$4yGmHFch z`S79Mcr<(if$Wy5Zd4(p3=|Pb5a#w=pB6qhApF)-e;D1b2vW#|PL341Syz0WiAT5nt*T+@x5ZcirAiJbgX7Qt>Lc^oS6c9DZxJ)T=$C}duBAN(4E-6B;( zYRbnKPZ+?Fn@gD*=3{gM;VIXfgES$4ee=wxok29Y%j4kv~rlf)PNlWFffQ z39()nPiJ1-mu^N7`q28j9IrV5Ycfo0ntg;E_#AQj*(K{ks6gw=9v*!gXHJdeSak9H z!8R5e-)<*Z!kWEX5Rh|*g64fG2`Ay>lD+P!_K}Hg#HZ*MMeUn^lTSR_*>l?C7{Gs( zpJp1sU4y!X=CFyO>je*()fT#=6x5rjhA5dnUOq*l_I1*t-eN`M7((iw>w=yDgy|4w z4T-5>r>Jr1K+&9|h5YM-Zpur3(|eXnrRaXa@)LFwvdIg@0-XED&ik;Ny<&3Fwg5H6Pa^afIZB&4eZa_kuGm33qGW zHbou4w2`U7hI~t8&KFKL%iwnOz9#TR6js2sH~JVEzlf0;Pz{w9Figf|jO!d(@zY0A zz!}@+;fHDx&4c=Ttt7d%0m&PS&qJEMqep^vieevqujmZv3@UaR&<)12&cWPPG7h1I z=Y!yEzvO|q4i2LE{DZL9h98y}$=fMzoJ0l}UR152xISq}_PFs|Pt)TWz+UE*!d(=q zVxadKA_DTMU4r!uO`xW41M*ayamhdexql6jkw){<18XI)7N#9vV+~jQu(@%a4J>Vk z&A+^nZ%w;3Y%@lcX}IaRDQ;{6-)jZ4kIwWF=XD&U1u?u_g|ygrdRa1{)WZTA0Ocj83!wn5x#Yz~#l2w;Q)n!XKKVkm#)49gG!(A7Z7>RM#3|MGZ@4$`WH=cC-Fp|RC=LV{nE&}B1h z9+lcqp&S+A*pxB5asB-0bKxG9lYOCCKaZSjQ z*&V=wVYR(x5p<3!C{N7S%^>We4R!--ftn>FS3ulS!11*-is*yoNvR4dJkSZsZeBlJ#uawy%`E6lPad=R5?jtRFu3MbD zP1H`*FR-C8hI0{1v>gD55VV~@UMun@lA@j$Sm;a^a_7gJG}@#Or^>qnkJrL1_o@fihsO^`|e z3xqe(tP2~?egE!iJ4ioBQqJq~EofF-1wb6F(#dW6wY@1Uo7;4LJ{Guq3XYMsbdUc_ zttuKD5ld`_f_+Lmn3Mm#{uiVR9 zAQTW|T2rLuDv|yAwt>kTj!(Tfq>w627IJGz7$6*Wxtegh)Yn+t@cst-=UAVxsqbY6 zNQyj67>|J#TYyP(vi^joN?HhuBxkP=KHVRylFWJyN*CILQ)a(Upa z9){f{+!O&l`Hz%iiK>N7!j7?TfK|aRr5L;8C#%Y*_U;gu$y?APBQB+XVB*NR6@j7L zb)){cq{!rqE>k~X)tF>8{a!&1&k>O|JxWdLb(z{Z-+fRUL*_~!-z@9AQLMV6T;88i;}RPZ#LJmYr5_+aK(3>$s#xA{TblCU zx=5BCDb2DlvzWRr1VN;Sq0hHQwjm@m>P#vl3!%V+Jv!O@{DS`=c~S|Ob!IbQXbV`7 zrrw}ze%uy~4>*?m9E{(~N8GABjY1Bs{M!|!;8<6TOcPs)a%gPB$vhz(xhWRx6^y2} z>@=Ye(iO=EH4LdYca+Bs*nsUO7S}#D-6s@nYJ$Yj3#zRf$jn1d)Dx+%@xeT)Q=N7I zbAZkE8J3z*?WV*%1K3f*^~YgTIKvn9oF|aivQN2$$uSQpo4I?4AMiF?2CRWK<5`r! z4OEKvL*#L~@If8L>Qq9N_Fs*r-I9bqRtRlz*(7o*Pg5tQ$sN~R1$0pzWh+5`gV4}> z&^w(K$v%Xc$RaIG#$R6@Ps&eXkM)gegUpBPmdr_Ntp9x9#8-dN4_OcZf~MjE5?Zm) z!qS4igo&UJhv(Xer%1(+m)_+`+!WFtlOm9J0{oF4urJ{@_XyWlkPFp4Sqhf=TJ8#WR_E(sC|Zr!xl1wHqQp)6joFN;7o|>(~8C;eVc8d zX)#ZP?jX7;ZR%|p-~a7@*Ep>9!f~WVrCigQMw-JPAP_%HZp_ejyNt9^ysilRHK1)A zw5_s$hJM1Qd$!aS%lrC5VQQNGUQ0~rKn$QtyjTQw_vn^65}0=~)Pt=KY;Tu2l8u=z zB{&cQrIEjCt{}f}ffoX4UZ^Y?f%>J2mv?$XI1Stm9|7uN!H zKUUNZ@ZPcmNgJPo$!rRX7i1r6iqHA?VeC_G{D2Wu1elB_B*VaEnQ4n8O&#K%Wo?$D zs-))Mxu@mk*WAEmwP}K+^Mpp5u-F9)qJW`<3!7y3)?#W8B~;(uk&2?BlSM`wKVW{+ zjY?{ib8>MN_1S(K;8ucpyH=CDHD^&{N6_@~#}H7F<(+Q-O;GnQEkATuk3VGl5T{*V|B5Y zj;>=+E*HoU*i*;3u2SY^I0ai%f_d&`O{H{1Q#EtgYy!BFkN7}oxAW}M1f zb^0s`SN@R)n2gx@Hg|UsAP1+p*rZfTiNFb57N6!z`j9Z23=XJCyFmfYaC8r6QIj}4 z4Z#%^-;g(Da-6v(-KDx07n2}$I>`V<5*x`a!o{Pf>w3q;9yEaqS)q0M?(*$Q(|LT0 zdbLS688$IxKKyR&r1OiWW#z>cN!{icVXr9|U=q(Cjb+9t+0gCrcFro6QKNf?GWi~!edFygAkkg?~61@UfF4H;!OWKGpa7-=?06Ov4H(z0mW&VvF@7B1oisU z=}cm@9!0BE9&-G^=~yX#R3WM(ZQu@jtB9p6zw9(=v|u(Z_1RfcibIoQUUBq~NRL|1 zY|rZArKCW6o6b4wn%fuzRXw={mz-iKDg#II1qcd#hNGDzevVciOS#8rDIr9R6++;J z`$dqjtnruP1}Y)_LNmME+of#FjVW91t6B#$s$BU=rX%GfRK=>6CCrHCi~S)FsZT=XsC|m=FTWPjtZo?IjCBBVxKRbHY4o4d zCx8``_RDk%s1y_2D=pBON2U>MXvPCQ(UH?{X3Zv+9NuygMU#tlG{4?yK+M(t-7&?M%#D8k2NHNona|ypn)s zqJ%IvMn(cKX))lY*U!|FjU0?~ibKca4Cs##e}KLNW0!6p zSdRQc=i5@OTw;qyzd`Kk5iOD()ehjlkw)!Rp`|Uhd2%_!)ZecG!MGap0X*m(Hwrus z#Vh$L6n#BN^*GB>q4PdejO1~yK~q|FY}`QB?B=$Z38i%R=HhHDI!@oEFIn8zWl1A$ zxy(ls{fhG6?@@$af*5T=Y72^HW4TqQN0UC(*7^OKpgucxIrx%4e22T=QJ0bIIq8SA zh3jOpk6#1JCNUk9c7%19?%s3Da1+&$Dil3V{Q^$%n@9HgjYkf$NLt@i6eTWN^Hxl1 zZj$?6CY?!H_oMP+Ie?nM7j`_Ytu2tMmqi>9P|D?fjV7~{1-495s;ln4rM4tCv_?^W znf(}awo=13_FXN!2LMr>e#2~}D1*5SCaRIrxqy!n=}F>TID7pv?-$dg>DR5%%To9% z!ek@1K0n+K;`&TYR6~XOO6OS_&0D?ey6JM_drBv92B!oe4390hB4mPXv>?`(oJ|tj z!B(atdofC(3wG1gXlSd|lsf6`D29_My2Impd7?7N8BDU2@#V29n?qjZ93Y*f{`;W$ z_EV*G0IklDVM(9JWVVZ)(I=VdZ1MW>OsKF1()Q#}V?f`;O4?V`-*m zDfog7UkRnFwcU|I(XocYqTVPeBv;?s9j2J&?1VKIptAN+(~~)ICRU^p5-qA{7*p!a zW6tu6RVvm6sq}-E-U%08pbQPnb_nF&5Sp&))NZG}J%<}=y@!tA64z*!9GrsNVv*hyy$C0hWXiHc1L01f}S{b|f}Ly#QDMj3ZW3 zE&&b%{vk;xgfLRq=0f-P6!6v%!o9wm99a|o4oR1lF`Ts9;^xb37|Q-O)o0r@B*0iB zls}mUQ__%?GEe@s*1Xlr;~kk3{#HrnmpIcg zcr??MO8Cyt6)E-oj;CNdvMBt)nuU(&2rH8mUNWD;t&)Bt>9>;hu^EW%_G+rqeVtTd z+cb^hdke+;?~*j#catNF1hBoN+eYIEt58eWV1+gh+N7i(OFGh`3r$IA2qygdOM0xx zHOwvZdkbNyq}O`L&yhvpA4q^n`cxb(R@tcR^KFdUX@lzzMyny%sP={BdiO_hkDDZY z+$;SYStWo@v(txbF@AhP!cyw=;y&xQf3*qM?vg%W{rH z?<7Z-NuWd07mF3aR4O?38*?idX!?Q)^Hq|*AZb5IXGq$R;q~SJ`^G-S5Z^2?P_Q zpO54@uflJas~vP(ul)z@&s(Z-y+ulVeZ-n0mqf6+q>q_*yJjSifOb%|Yb}uYbe<>t zIPy#atrpt4-PG=ylE9E_Ili94Uxbuyb?2X>5r|+1OAlUO4{e|lEuqT4cePjhIU0oo zS|q(s(zCqXi(-L(W zf(VM-qMHDYh9!bFOVxcO?<7#<)|N?ngXaJ^8kq!|CGBDP_Pt4_5vN@EH(ChLa{wF; zt0nkSu^D<<)&`0s9{t2?{vD2gGzs1!>EU|k3{?5_zhBZW{=^-Q!a}o$q$`V!)yfhP z6lrR5ls{#MV|c;Y+)8*?W|ahr*!`dLaqA98X#&)2m2!7v(IhA`-Pgx^IUJ)Dq@5*Q znl+PP$mKmIX%Byz4o7*yG+xrVR>PkO5-5=HGtHl*!%@CKwMcrEHSkEiNl>8n|4W|q zGdz6uxuYmT2)!YM+kumT?*K0kCwOVQe4quG@jkZoi3_38`|zyJw&7hs1YK|yPkbgZN$g&c$_ zBY}Y`K=*k$90{8QyGptyo5JErjB(N-H*EKZs7l6HhD>{y<188 zeabTfi_InYnhytY^=9Pn3Qf8bHzDqtRP9WW6%47d`Q3OoV4 z0PF+Y4XgvYf#wiGuk{Q3j(zU$H`G5m;8H;VL;ni61~?^zu+|B{ktcQkb-<5IkcUQj z`t9!oY$E2$uLs)f)djSgu>TF%2Dl$s0{j7306Y(z4Q!tU;U9(smIG%2zX&17caI}4 zYX3b|n99XS19!}qg}s3 z_Wm5;Q;pO;$O$0ZByby}FTn51#Ovm9CT35>M@pF;Qh_kjrgWw6^IFwK);PlKg4&EBkM#kPSS@ZJ!#>>m+Gwz z6!_V0NuL_2Q;(6_e@S{d$$KdBHR~k3$#;+=4SW1Ia<=4#Y}@b5J11AUzFOg z)BGK$N_wKoB+x7AR!N7uDd5P9k6@qy$V^GUw85VnYE1$|er~O#6GoGUYxgim(sd)4 zH=nDJ1o|ZXNYWO*gB*Dygw3o&?w2hKu3iKnn>5r?#`kP(S%j;S(gq5Ae1oJ@^STdQ zwP{d<5Y`YAiQ0&bLF$Q>l+m0fgzy4z0q`EXm;PcxUBR_A16xXJ_g&-2lLpWv>8+Ah z*4YG*+4IMiTa9NX!E8xC9d2f!q{S6z0hdVfNyCoZ5Ws9n4-BU=*EkRV7m~KC@Ovjp z`n04MDlrL)eC%aOCt1(M0Ro-i4njv3g}>7TuAp$#B8#xVS9VEyXQcvVIRi&nq$6n( zc)+p~gCz=GW!F5$caM{& z7-5a>leDAnDo55xpk2~glAbN1-VdbcP8_0Ae{ab9lO}+5lHOeno7&Qd&$O^(e*z@1 zQqp0*n;cmsfhI|ZO8R4gc>;GynlQzU)CY7vGt35sY(Kb17a zcaS45L@?G8UG|mq>cM^StZv})LQ{uEIw{K}0GX-LTIG9gk@OBpi%MynG9(hEX>QkWs>p-Qp_OCf4Iu&CJslM2w?_z~U59qI#G(EQl5YBK)cL$R-197J1Tr{TYKf%z zz9Srs=ojde^zo6BXe2F^w1wH#Z?Saa{tB}0`Xqf{QfD3Rdy?f0EHiE7^rTXh9gZv# zL6fP+cNK~Tm}h+6kk9NT>37AnhZ0Gkr(Slqk>5XADzpo~!?giZ?|(2vs2?a+%4-_I z6{R``iwy0on*iE9d!?VlF-RyTn1}v(v-Rf`o2O^_1^-<{Ybb>P{wC>F9(Hp$(nSKz zlJ>XK;xNK{r%U?oa3j9#eRGRcBfiB6z~RU?VT?DeVbv&e3KrEP65wz+k|%+7OY!Y3 zF{faaSKB!p`67W%D?C~?g79y$oPri#HHRZ#B+zDUz#kii0Iv2n+zv;6Nnk5Uw+vHl zx0|n$!;xPC7%SV^KNj5;_fbi;8LVmkrKQVhX4t#!6`1KI23oMSb^g1P@uTG6)jd;qy^fW z_V?<#_shHP{ohY-R?b@c%$|8>=Gk+0&g{I_R9C{orosjQ0C+0O3fcew(v#m8COSeg zo={Q&0FX}k>gu~|L%bMV+?-*y4p0Vn9~USC)Y}#Y0C=xdXWDyG*TseXf|J&xc0EZL z+y0up+{+Uvt@+-P)z!(u>~Y-b~snl+RyG^QfGm;TvSnGg^dl`l4h-YWRJUb zf8A2uSYLeq*EoLrQW+ib0Xd#OA@uIl5lnT3}d;xYvD^uORU`o*Z@ z_aCP#Q-{ule^d>?mlY;sPVrgdgSzOw@9s|LK4kBxGx`J!CDYfdUCWeA*l$^@_A1^9 zZ_4k{jf!66>I$I2R&qssdFMmE^NYw>d>dKZbu6pwoLG=>w_=f#C}@l zU;g}&w9vdO8z7Op;Y@4>(+yk`56GV8DCnyCF0VIfaFy+^;n{l7mpKO4a(>p@?YdKI zvp$BsIU2a_)wvmae%zk-#9CaY{B18iP{Hk`96t9qm%gqVLABEK-MCTv7{OA%s-$Q# zYu``V&gANIb2F->R%*4#0PUA~&suIAZ#>^9@^&Lz5A~P_b8mF756g6YeHeRkpg$hA z_1Ui~{Y3+p6;-PE-H5?c>H|t0fn%r0e!qN~00RqR%4F*OQRH^|c*ck@`X$v=>&k%b z$9GB|7$%cQn1nZEY{W#A7pyE;tlqI&@(gh)_wF!tq#?pJTv--g7$saa{F5fyT9&=g z8oX#=7_#(60aLiVxU@w5y&Kt9QL6-Kj#Y&$k%{Yh=C`D%=lYz6F%j)-<6MTijdru9 zI^)mn$_?!u8=IGsLQ6}u;;Wk$d=E-<5+A?tnmF_yNPJ2sFgm5BMD359mr?dnz++(< zX6DM;%<<`Y>btg;n--{kJ>!q-1 z)Hu9S9sBcZ<=t_39)ZyHMcJ`E+c zy==y#69zCRbb;N2RqF2Jip=AQ#>T*oIse_2G@UikcmAz^hP!9fi(SUYUG)3IoM(5C zfWL;WHxD2gUXM+Dc%E%h|C(Aehk0VSw1J^|wsol@#v5^+^1HBQ>Ts#MZ`h}CIg>S4 z`;v-Hm*t8MJ?V}lkp@$F2Ln#c53YT8tC0(S$S*((FuQlZ(nj-wpN6jLmt`ce_=Z$r z6%4gbCi$yon~ti(8$?zzJ9(*I+oW4JN9f6EjDAp;BYX2X{>9uZ7TM(5O1Ve26l}yg zdg-?9N15~EjbHo|cidGg9+@Bb{d7padonBqSveHGq5Bfh_tnw5OnPFADC^DPWYN$0 z$~!TNr^;{rQuzt#gt4cVvCv*nbDwL!#L=OgF!Y(TXEFTf z$Twq#Uq?k56DEjKZzLQqFvg|5Yqy4SHBHr4P)s&Nfx%4n^p4ZxxGSb74{#VDyY={| z^v-9}&~7Bo`CrdbSJtgdPo_wpx9zf0=uuIz$_qu{Gs@YYypanYOe(W1a?fXU5nGAd zMeQp?C05ru(!rl0&{(6^TF;K1TB$E>*qjAI6rldJA6sG%3P#!SKKl3JqYBR?*H@p{wYp9>JQV3(7^~7*`FzY-be6yEb=L#FedkC&&a#)n;s%?&X zM7H6WU^nn0#_#|iMO@{%V~sfBXh78L=A(mDtNFnY>t`d$Kt(-AN)l(9=A$)Y>5fzB zj$hPVKktUNe$G*DJgsCQ7)ZeGRv*WPrp@Xcy^fwAMaaGVYK*$9{?SxmFHr6w&|F z_UIx+mfMw<&GgL%2JjvY-eacpGL|C6*duokjSI(Xw*jkE(g)lgBosI0w^syFdTgx%M@n9)RQUrc6pyU#C&JQUY^~X{ zeoXzwviG$i-OrWuKAo9W-q$mE{W9FkBW*1jwGC{YRWC9yGGIY{>*xEV9$F3+-oW>I zyxous~xe6H$EDrB@5;hAUy0+$0%{COs~P z4C_sb`8!=6+6nTOW#{+bLa)CYTj_#HA|$k_wy_mfy`HRJ zw*@|Ol8<(ddY4^rT?3rMba>kGwt&QQx|EFsr>d0u_@Rs7zq$x`vmU=k`{yDEWSKO3T>j}+~Ev; z!+N(j>y1+R4+7JX2bHldZ&q3UX?A}01`Oj@3McaPl}4iH{T6e!&&6wXtK3m?bzDZT zKMh^lVmGoWa6R<{lFTc}y{n>(9`UQsWKpb?(6wPSh2`v&qT;lvzvVC3hsle~OteO7 z>U^oQY$T?Me2)vmcG!PHP8&)-J)b{K`S65jh9vHaF|Q}(Im=Fx12SBJ4lQ1!K+l$u zDC~P*I5b!&>UvGvJdUQ!086y`%~+kl!A2W%-7oW+L^b_%+HBn8s81p+64g>O07wTC z#OTY+g_JH5_g+Zo>J#sRX8vT*u;ztte>9rcg>=a)b5*%-49))Zhx(4`&K>K8nTL-b zVgZtE-&Fwo%z>n-7zx4*-u=`_6v2e)-~~+3)Otdihq8;FDt8`cPJ6KVX#`30mlFen zTq*A3XFB$~ou8LE={3xGZpOBnJ0nWtucO8~PM@2#osX^V>wk9lj5w*KnHrxC? zA5Z_2Q1PY=V%ZF&i)<3d=uy{ZXJJJmLgyVUN8~QxJT}@}qwLwUPgw8j50bu7Cp6d5 zh}|-IB@32uUp(av2dRBEaev&1_Z|C!A{UxwU9|o%n^#AVZn=xn2^HFBq&azvM509+yPYdIZeU;n zV$1|WYxEyBerjXY9}`FOfEo9Mg-#D7niW`L$eN&tWg2;>@TFlZmghF~x`gGIE4F^I z!GO)ZJ~Pw)u&Ybo1I2G4Uo4*S`6+uMJGT$trIBw=-mWjD6+F;rAiwBD&Dp?O^Zy|& zD3>mIumCy&Q0Ns+GRL!3*Y^{+I~F&7gb)xoqz%x$+bL}t$2DNZst@;Gbbad-8D*-& zsP>U~2JqhQ8WU4ili)0pH}>1C`vNw%=YUM3#z_o;W50?sz9xl~3ixgDnvRh^ae_q% zBjZ&M31bG=dclpFGfJQoTI#TO!g9!{`fA58SpxkdJ7dfkvOeOG;c?&hr!m!UJ41s? zXqV|G0TfwuPNQ|fcb-#73)QD=D~T~>4GvjIBtB0^OAVfFC8N{DAL)|=pJ1X+fj6Jv z3BJgL)NZpv+~+hx)w>ISS2U%8#x@Zw?w0#=@SqM;kqL_OwgzME^`U+7mU4TcD1L4b$$tKxrX*WO$xVA zi{r>#c*#CfEC*6vlf5}v6s1hbgK$wlDY(t2t}f}S<`sjGsQixUAE3(iL zOY^KeZ?!da=biB<^?8r>FH^E5Gm&hAy|?K)#?!nT4B8meGzXw(AFH~%@v0Aphkdtp|e zd52Rx8vXY?bhJBr_Q?SgfWxz=O-kt(Qo?lny=X2)@Zc_~g$6tTwlta_QItqwx~3~3 zV2H!fjCc!xE!ED8IkhN040VdH&NoC))?lw#APG1sio%Z!auZ6yoIk_lK2@1ca8)zE z0cL$!`pM|NDCZL7;L(D`-YYS4Wn#s%oo>C!B&Qw|@0 z@<;9wVSO#VJ;^$o5gtX@6qhwiw;XQEAt_0uQ)pJS!$ia4-)qE16G?qtnXg7#!+!lC zH8p4M({@V3y`W0vi&1CvB1%HWZ)n>1_!pPY!E|AbLMg)xt^! z17^T6VRTlm7l)eU=|u)r8Ms1S^4KvI@wD+F)=};&6f1J$Ff}2z#v+a_B|ucF{;w^r zwQi@Nz@}fPKRrqAw{o;^+b-HXYIRR0 zDmU;f`wlBU$D}4k#3R!! z_XWqDjRwWEAF)%tl;J?HN5Y)dBfjfTS|X}^9NFPG$7CEaBKzUDeIcz9qaVCJ|7y~} z8q~u!IUz))YSq7^Zb-;spu4l8Jgj{%e@W(4p7CB~?2{Dl>WS=HwD?^r?g$-n!vnt% zS2o3keTc}g)~Lnn>!8r2Dg|w|QVYfby_j#@cnX$YiH+B_RatL}V5pwBeZF)@XTFUHvDg|PTbGdxBO@0+8!R)+@U=_-k7*8qJ@5G+<-a7 zo0K0VvOg^sT#Qqw z+U{7M#Cv!mJqlA{$tsm1UPMMvL@v;@TH5I2(Lr!1kDhG3oLs*GL&I%?Jp;X zpc$5(_ZpyUSq|OVlxl(^60Lf?keGu@^iz4=U^^|pLm=Sing25zl4?zIk+>;3DT@en zb0$jeb#uQN8dR-;C;TZ3ZdGqH1g2q{{Ns696d}eSL8}G%{-wibX-<^2~H0 z_}rTO2-Jj;dnzmGpeioqS#x_mfy*OnWkGKwX$+h6mDN-@kfZD&EP)b`T%_^fL19l( zJ4ngUE7qm;wxVE?QX8FJ54Q=pa|8KenQO6wVu8zV===`tU1IhX{F`GPuQ0))9V#9X zL|F_{m(f|pXYFqL;PDpc4;g7MZUp>k2o2aRNX9~j)C^aPkTnEV`!C`i@l?i9fNJn8 z)S~(aGqvomHA`Z|)nZT-K>MB&h7f5jQSV40NY`+&`+K@ndrR&e_M*OAoq zd5~z*ic-Y%%njj1-K3eSx8LV9ud}BU@t-%<>n2^xPU-x&9m5ty13PYJDY{z|?|#sj zmTh%OzgpyMshQlKrFi&EW4h^Tme>E8WNq=_LIUmPO1wk8*WVw%rrGtE^eaR^e^V5l z;$)q7#b-%U*0e{7e)*F8P8Z(IXT>k*u}w&97gm>k_MQ zrXIvWZ@8_zyrzo0{NK-Z5huFY{)tk`{hB1<8amGux$r$7ai$kY7(60%P39u&m9yn; zI5xkWk+GRcC~gtMeust;NLGo@JJBZ5f*rC&5Lp@hrZ76$+f3Vra5sGTPV79l-|q%V zu6|Uv-Wpj`cubjn$AY(;3*BtHA5$)pfq`E~14dSVQB~u2T-p}>h3C9WrqYZv9rxn( z09;L!X9sU51N`iuLxTneRbiB7rxQI48HhJdGYQx=cGKU=fG}s}vf~p+m++>xr>Eeh zcxQ@LyrtU15lfkn&0gStwBIE25kMI5&4gViJ~ogH~0 zR?ZesUT;Si#9=%DASvVR0yd8m!8vacId z$5&m~(%0Tn+=@v?8e7s^0s-I%b%!u`J32VQCA_7W{@_X=#=pgUObmZi-0h{9^ud}8 z^3HBh1|eP{UJy{x+tyQnNgA6$(#;Acp{?-rZxDz(DJC0tcNYmhJ})mXUN1pjXE$p; zesOVeK9B&PfB+Do0fhTFxkJ2xPH^Vm5PxAPK;f2dwl40r&Q1)!F(DSt9_~_1Oo(}g zzsKk30tWvB-U!c|%Lp9P;b6z=Td zW(igFggUu1|0{%*=&l zM~F3)&&A35&%|%SC1f>Kq?iPFLI0F!IzZfE2m{13VC!V%>;?bll&-BKRL33in@@fb zQDJ^j5WOtM$h+6!O$`9fd_$!~^3nPI@2SF|5ccmf#{=g!#k&t(T zLfoC*be)|Yq?mp~F#NXsQ{W7ee=Uo$EgYfY^SkE%R`ohi*T3%mdI%hB|4cD3{3%=s zh~-~S;1Ex!)gMO)yT7I^Z6Hq8P{b4dx03q1-1h&lTp&RKkd-iEi3RzEErCKXei5LB zm4zTs1OkP?KrkUIODpkzM~6GZ+`S-fP+4n4rig41CG;m729Cdx$@%a4UN+F*Oo0Rh zfgpaMfT%9Nu!NwvgpfEB-~W7i{9+JM5s(<7{6wLGi1e&PfDn+C5Kur|TmUL&VI>B$ zg#LTD|99yL^6T;oNq|Ho1cm;do+RJzE$i=#DarT0E%#pv|FXv+X!%Qq*gFwhG2cJ- z;=l3ryF&g4KYuH;|3ME3=>IzTkNEvBUH_%)KVslNQvPps{g2+Kv;01g1~GXK7iyh~&}5kd@i z6|f@40TMkPI+KrM<_tna>8_~nF7NF4yD0$tc~ga2F?iX!+c5loQ+*MPPYwV)>QhmW z)%9LEUH8dOnfK}WLcgWngXY@L#T9Et4Cdz zfQ>%eLW)M@l}vRr&}hNLTQKB5NRh@7sgL@{%%+GqT#?cu0n}~2ylmmDx z93R^pr#AX1QP)cFF=v2vR_2c*D0VSN74A#QC9*?|?6uzWHS8113Kr7wk{p^Zz-X_a z-cl9qX___g7ki|qS!^nbGXxaL{{HP*$leB?nLH{ZcQhAl7r3?gA)O1rByiZ&@Nu4gx8?yW8(_@Cus|JMf5fE#0epWjm`v7 zn+~(=oQlj?c~_k?p~y?fRnA$63Y3w278q zjL4b++{+|MnYQ*@yO`Kpbt2)#uy($X9j}schVo_JnEcxZ{6l*kWxLX8LXvJWbFVBp zZ9zxm4r19aj|WRXm^tg@C$*+8keXgz5`rd6YXPd`?jj(8ki|NIsj>ZpIi8}(U3fRy z7KX^HVweH7w|LTy=rBUACKF$L+X>b;G)v0dxg*T^L1zV5woBzx)V-&BFU}%7#?|A@ z%oPOep?xBg#YVS*t|L{(Te5Aj<;>&<)&N91P?gwF_qDmggLl=G=6*81XGS;)$Wr8N z?>2drdv>Ij+!R}T>!0+1l#=v~KF9r}ESMIoq5D&`5Bb(4OR@Zio@kCAh7L(Ay`uho zfJHsW^DN}RlR`MXWmMr8FG8~J;b2mPk7IHZ_p|UG(^3xXKyXVNl@Zc?A-G64M+d@w zLn0RFrJ#SWUTctC;m>{YX%_ta?5A+wj_52*s{$RlMt5q&tmsN4y%TK2+eHQW;+K0* zZk%H02`0^X;4!Qr+EmqdGP!D&Yp(%(M!{j?31W8SEjj)~C2^l562FP0&SEUNTsZy} zAJq7q8XXsd4NxCMjj1ydVg2hN0yBCuLnKRExNstL8^v7hdW`@{hK(mwdP>*J?(50t zQTNV--fV=@xJaIMBNwP!7G<|iBtG!X!{anUJ#MYa(|Q^(5^rp6j|ZtUGWBwp>inKK zZszx8E%u)27Vys6K$Pj~ZK=F+`47{HP;0lW;YFk}BHnk&BZ^q$-SiS+$rYzeboy}2 zJ0)DUDp|3L(86t3&M*Z3Wx20xp!0(#JT)R*s6ZTnB#iKKtr#r<~c*@sf|P!-w~{?hpRp(N}` zpMO$WjBmTc?BtPlg)~lwSpu@YN-&LzeuGD+i1-rMfyk|4pZG(pyfSBdJOCZPHpyJ&m<=C`VEM-q$#=9rJ5(G1TF(9FUkpO1S@d@ zS7|T!bqd}a;BqILadhif^t@iq7(B5X~UkY_{Er%Mhg5+bjrq9FANj;d zBFPH?p(j;&z7ksN)2gIxJH+n^DYyRd=2|Wv8q0m!ru{(Utfj)1i~M5+TJmEB%XJItf*QsQ74PnNgWM zj67OwoRBP}W*?y!@N8T1q#4m(3qAN+$cyljPp23icCwY6XV=O4s+(aj!fNf*BKz*J&^;{!;3apR>IM?{WhW={4*IZ#CiocG7T%Q zejHtdMXfXtN9>Up>8-;x%|mcav)_hCQ2)w9)eH7t0mAgwl6D1lHn?8OZ+RjB@L(TO z+!z@EU&Gz72T=^f?n%c zE59r7Ds1%AH9Bsgm&EOIpXVkYvMDmG>HD(xmhV>9vi&NN7@uDU^LB(uwC?UM?yK<8 zt+hyu?QWEtDc^c09H*4!k7DcF1x%}(vQF-V#I#IY6<0xvG@qV|x^maP?pIWVcZROM z-TAsUjZaBB$cb-PG^+okWn4vdPY0^xOj=1_{$rA7ShJKV=>mc1-o$yP_SVO6s} zH*4)V#9lQ>aQ-zW4f}or2H=+XD2W_1kK5O2)Vz%5<|FVKjl=xBu)WwT_kofTH5mi01SFUSjzbYpsW=!D~ z(})ULW!rF=(~7xV@5Xs-PX#`(#EyJR1?Ppy&NVi?OJ>j@`I_-nf_5{}#cY*4UsJ+7 z$vL#FHN}v;R#|Wk4H4HHB~?9uHNgH;!hvj9 zG$=rwOVI**uY>AdvKFi^F2kHiW1`qHZNyc~pM2w)t{4UmxqQ!l;ehNzwGTr4umr;+ z%ousZveM>*E14&!Cxei3I)0ksGnX8nC6RYG)!9xs^7CHacPI~!!NAN_C1L~H#mU*u z(|5nbSd&DSDbvYq<4T+Ts^x?GSMP7adkXNmOY%iQV}0a%h54QJMHTwZI26xXG-}aq zi$gcFK3!;I^S-|TlCwWukhTnFP24+#nWJ84k$1ye<)3ki^%UNeOUYW?(R_yYbSCbV zClyN0ZFeOKz*Nxd zZf;AJ8@r2NtV>SqR!TMF6Z0IeVxrzY_%`!BH};bH9kE_lIU9k}QfJ9T&T`NWobWpz_75V+hU0Ve=`6kKdIq+R+@Fb=rJ3v|2 z=<(?s$@A_#W7J8YOV$|8ID%hjNxMOK?;$DjaDvvO%JlNzT*uDqLXG}v2|B4x#P9*w5!169g; zm7=k&H?{51_blahwAC*b-!NAgS_{unb+dK~BeN>PmVG_y{ca`gA4yj^>mkuN&#F|s zsxIH0pCiuHN7ZOPVj0}W2{XWnxX?+;^M`k||@3NCsKNkq1CUQC;DkoO#Iq 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 9/9] 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