feat: added grouped path markers

fix: fixed shortcuts getting stuck
This commit is contained in:
Davide Passoni 2025-02-06 16:28:23 +01:00
parent 52606b8d57
commit 1deeaacf1d
5 changed files with 98 additions and 64 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="41"
height="51"
viewBox="0 0 41 51"
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
version="1.1"
id="svg5"
@ -22,10 +22,10 @@
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="15.941176"
inkscape:cx="20.48155"
inkscape:cy="25.5"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:cx="20.512916"
inkscape:cy="25.500001"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
@ -33,7 +33,7 @@
<g
filter="url(#filter0_d_1082_10311)"
id="g3"
transform="translate(2.0701107,2.0701107)">
transform="matrix(0.65082874,0,0,0.65082874,3.8892989,2.018336)">
<mask
id="path-1-outside-1_1082_10311"
maskUnits="userSpaceOnUse"
@ -70,7 +70,7 @@
x="0"
y="0.75"
width="40.5"
height="49.9443"
height="49.944302"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,30 @@
import { DivIcon, LatLngExpression, Map, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class PathMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.options.interactive = true;
this.options.draggable = true;
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [32, 32],
iconAnchor: [16, 27],
className: "leaflet-path-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-path-icon");
var img = document.createElement("img");
img.src = "images/markers/path.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
}

View File

