From be69aeb69e6cbcbc1f490d1d2694cca72402c89f Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 17 Apr 2023 18:01:23 +0200 Subject: [PATCH] Custom formations --- client/demo.js | 3 + .../images/icons/arrows-to-eye-solid.svg | 1 + client/public/images/icons/echelon-lh.svg | 63 +++ client/public/images/icons/echelon-rh.svg | 63 +++ client/public/images/icons/echelon.svg | 63 +++ client/public/images/icons/follow.svg | 57 +-- client/public/images/icons/front.svg | 63 +++ client/public/images/icons/line-abreast.svg | 65 +++ client/public/images/icons/trail.svg | 63 +++ .../public/images/reference-system-test.svg | 370 ++++++++++++++++++ client/public/images/reference-system.svg | 218 +++++++++++ client/public/stylesheets/contextmenus.css | 66 ++++ client/src/controls/unitcontextmenu.ts | 13 + client/src/map/map.ts | 36 +- client/src/units/unit.ts | 48 ++- client/views/contextmenus.ejs | 38 ++ 16 files changed, 1194 insertions(+), 36 deletions(-) create mode 100644 client/public/images/icons/arrows-to-eye-solid.svg create mode 100644 client/public/images/icons/echelon-lh.svg create mode 100644 client/public/images/icons/echelon-rh.svg create mode 100644 client/public/images/icons/echelon.svg create mode 100644 client/public/images/icons/front.svg create mode 100644 client/public/images/icons/line-abreast.svg create mode 100644 client/public/images/icons/trail.svg create mode 100644 client/public/images/reference-system-test.svg create mode 100644 client/public/images/reference-system.svg diff --git a/client/demo.js b/client/demo.js index b26fe651..1a4bf887 100644 --- a/client/demo.js +++ b/client/demo.js @@ -634,6 +634,9 @@ class DemoDataGenerator { units(req, res){ var ret = this.demoUnits; + for (let ID in this.demoUnits["units"]){ + this.demoUnits["units"][ID].flightData.latitude += 0.00; + } ret.time = Date.now(); res.send(JSON.stringify(ret)); }; diff --git a/client/public/images/icons/arrows-to-eye-solid.svg b/client/public/images/icons/arrows-to-eye-solid.svg new file mode 100644 index 00000000..11815283 --- /dev/null +++ b/client/public/images/icons/arrows-to-eye-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/icons/echelon-lh.svg b/client/public/images/icons/echelon-lh.svg new file mode 100644 index 00000000..9359bfba --- /dev/null +++ b/client/public/images/icons/echelon-lh.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/icons/echelon-rh.svg b/client/public/images/icons/echelon-rh.svg new file mode 100644 index 00000000..2da057de --- /dev/null +++ b/client/public/images/icons/echelon-rh.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/icons/echelon.svg b/client/public/images/icons/echelon.svg new file mode 100644 index 00000000..21bb81bb --- /dev/null +++ b/client/public/images/icons/echelon.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/icons/follow.svg b/client/public/images/icons/follow.svg index 2ec555ac..b3296b24 100644 --- a/client/public/images/icons/follow.svg +++ b/client/public/images/icons/follow.svg @@ -7,14 +7,14 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - inkscape:version="1.0 (4035a4fb49, 2020-05-01)" - sodipodi:docname="follow.svg" - id="svg14" - version="1.1" - fill="none" - viewBox="0 0 16 16" + width="16" height="16" - width="16"> + viewBox="0 0 16 16" + fill="none" + version="1.1" + id="svg14" + sodipodi:docname="follow.svg" + inkscape:version="1.0 (4035a4fb49, 2020-05-01)"> @@ -30,28 +30,29 @@ + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1017" + id="namedview16" + showgrid="false" + inkscape:zoom="12.142857" + inkscape:cx="22.433158" + inkscape:cy="14.663012" + inkscape:window-x="1912" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="svg14" /> + id="path2" + style="fill:#000000;fill-opacity:1;stroke-width:1.38669" /> diff --git a/client/public/images/icons/front.svg b/client/public/images/icons/front.svg new file mode 100644 index 00000000..cfb0f29a --- /dev/null +++ b/client/public/images/icons/front.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/icons/line-abreast.svg b/client/public/images/icons/line-abreast.svg new file mode 100644 index 00000000..c146a848 --- /dev/null +++ b/client/public/images/icons/line-abreast.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/icons/trail.svg b/client/public/images/icons/trail.svg new file mode 100644 index 00000000..fb23fd08 --- /dev/null +++ b/client/public/images/icons/trail.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/reference-system-test.svg b/client/public/images/reference-system-test.svg new file mode 100644 index 00000000..ff6f152a --- /dev/null +++ b/client/public/images/reference-system-test.svg @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/reference-system.svg b/client/public/images/reference-system.svg new file mode 100644 index 00000000..cd71d782 --- /dev/null +++ b/client/public/images/reference-system.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/client/public/stylesheets/contextmenus.css b/client/public/stylesheets/contextmenus.css index 78b9d3e7..7443e972 100644 --- a/client/public/stylesheets/contextmenus.css +++ b/client/public/stylesheets/contextmenus.css @@ -235,6 +235,10 @@ width: 16px; } +#center-map::before { + content: url( /images/icons/arrows-to-eye-solid.svg ); +} + #refuel::before { content: url( /images/icons/fuel.svg ); } @@ -247,6 +251,68 @@ content: url( /images/icons/follow.svg ); } +#trail::before { + content: url( /images/icons/trail.svg ); +} + +#echelon-lh::before { + content: url( /images/icons/echelon-lh.svg ); +} + +#echelon-rh::before { + content: url( /images/icons/echelon-rh.svg ); +} + +#line-abreast::before { + content: url( /images/icons/line-abreast.svg ); +} + +#front::before { + content: url( /images/icons/front.svg ); +} + +#custom::before { + content: url( /images/icons/custom.svg ); +} + +#custom-formation-dialog { + 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; + align-items: center; +} + +#custom-formation-dialog > .ol-dialog-content > .ol-group { + width: 100%; + justify-content: space-between; +} + +#reference-system { + content: url( /images/reference-system.svg ); + display: inline-block; + filter: invert(100%); + width: 50px; + transform: translate(-50%, -50%); + position: absolute; +} + +.formation-position-clock { + transform: translate(-50%, -50%); + display: flex; + position: absolute; + align-items: center; + justify-content: center; + height: 20px; + width: 20px; +} + /* Airbase context menu */ #airbase-contextmenu { display: flex; diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index 51bde81b..ea6a82cf 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -1,8 +1,21 @@ import { ContextMenu } from "./contextmenu"; export class UnitContextMenu extends ContextMenu { + #callback: CallableFunction | null = null; + constructor(id: string) { super(id); + + document.addEventListener("applyCustomFormation", () => { + var dialog = document.getElementById("custom-formation-dialog"); + if (dialog) + { + dialog.classList.add("hide"); + } + + if (this.#callback) + this.#callback() + }) } setOptions(options: {[key: string]: string}, callback: CallableFunction) diff --git a/client/src/map/map.ts b/client/src/map/map.ts index e76021ee..3509221b 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -19,6 +19,7 @@ export class Map extends L.Map { #preventLeftClick: boolean = false; #leftClickTimer: number = 0; #lastMousePosition: L.Point = new L.Point(0, 0); + #centerUnit: Unit | null = null; #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); @@ -29,7 +30,7 @@ export class Map extends L.Map { constructor(ID: string) { /* Init the leaflet map */ //@ts-ignore - super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true }); + super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: false }); this.setView([37.23, -115.8], 12); this.setLayer("ArcGIS Satellite"); @@ -39,7 +40,9 @@ export class Map extends L.Map { /* Register event handles */ this.on("click", (e: any) => this.#onClick(e)); - this.on("dblclick", (e: any) => this.#onDoubleClick(e)); + this.on("dblclick", (e: any) => this.#onDoubleClick(e)); + this.on("zoomstart", (e: any) => this.#onZoom(e)); + this.on("drag", (e: any) => this.centerOnUnit(null)); this.on("contextmenu", (e: any) => this.#onContextMenu(e)); this.on('selectionend', (e: any) => this.#onSelectionEnd(e)); this.on('mousedown', (e: any) => this.#onMouseDown(e)); @@ -56,6 +59,11 @@ export class Map extends L.Map { document.body.toggleAttribute("data-hide-" + ev.detail.category); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); + + document.addEventListener("unitUpdated", (ev: CustomEvent) => { + if (this.#centerUnit != null && ev.detail == this.#centerUnit) + this.#panToUnit(this.#centerUnit); + }); this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()) } @@ -195,6 +203,18 @@ export class Map extends L.Map { //this.#aircraftSpawnMenu(e); } + centerOnUnit(ID: number | null){ + if (ID != null) + { + this.options.scrollWheelZoom = 'center'; + this.#centerUnit = getUnitsManager().getUnitByID(ID); + } + else { + this.options.scrollWheelZoom = undefined; + this.#centerUnit = null; + } + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { @@ -256,4 +276,16 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; } + + #onZoom(e: any) + { + if (this.#centerUnit != null) + this.#panToUnit(this.#centerUnit); + } + + #panToUnit(unit: Unit) + { + var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude); + this.setView(unitPosition, this.getZoom(), {animate: false}); + } } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index ba30d161..b282b4a9 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -142,7 +142,6 @@ export class Unit extends Marker { } setData(data: UpdateData) { - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); var updateMarker = false; if ((data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)) @@ -212,6 +211,8 @@ export class Unit extends Marker { } else this.#clearPath(); + + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } getData() { @@ -413,11 +414,14 @@ export class Unit extends Marker { #onContextMenu(e: any) { var options: {[key: string]: string} = {}; + + options["Center"] = `
Center map
`; + if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this))) { options = { 'Attack': `
Attack
`, - 'Follow': `
Follow
` + 'Follow': `
Follow
`, } } else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) @@ -433,18 +437,50 @@ export class Unit extends Marker { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { getMap().hideUnitContextMenu(); - this.#executeAction(option); + this.#executeAction(e, option); }); } } - #executeAction(action: string) { + #executeAction(e: any, action: string) { + if (action === "Center") + getMap().centerOnUnit(this.ID); if (action === "Attack") getUnitsManager().selectedUnitsAttackUnit(this.ID); - if (action === "Refuel") + else if (action === "Refuel") getUnitsManager().selectedUnitsRefuel(); - if (action === "Follow") + else if (action === "Follow") + this.#showFollowOptions(e); + } + + #showFollowOptions(e: any) { + var options: {[key: string]: string} = {}; + + options = { + 'Trail': `
Trail
`, + 'Echelon (LH)': `
Echelon (LH)
`, + 'Echelon (RH)': `
Echelon (RH)
`, + 'Line abreast': `
Line abreast
`, + 'Front': `
In front
`, + 'Custom': `
Custom
` + } + + getMap().getUnitContextMenu().setOptions(options, (option: string) => { + getMap().hideUnitContextMenu(); + this.#applyFollowOptions(option); + }); + getMap().showUnitContextMenu(e); + } + + #applyFollowOptions(action: string) + { + if (action === "Custom") + { + document.getElementById("custom-formation-dialog")?.classList.remove("hide"); + } + else { getUnitsManager().selectedUnitsFollowUnit(this.ID); + } } #updateMarker() { diff --git a/client/views/contextmenus.ejs b/client/views/contextmenus.ejs index b386ec9a..6c3e0335 100644 --- a/client/views/contextmenus.ejs +++ b/client/views/contextmenus.ejs @@ -79,6 +79,44 @@ +
+
+ +
+

Custom formation

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+
+
+ + +
+