mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: started adding editable laser/infrared
This commit is contained in:
@@ -35,6 +35,7 @@ import { ContextAction } from "../unit/contextaction";
|
||||
import "./markers/stylesheets/airbase.css";
|
||||
import "./markers/stylesheets/bullseye.css";
|
||||
import "./markers/stylesheets/units.css";
|
||||
import "./markers/stylesheets/spot.css";
|
||||
import "./stylesheets/map.css";
|
||||
|
||||
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
||||
|
||||
216
frontend/react/src/map/markers/spoteditmarker.ts
Normal file
216
frontend/react/src/map/markers/spoteditmarker.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { Marker, LatLng, DivIcon, DomEvent, Map } from "leaflet";
|
||||
|
||||
export class SpotEditMarker extends Marker {
|
||||
#textValue: string;
|
||||
#isEditable: boolean = false;
|
||||
#rotationAngle: number; // Rotation angle in radians
|
||||
#previousValue: string;
|
||||
|
||||
constructor(latlng: LatLng, textValue: string, rotationAngle: number = 0) {
|
||||
super(latlng, {
|
||||
icon: new DivIcon({
|
||||
className: "leaflet-spot-input-marker",
|
||||
html: `<div class="container">
|
||||
<input class="input"/>
|
||||
<div class="text">${textValue}</div>
|
||||
<div class="delete">X</div>
|
||||
</div>`,
|
||||
}),
|
||||
});
|
||||
|
||||
this.#textValue = textValue;
|
||||
this.#rotationAngle = rotationAngle;
|
||||
this.#previousValue = textValue;
|
||||
}
|
||||
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
const button = element.querySelector(".delete");
|
||||
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));
|
||||
|
||||
// Add click event listener to delete spot
|
||||
button?.addEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add click event listener to input spot
|
||||
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("input", (ev) => this.#validateInput(ev));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// 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));
|
||||
|
||||
// Add click event listener to delete spot
|
||||
button?.removeEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add 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("input", (ev) => this.#validateInput(ev));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Method to set the text value
|
||||
setTextValue(textValue: string) {
|
||||
this.#textValue = textValue;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
if (text) text.textContent = textValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get the text value
|
||||
getTextValue() {
|
||||
return this.#textValue;
|
||||
}
|
||||
|
||||
// Method to set the rotation angle in radians
|
||||
setRotationAngle(angle: number) {
|
||||
this.#rotationAngle = angle;
|
||||
if (!this.#isEditable) {
|
||||
this.#updateRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get the rotation angle in radians
|
||||
getRotationAngle() {
|
||||
return this.#rotationAngle;
|
||||
}
|
||||
|
||||
// Method to update the rotation angle to ensure the text is always readable
|
||||
#updateRotation() {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const container = element.querySelector(".container") as HTMLDivElement;
|
||||
if (container) {
|
||||
let angle = this.#rotationAngle % (2 * Math.PI);
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
if (angle > Math.PI / 2 && angle < (3 * Math.PI) / 2) {
|
||||
angle = (angle + Math.PI) % (2 * Math.PI); // Flip the text to be upright
|
||||
}
|
||||
const angleInDegrees = angle * (180 / Math.PI);
|
||||
container.style.transform = `rotate(${angleInDegrees}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#toggleEditMode(ev) {
|
||||
this.#isEditable = !this.#isEditable;
|
||||
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const textElement = element.querySelector(".text");
|
||||
const inputElement = element.querySelector(".input") as HTMLInputElement;
|
||||
const buttonElement = element.querySelector(".delete");
|
||||
const container = element.querySelector(".container") as HTMLDivElement;
|
||||
if (this.#isEditable) {
|
||||
// Rotate to horizontal and make text editable
|
||||
if (textElement && inputElement && buttonElement) {
|
||||
textElement.classList.add("edit-mode");
|
||||
inputElement.style.display = "block";
|
||||
inputElement.value = this.#textValue;
|
||||
inputElement.focus();
|
||||
buttonElement.textContent = "✔"; // Change to check mark
|
||||
buttonElement.classList.add("edit-mode");
|
||||
}
|
||||
container.style.transform = `rotate(0deg)`;
|
||||
} else {
|
||||
// Save the edited text and revert to normal mode
|
||||
if (textElement && inputElement && buttonElement) {
|
||||
const newText = inputElement.value || this.#textValue;
|
||||
this.#textValue = newText;
|
||||
textElement.classList.remove("edit-mode");
|
||||
inputElement.style.display = "none";
|
||||
buttonElement.textContent = "X"; // Change to delete mark
|
||||
buttonElement.classList.remove("edit-mode");
|
||||
}
|
||||
this.#updateRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#stopEventPropagation(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
#validateInput(ev) {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const input = ev.target as HTMLInputElement;
|
||||
const text = element.querySelector(".text");
|
||||
|
||||
// Validate the input value
|
||||
const value = input.value;
|
||||
|
||||
// Check if the value is a partial valid input
|
||||
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
|
||||
const isValid = /^[1][1-7][1-8][1-8]$/.test(value) && Number(value) <= 1788;
|
||||
|
||||
if (isPartialValid || isValid) {
|
||||
if (text) text.textContent = value;
|
||||
this.#previousValue = value;
|
||||
} else {
|
||||
this.#errorFunction();
|
||||
input.value = this.#previousValue;
|
||||
if (text) text.textContent = this.#previousValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#errorFunction() {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const input = element.querySelector(".input") as HTMLInputElement;
|
||||
if (input) {
|
||||
input.classList.add("error-flash");
|
||||
setTimeout(() => {
|
||||
input.classList.remove("error-flash");
|
||||
}, 300); // Duration of the flash effect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#acceptInput(ev) {
|
||||
if (ev.key === "Enter") this.#toggleEditMode(ev);
|
||||
}
|
||||
}
|
||||
23
frontend/react/src/map/markers/spotmarker.ts
Normal file
23
frontend/react/src/map/markers/spotmarker.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class SpotMarker extends CustomMarker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.options.interactive = false;
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(
|
||||
new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26],
|
||||
className: "leaflet-spot-marker",
|
||||
})
|
||||
);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-spot-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
90
frontend/react/src/map/markers/stylesheets/spot.css
Normal file
90
frontend/react/src/map/markers/stylesheets/spot.css
Normal file
@@ -0,0 +1,90 @@
|
||||
.leaflet-spot-input-marker {
|
||||
text-align: center;
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete {
|
||||
background-color: darkred;
|
||||
color: #fffd;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
font-weight: bold;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete:hover {
|
||||
background-color: lightcoral;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
transform-origin: center;
|
||||
background-color: var(--background-steel);
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
column-gap: 6px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .text {
|
||||
margin-left: 12px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .input {
|
||||
display: none;
|
||||
color: white;
|
||||
appearance: none; /* Remove default appearance */
|
||||
background-color: #333; /* Dark background */
|
||||
border-radius: 8px; /* Rounded borders */
|
||||
border: 1px solid #555; /* Border color */
|
||||
padding: 4px 8px; /* Padding for better appearance */
|
||||
outline: none; /* Remove default outline */
|
||||
font-size: 13px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.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 */
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container .input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container .text.edit-mode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .input.error-flash {
|
||||
animation: error-flash 0.3s;
|
||||
}
|
||||
|
||||
@keyframes error-flash {
|
||||
0% {
|
||||
border-color: #555;
|
||||
}
|
||||
50% {
|
||||
border-color: red;
|
||||
}
|
||||
100% {
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete.edit-mode {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete.edit-mode:hover {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
@@ -141,6 +141,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-spot-icon {
|
||||
background-image: url("/images/markers/target.svg");
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-text-icon {
|
||||
color: #111111;
|
||||
text-align: center;
|
||||
|
||||
Reference in New Issue
Block a user