feat: added laser code change, target move, and delete

Note: deleted lasers are not removed from table and keep being drawn. Also added a cooler looking server page
This commit is contained in:
Davide Passoni
2025-01-30 16:20:31 +01:00
parent cc902aec04
commit 9525982161
19 changed files with 844 additions and 218 deletions

View File

@@ -69,7 +69,7 @@ import { SmokeMarker } from "./markers/smokemarker";
/* Register the handler for the box selection */
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
initDraggablePath(L); // TODO: breaks app when compiled
initDraggablePath(L);
export class Map extends L.Map {
/* Options */

View File

@@ -1,21 +0,0 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DestinationPreviewHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: true });
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [18, 18],
iconAnchor: [9, 9],
className: "leaflet-destination-preview-handle-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-destination-preview-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,4 +1,4 @@
import { Marker, LatLng, DivIcon, DomEvent, Map } from "leaflet";
import { Marker, LatLng, DivIcon, Map } from "leaflet";
export class SpotEditMarker extends Marker {
#textValue: string;
@@ -6,14 +6,28 @@ export class SpotEditMarker extends Marker {
#rotationAngle: number; // Rotation angle in radians
#previousValue: string;
constructor(latlng: LatLng, textValue: string, rotationAngle: number = 0) {
onValueUpdated: (value: number) => void = () => {};
onDeleteButtonClicked: () => void = () => {};
/**
* Constructor for SpotEditMarker
* @param {LatLng} latlng - The geographical position of the marker.
* @param {string} textValue - The initial text value to display.
* @param {number} rotationAngle - The initial rotation angle in radians.
*/
constructor(latlng: LatLng, textValue: string, rotationAngle: number = 0, type: string) {
super(latlng, {
icon: new DivIcon({
className: "leaflet-spot-input-marker",
html: `<div class="container">
html:
type === "laser"
? `<div class="container">
<input class="input"/>
<div class="text">${textValue}</div>
<div class="delete">X</div>
</div>`
: `<div class="container">
<div class="delete">X</div>
</div>`,
}),
});
@@ -23,21 +37,34 @@ export class SpotEditMarker extends Marker {
this.#previousValue = textValue;
}
/**
* Called when the marker is added to the map.
* @param {Map} map - The map instance.
* @returns {this} - The current instance of SpotEditMarker.
*/
onAdd(map: Map): this {
super.onAdd(map);
const element = this.getElement();
if (element) {
const text = element.querySelector(".text");
const button = element.querySelector(".delete");
const container = element.querySelector(".container") as HTMLDivElement;
const button = element.querySelector(".delete") as HTMLDivElement;
const input = element.querySelector(".input") as HTMLInputElement;
// Add click event listener to toggle edit mode
text?.addEventListener("mousedown", (ev) => this.#toggleEditMode(ev));
text?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
text?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
container?.addEventListener("mousedown", (ev) => {
ev.stopPropagation();
ev.preventDefault();
this.#setEditMode(ev, !this.#isEditable);
});
container?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
container?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
// Add click event listener to delete spot
button?.addEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
button?.addEventListener("mousedown", (ev) => {
ev.stopPropagation();
ev.preventDefault();
this.#onButtonClicked(ev);
});
button?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
button?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
@@ -45,46 +72,62 @@ export class SpotEditMarker extends Marker {
input?.addEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
input?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
input?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
input?.addEventListener("blur", (ev) => this.#toggleEditMode(ev));
input?.addEventListener("keydown", (ev) => this.#acceptInput(ev));
input?.addEventListener("blur", (ev) => this.#setEditMode(ev, false));
input?.addEventListener("keydown", (ev) => this.#onKeyDown(ev));
input?.addEventListener("input", (ev) => this.#validateInput(ev));
}
return this;
}
/**
* Called when the marker is removed from the map.
* @param {Map} map - The map instance.
* @returns {this} - The current instance of SpotEditMarker.
*/
onRemove(map: Map): this {
super.onRemove(map);
const element = this.getElement();
if (element) {
const text = element.querySelector(".text");
const button = element.querySelector(".delete");
const input = element.querySelector(".input");
const container = element.querySelector(".container") as HTMLDivElement;
const button = element.querySelector(".delete") as HTMLDivElement;
const input = element.querySelector(".input") as HTMLInputElement;
// Add click event listener to toggle edit mode
text?.removeEventListener("mousedown", (ev) => this.#toggleEditMode(ev));
text?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
text?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
// Remove click event listener to toggle edit mode
container?.removeEventListener("mousedown", (ev) => {
ev.stopPropagation();
ev.preventDefault();
this.#setEditMode(ev, !this.#isEditable);
});
container?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
container?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
// Add click event listener to delete spot
button?.removeEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
// Remove click event listener to delete spot
button?.removeEventListener("mousedown", (ev) => {
ev.stopPropagation();
ev.preventDefault();
this.#onButtonClicked(ev);
});
button?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
button?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
// Add click event listener to input spot
// Remove click event listener to input spot
input?.removeEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
input?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
input?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
input?.removeEventListener("blur", (ev) => this.#toggleEditMode(ev));
input?.removeEventListener("keydown", (ev) => this.#acceptInput(ev));
input?.removeEventListener("blur", (ev) => this.#setEditMode(ev, false));
input?.removeEventListener("keydown", (ev) => this.#onKeyDown(ev));
input?.removeEventListener("input", (ev) => this.#validateInput(ev));
}
return this;
}
// Method to set the text value
/**
* Sets the text value of the marker.
* @param {string} textValue - The new text value.
*/
setTextValue(textValue: string) {
this.#textValue = textValue;
const element = this.getElement();
@@ -94,12 +137,18 @@ export class SpotEditMarker extends Marker {
}
}
// Method to get the text value
/**
* Gets the text value of the marker.
* @returns {string} - The current text value.
*/
getTextValue() {
return this.#textValue;
}
// Method to set the rotation angle in radians
/**
* Sets the rotation angle of the marker in radians.
* @param {number} angle - The new rotation angle in radians.
*/
setRotationAngle(angle: number) {
this.#rotationAngle = angle;
if (!this.#isEditable) {
@@ -107,12 +156,17 @@ export class SpotEditMarker extends Marker {
}
}
// Method to get the rotation angle in radians
/**
* Gets the rotation angle of the marker in radians.
* @returns {number} - The current rotation angle in radians.
*/
getRotationAngle() {
return this.#rotationAngle;
}
// Method to update the rotation angle to ensure the text is always readable
/**
* Updates the rotation angle to ensure the text is always readable.
*/
#updateRotation() {
const element = this.getElement();
if (element) {
@@ -129,11 +183,12 @@ export class SpotEditMarker extends Marker {
}
}
#toggleEditMode(ev) {
this.#isEditable = !this.#isEditable;
ev.stopPropagation();
ev.preventDefault();
/**
* Toggles the edit mode of the marker.
* @param {Event} ev - The event object.
*/
#setEditMode(ev: Event, editable: boolean) {
this.#isEditable = editable;
const element = this.getElement();
if (element) {
@@ -167,23 +222,43 @@ export class SpotEditMarker extends Marker {
}
}
#stopEventPropagation(ev) {
/**
* Stops the event propagation.
* @param {Event} ev - The event object.
*/
#stopEventPropagation(ev: Event) {
ev.stopPropagation();
}
#validateInput(ev) {
/**
* Validates the input value.
* @param {Event} ev - The event object.
*/
#validateInput(ev: Event) {
const element = this.getElement();
if (element) {
const input = ev.target as HTMLInputElement;
const text = element.querySelector(".text");
// Validate the input value
// Validate the input value (must be a valid laser code)
const value = input.value;
// Check if the value is a partial valid input
// Conditions for partial validity:
// 1. The first digit is 1.
// 2. The first digit is 1 and the second digit is between 1 and 7.
// 3. The first digit is 1, the second digit is between 1 and 7, and the third digit is between 1 and 8.
// 4. The first digit is 1, the second digit is between 1 and 7, the third digit is between 1 and 8, and the fourth digit is between 1 and 8.
const isPartialValid = /^[1]$|^[1][1-7]$|^[1][1-7][1-8]$|^[1][1-7][1-8][1-8]$/.test(value);
// Check if the value is a complete valid input
// Conditions for complete validity:
// 1. The input is a four-digit number where:
// - The first digit is 1.
// - The second digit is between 1 and 7.
// - The third digit is between 1 and 8.
// - The fourth digit is between 1 and 8.
// 2. The number is between 1111 and 1788.
const isValid = /^[1][1-7][1-8][1-8]$/.test(value) && Number(value) <= 1788;
if (isPartialValid || isValid) {
@@ -197,6 +272,9 @@ export class SpotEditMarker extends Marker {
}
}
/**
* Handles invalid input by causing a small red flash around the input element.
*/
#errorFunction() {
const element = this.getElement();
if (element) {
@@ -210,7 +288,59 @@ export class SpotEditMarker extends Marker {
}
}
/**
* Handles the key down event.
* @param {Event} ev - The keyboard event object.
*/
#onKeyDown(ev) {
if (ev.key === "Enter") this.#acceptInput(ev);
else if (ev.key === "Escape") this.#setEditMode(ev, false);
}
/**
* Accepts the input value when the Enter key is pressed.
* @param {Event} ev - The keyboard event object.
*/
#acceptInput(ev) {
if (ev.key === "Enter") this.#toggleEditMode(ev);
const element = this.getElement();
if (element) {
const input = element.querySelector(".input") as HTMLInputElement;
if (input) {
// Validate the input value (must be a valid laser code)
const value = Number(input.value);
if (value >= 1111 && value <= 1788) {
this.#setEditMode(ev, false);
this.onValueUpdated(value);
this.#successFunction();
} else {
this.#errorFunction();
}
}
}
}
/**
* Handles the button click event.
* @param {Event} ev - The event object.
*/
#onButtonClicked(ev: Event) {
if (this.#isEditable) this.#acceptInput(ev);
else this.onDeleteButtonClicked();
}
/**
* Handles valid input by causing a green flash around the container.
*/
#successFunction() {
const element = this.getElement();
if (element) {
const container = element.querySelector(".container") as HTMLDivElement;
if (container) {
container.classList.add("success-flash");
setTimeout(() => {
container.classList.remove("success-flash");
}, 900); // Duration of the flash effect (3 flashes, 300ms each)
}
}
}
}

View File

@@ -4,7 +4,8 @@ import { CustomMarker } from "./custommarker";
export class SpotMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.options.interactive = false;
this.options.interactive = true;
this.options.draggable = true;
this.setZIndexOffset(9999);
}

View File

@@ -1,3 +1,4 @@
/* Container for the spot input marker */
.leaflet-spot-input-marker {
text-align: center;
display: flex !important;
@@ -5,6 +6,7 @@
align-items: center;
}
/* Delete button styles */
.leaflet-spot-input-marker .delete {
background-color: darkred;
color: #fffd;
@@ -17,10 +19,12 @@
align-content: center;
}
/* Delete button hover effect */
.leaflet-spot-input-marker .delete:hover {
background-color: lightcoral;
}
/* Container for the spot input marker content */
.leaflet-spot-input-marker .container {
width: fit-content;
display: flex;
@@ -31,14 +35,17 @@
font-size: 13px;
column-gap: 6px;
align-content: center;
border: 2px solid transparent;
}
/* Text inside the spot input marker */
.leaflet-spot-input-marker .text {
margin-left: 12px;
margin-top: auto;
margin-bottom: auto;
}
/* Input field inside the spot input marker */
.leaflet-spot-input-marker .input {
display: none;
color: white;
@@ -50,25 +57,32 @@
outline: none; /* Remove default outline */
font-size: 13px;
width: 80px;
border-top-left-radius: 999px;
border-bottom-left-radius: 999px;
}
/* Input field focus effect */
.leaflet-spot-input-marker .input:focus {
border-color: #777; /* Border color on focus */
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Shadow on focus */
}
/* Hide input field inside the container */
.leaflet-spot-input-marker .container .input {
display: none;
}
/* Hide text in edit mode */
.leaflet-spot-input-marker .container .text.edit-mode {
display: none;
}
/* Error flash animation for input field */
.leaflet-spot-input-marker .input.error-flash {
animation: error-flash 0.3s;
}
/* Keyframes for error flash animation */
@keyframes error-flash {
0% {
border-color: #555;
@@ -81,10 +95,27 @@
}
}
/* Edit mode styles for delete button */
.leaflet-spot-input-marker .delete.edit-mode {
background-color: green;
}
/* Edit mode hover effect for delete button */
.leaflet-spot-input-marker .delete.edit-mode:hover {
background-color: lightgreen;
}
/* Success flash animation for container */
.leaflet-spot-input-marker .container.success-flash {
animation: success-flash 0.3s 3;
}
/* Keyframes for success flash animation */
@keyframes success-flash {
0%, 100% {
border-color: transparent;
}
33%, 66% {
border-color: green;
}
}