@ -13,14 +13,18 @@ export class Shortcut {
this.#id = id;
this.#options = options;
AppStateChangedEvent.on((state, subState) => (this.#keydown = false));
ModalEvent.on((modal) => (this.#modal = modal))
/* I don't know why I set this, may be a leftover from initial shortcut experiments.
If enabled, it will cause the keydown to be reset when the app state changes, which may
cause shortcuts that cause a state change (like unit selection) to remain stuck. */
//AppStateChangedEvent.on((state, subState) => (this.#keydown = false));
ModalEvent.on((modal) => (this.#modal = modal));
/* On keyup, it is enough to check the code only, not the entire combination */
document.addEventListener("keyup", (ev: any) => {
if (this.#modal) return;
if (this.#keydown && this.getOptions().code === ev.code) {
console.log(`Keyup for shortcut ${this.#id}`)
console.log(`Keyup for shortcut ${this.#id}`);
ev.preventDefault();
this.#keydown = false;
this.getOptions().keyUpCallback(ev);
@ -30,7 +34,7 @@ export class Shortcut {
/* Forced keyup, in case the window loses focus */
document.addEventListener("blur", (ev: any) => {
if (this.#keydown) {
console.log(`Keyup (forced by blur) for shortcut ${this.#id}`)
console.log(`Keyup (forced by blur) for shortcut ${this.#id}`);
ev.preventDefault();
this.#keydown = false;
this.getOptions().keyUpCallback(ev);
@ -46,10 +50,10 @@ export class Shortcut {
(this.getOptions().ctrlKey === undefined || ev.ctrlKey === (this.getOptions().ctrlKey ?? ev.code.indexOf("Control") >= 0)) &&
(this.getOptions().shiftKey === undefined || ev.shiftKey === (this.getOptions().shiftKey ?? ev.code.indexOf("Shift") >= 0))
) {
console.log(`Keydown event for shortcut ${this.#id}`)
console.log(`Keydown event for shortcut ${this.#id}`);
ev.preventDefault();
this.#keydown = true;
const keyDownCallback = this.getOptions().keyDownCallback
const keyDownCallback = this.getOptions().keyDownCallback;
if (keyDownCallback) keyDownCallback(ev); /* Key down event is optional */
}
});
@ -84,4 +88,8 @@ export class Shortcut {
);
return actions;
}
#setKeydown(keydown: boolean) {
this.#keydown = keydown;
}
}

View File

@ -72,15 +72,9 @@ import { ArrowMarker } from "../map/markers/arrowmarker";
import { Spot } from "../mission/spot";
import { SpotEditMarker } from "../map/markers/spoteditmarker";
import { SpotMarker } from "../map/markers/spotmarker";
import { get } from "http";
const bearingStrings = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west", "north"];
var pathIcon = new Icon({
iconUrl: "images/markers/path.svg",
iconAnchor: [20, 43],
});
/**
* Unit class which controls unit behaviour
*/
@ -164,7 +158,6 @@ export abstract class Unit extends CustomMarker {
#selected: boolean = false;
#hidden: boolean = false;
#highlighted: boolean = false;
#pathMarkers: Marker[] = [];
#pathPolyline: Polyline;
#contactsPolylines: Polyline[] = [];
#engagementCircle: RangeCircle;
@ -1223,14 +1216,6 @@ export abstract class Unit extends CustomMarker {
if (!this.#human) this.#activePath = [];
}
updatePathFromMarkers() {
var path: any = [];
this.#pathMarkers.forEach((marker) => {
path[Object.keys(path).length.toString()] = marker.getLatLng();
});
getApp().getServerManager().addDestination(this.ID, path);
}
attackUnit(targetID: number) {
/* Units can't attack themselves */
if (!this.#human) if (this.ID != targetID) getApp().getServerManager().attackUnit(this.ID, targetID);
@ -1716,37 +1701,8 @@ export abstract class Unit extends CustomMarker {
var points: LatLng[] = [];
points.push(new LatLng(this.#position.lat, this.#position.lng));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.#activePath).length) {
var marker = new Marker([0, 0], {
icon: pathIcon,
draggable: true,
}).addTo(getApp().getMap());
marker.on("dragstart", (event) => {
event.target.options["freeze"] = true;
});
marker.on("dragend", (event) => {
this.updatePathFromMarkers();
event.target.options["freeze"] = false;
});
this.#pathMarkers.push(marker);
}
/* Remove markers if too many */
while (this.#pathMarkers.length > Object.keys(this.#activePath).length) {
getApp()
.getMap()
.removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1);
}
/* Update the position of the existing markers (to avoid creating markers uselessly) */
for (let WP in this.#activePath) {
var destination = this.#activePath[WP];
var frozen = this.#pathMarkers[parseInt(WP)].options["freeze"];
if (!frozen) {
this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]);
}
points.push(new LatLng(destination.lat, destination.lng));
}
@ -1763,10 +1719,6 @@ export abstract class Unit extends CustomMarker {
#clearPath() {
if (this.#pathPolyline.getLatLngs().length != 0) {
for (let WP in this.#pathMarkers) {
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
}
this.#pathMarkers = [];
this.#pathPolyline.setLatLngs([]);
}
}

View File

@ -38,6 +38,7 @@ import {
} from "../events";
import { UnitDatabase } from "./databases/unitdatabase";
import * as turf from "@turf/turf";
import { PathMarker } from "../map/markers/pathmarker";
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
@ -54,6 +55,7 @@ export class UnitsManager {
#protectionCallback: (units: Unit[]) => void = (units) => {};
#AWACSReference: Unit | null = null;
#clusters: { [key: number]: Unit[] } = {};
#pathMarkers: PathMarker[] = [];
constructor() {
this.#unitDatabase = new UnitDatabase();
@ -303,8 +305,50 @@ export class UnitsManager {
}
/* Update all the lines of all the selected units. This code is handled by the UnitsManager since, for example, it must be run both when the detected OR the detecting unit is updated */
let pathMarkersCoordinates: LatLng[] = [];
for (let ID in this.#units) {
if (this.#units[ID].getSelected()) this.#units[ID].drawLines();
if (this.#units[ID].getSelected()) {
this.#units[ID].drawLines();
const unitPath = this.#units[ID].getActivePath();
unitPath.forEach((latlng: LatLng) => {
if (pathMarkersCoordinates.every((coord: LatLng) => coord.lat != latlng.lat && coord.lng != latlng.lng)) pathMarkersCoordinates.push(latlng);
});
}
}
/* Update the path markers */
if (this.#pathMarkers.find((pathMarker: PathMarker) => pathMarker.options["freeze"]) === undefined) {
this.#pathMarkers.forEach((pathMarker: PathMarker) => {
if (!pathMarkersCoordinates.some((coord: LatLng) => pathMarker.getLatLng().equals(coord))) {
pathMarker.remove();
this.#pathMarkers = this.#pathMarkers.filter((marker: PathMarker) => marker !== pathMarker);
}
});
pathMarkersCoordinates.forEach((latlng: LatLng) => {
if (!this.#pathMarkers.some((pathMarker: PathMarker) => pathMarker.getLatLng().equals(latlng))) {
const pathMarker = new PathMarker(latlng);
pathMarker.on("dragstart", (event) => {
event.target.options["freeze"] = true;
event.target.options["originalPosition"] = event.target.getLatLng();
});
pathMarker.on("dragend", (event) => {
event.target.options["freeze"] = false;
this.getSelectedUnits().forEach((unit) => {
let path = [...unit.getActivePath()];
const idx = path.findIndex((coord: LatLng) => coord.equals(event.target.options["originalPosition"]));
if (idx !== -1) {
path[idx] = event.target.getLatLng();
getApp().getServerManager().addDestination(unit.ID, path);
}
});
});
pathMarker.addTo(getApp().getMap());
this.#pathMarkers.push(pathMarker);
}
});
}
/* Compute the base clusters */