feat(map): added coordinates copy feature

This commit is contained in:
MarcoJayUsai
2025-03-22 12:33:34 +01:00
parent d35e6063e7
commit 45f828c838
7 changed files with 182 additions and 177 deletions

View File

@@ -892,3 +892,19 @@ export class WeaponsRefreshedEvent {
if (DEBUG) console.log(`Event ${this.name} dispatched`); if (DEBUG) console.log(`Event ${this.name} dispatched`);
} }
} }
export class CoordinatesFreezeEvent {
static on(callback: () => void) {
document.addEventListener(
this.name,
(ev: CustomEventInit) => {
callback();
}
)
}
static dispatch() {
document.dispatchEvent(new CustomEvent(this.name));
if (DEBUG) console.log(`Event ${this.name} dispatched`);
}
}

View File

@@ -49,6 +49,7 @@ import {
ConfigLoadedEvent, ConfigLoadedEvent,
ContextActionChangedEvent, ContextActionChangedEvent,
ContextActionSetChangedEvent, ContextActionSetChangedEvent,
CoordinatesFreezeEvent,
HiddenTypesChangedEvent, HiddenTypesChangedEvent,
MapContextMenuRequestEvent, MapContextMenuRequestEvent,
MapOptionsChangedEvent, MapOptionsChangedEvent,
@@ -509,7 +510,7 @@ export class Map extends L.Map {
code: "ShiftLeft", code: "ShiftLeft",
altKey: false, altKey: false,
ctrlKey: false, ctrlKey: false,
}); })
} }
setLayerName(layerName: string) { setLayerName(layerName: string) {
@@ -1031,6 +1032,7 @@ export class Map extends L.Map {
} }
#onLeftShortClick(e: L.LeafletMouseEvent) { #onLeftShortClick(e: L.LeafletMouseEvent) {
CoordinatesFreezeEvent.dispatch();
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) { if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) {
this.#debounceTimeout = window.setTimeout(() => { this.#debounceTimeout = window.setTimeout(() => {
if (!this.#isSelecting) { if (!this.#isSelecting) {

View File

@@ -769,4 +769,8 @@ export function secondsToTimeString(seconds: number) {
const secs = Math.floor(seconds % 60); const secs = Math.floor(seconds % 60);
return `${zeroPad(hours, 2)}:${zeroPad(minutes, 2)}:${zeroPad(secs, 2)}`; return `${zeroPad(hours, 2)}:${zeroPad(minutes, 2)}:${zeroPad(secs, 2)}`;
}
export function isTrustedEnvironment() {
return window.location.protocol === "https:";
} }

View File

@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { ConvertDDToDMS, DDToDDM, latLngToMGRS, latLngToUTM, zeroAppend } from "../../other/utils"; import { ConvertDDToDMS, DDToDDM, latLngToMGRS, latLngToUTM, zeroAppend } from "../../other/utils";
export function OlLocation(props: { location: LatLng; className?: string; referenceSystem?: string; onClick?: () => void }) { export function OlLocation(props: { location: LatLng; className?: string; referenceSystem?: string; onClick?: () => void; onRefSystemChange?: (refSystem: string) => any }) {
const [referenceSystem, setReferenceSystem] = props.referenceSystem ? [props.referenceSystem, () => {}] : useState("LatLngDec"); const [referenceSystem, setReferenceSystem] = props.referenceSystem ? [props.referenceSystem, () => {}] : useState("LatLngDec");
const MGRS = latLngToMGRS(props.location.lat, props.location.lng, 6); const MGRS = latLngToMGRS(props.location.lat, props.location.lng, 6);
if (referenceSystem === "MGRS") { if (referenceSystem === "MGRS") {
@@ -17,6 +17,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
? props.onClick ? props.onClick
: (ev) => { : (ev) => {
setReferenceSystem("LatLngDec"); setReferenceSystem("LatLngDec");
props.onRefSystemChange ? props.onRefSystemChange("LatLngDec") : null;
ev.stopPropagation(); ev.stopPropagation();
} }
} }
@@ -44,6 +45,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
? props.onClick ? props.onClick
: (ev) => { : (ev) => {
setReferenceSystem("LatLngDMS"); setReferenceSystem("LatLngDMS");
props.onRefSystemChange ? props.onRefSystemChange("LatLngDMS") : null;
ev.stopPropagation(); ev.stopPropagation();
} }
} }
@@ -83,6 +85,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
? props.onClick ? props.onClick
: (ev) => { : (ev) => {
setReferenceSystem("LatLngDDM"); setReferenceSystem("LatLngDDM");
props.onRefSystemChange ? props.onRefSystemChange("LatLngDDM") : null;
ev.stopPropagation(); ev.stopPropagation();
} }
} }
@@ -122,6 +125,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
? props.onClick ? props.onClick
: (ev) => { : (ev) => {
setReferenceSystem("MGRS"); setReferenceSystem("MGRS");
props.onRefSystemChange ? props.onRefSystemChange("MGRS") : null;
ev.stopPropagation(); ev.stopPropagation();
} }
} }

View File

@@ -1,11 +1,12 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { OlLocation } from "../components/ollocation"; import { OlLocation } from "../components/ollocation";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain } from "react-icons/fa6"; import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain, FaCopy, FaXmark } from "react-icons/fa6";
import { BullseyesDataChangedEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events"; import { BullseyesDataChangedEvent, CoordinatesFreezeEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
import { computeBearingRangeString, mToFt } from "../../other/utils"; import { computeBearingRangeString, ConvertDDToDMS, DDToDDM, isTrustedEnvironment, latLngToMGRS, mToFt, zeroAppend } from "../../other/utils";
import { Bullseye } from "../../mission/bullseye"; import { Bullseye } from "../../mission/bullseye";
import { Unit } from "../../unit/unit"; import { Unit } from "../../unit/unit";
import { getApp } from "../../olympusapp";
export function CoordinatesPanel(props: {}) { export function CoordinatesPanel(props: {}) {
const [latlng, setLatlng] = useState(new LatLng(0, 0)); const [latlng, setLatlng] = useState(new LatLng(0, 0));
@@ -13,7 +14,10 @@ export function CoordinatesPanel(props: {}) {
const [bullseyes, setBullseyes] = useState(null as null | { [name: string]: Bullseye }); const [bullseyes, setBullseyes] = useState(null as null | { [name: string]: Bullseye });
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]); const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const [copyCoordsOpen, setCopyCoordsOpen] = useState(false);
const [refSystem, setRefSystem] = useState("LatLngDec");
const [copyableCoordinates, setCopyableCoordinates] = useState("To start, click any point on the map.");
useEffect(() => { useEffect(() => {
MouseMovedEvent.on((latlng, elevation) => { MouseMovedEvent.on((latlng, elevation) => {
setLatlng(latlng); setLatlng(latlng);
@@ -23,18 +27,43 @@ export function CoordinatesPanel(props: {}) {
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes)); BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits)); SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
SelectionClearedEvent.on(() => setSelectedUnits([])); SelectionClearedEvent.on(() => setSelectedUnits([]));
}, []); CoordinatesFreezeEvent.on( () => {
setCopyableCoordinates(getCopyableCoordinates());
});
}, [refSystem, latlng, elevation]);
const getCopyableCoordinates = () => {
let returnString = '';
switch (refSystem) {
case "LatLngDec":
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${zeroAppend(latlng.lat, 3, true, 6)}°, ${latlng.lng >= 0 ? "E" : "W"} ${zeroAppend(latlng.lng, 3, true, 6)}°,`
break;
case "LatLngDMS":
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${ConvertDDToDMS(latlng.lat, false)}, ${latlng.lng >= 0 ? "E" : "W"} ${ConvertDDToDMS(latlng.lng, false)},`
break;
case "LatLngDDM":
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${DDToDDM(latlng.lat)}, ${latlng.lng >= 0 ? "E" : "W"} ${DDToDDM(latlng.lng)}`;
break;
case "MGRS":
returnString = latLngToMGRS(latlng.lat, latlng.lng, 6)?.string || "Error";
break;
}
returnString += ` Elevation: ${Math.round(elevation)}`;
return returnString;
};
return ( return (
<div <div
className={` className={`
flex w-full flex-col items-center justify-between gap-2 rounded-lg flex w-full flex-col justify-between gap-2 rounded-lg bg-gray-200 px-3
bg-gray-200 px-3 py-3 text-sm backdrop-blur-lg backdrop-grayscale py-3 text-sm backdrop-blur-lg backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200 dark:bg-olympus-800/90 dark:text-gray-200
`} `}
onClick={() => setOpen(!open)}
> >
<div className="absolute right-[12px] top-[15px]"> <div className="absolute right-[12px] top-[15px]" onClick={() => setOpen(!open)}>
{open ? ( {open ? (
<FaChevronDown className="cursor-pointer" /> <FaChevronDown className="cursor-pointer" />
) : ( ) : (
@@ -44,11 +73,7 @@ export function CoordinatesPanel(props: {}) {
)} )}
</div> </div>
{open && bullseyes && ( {open && bullseyes && (
<div <div className={`flex w-full flex-col items-start justify-start gap-2`}>
className={`
flex w-full flex-col items-start justify-start gap-2
`}
>
<div <div
className={` className={`
flex flex min-w-64 max-w-64 items-start justify-between gap-2 flex flex min-w-64 max-w-64 items-start justify-between gap-2
@@ -98,26 +123,74 @@ export function CoordinatesPanel(props: {}) {
)} )}
<div <div
className={` className={`flex w-full items-center justify-between pointer-events-all`}
flex w-full items-center justify-between pointer-events-all
`}
> >
<OlLocation className="!min-w-64 !max-w-64 bg-transparent !p-0" location={latlng} /> <OlLocation onRefSystemChange={(evt) => setRefSystem(evt)} className={`
!min-w-64 !max-w-64 bg-transparent !p-0
`} location={latlng} />
</div> </div>
{open && ( {open && [
<div className="flex w-full items-center justify-start"> <div
<span className={`
className={` flex w-full min-w-64 max-w-64 items-center justify-between
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold `}
text-olympus-700 >
`} <div className="flex">
<span
className={`
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaMountain />
</span>
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
</div>
<div
className="ml-auto flex w-[50%]"
onClick={async (evt) => {
evt.stopPropagation();
setCopyCoordsOpen(true);
if (isTrustedEnvironment()) {
try {
await navigator.clipboard.writeText(copyableCoordinates);
getApp().addInfoMessage(`Coordinates copied to clipboard: ${copyableCoordinates}`);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
}}
> >
<FaMountain /> <span
</span> className={`
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div> mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
</div> text-olympus-700
)} `}
>
<FaCopy />
</span>
<div className="min-w-12">Copy Coords</div>
</div>
</div>,
open && copyCoordsOpen && (
<div
className={`
relative mt-4 flex w-full min-w-64 items-center justify-between
`}
onClick={(evt) => evt.stopPropagation()}
>
<textarea readOnly={true} className="resize-none p-2 text-black" name="coordsTextArea" id="coordsTextArea" cols={25} rows={2} value={copyableCoordinates}></textarea>
<div className="absolute right-[0] top-[0px] background-transparent" onClick={() => setCopyCoordsOpen(false)}>
<FaXmark className="cursor-pointer" />
</div>
</div>
),
]}
</div> </div>
); );
} }

View File

@@ -417,9 +417,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
<td className="flex gap-2 text-lg text-gray-200"> <td className="flex gap-2 text-lg text-gray-200">
<FontAwesomeIcon icon={entry[1][0] as IconDefinition} />{" "} <FontAwesomeIcon icon={entry[1][0] as IconDefinition} />{" "}
<div <div
className={` className={`text-sm text-gray-400`}
text-sm text-gray-400
`}
> >
{entry[1][1] as string} {entry[1][1] as string}
</div> </div>
@@ -799,9 +797,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeHold} icon={olButtonsRoeHold}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Hold fire: The unit will not shoot in any circumstance Hold fire: The unit will not shoot in any circumstance
</div> </div>
@@ -809,9 +805,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeReturn} icon={olButtonsRoeReturn}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Return fire: The unit will not fire unless fired upon Return fire: The unit will not fire unless fired upon
</div> </div>
@@ -819,17 +813,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeDesignated} icon={olButtonsRoeDesignated}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
<div> <div>
{" "} {" "}
Fire on target: The unit will not fire unless fired upon{" "} Fire on target: The unit will not fire unless fired upon{" "}
<p <p
className={` className={`inline font-bold`}
inline font-bold
`}
> >
or or
</p>{" "} </p>{" "}
@@ -840,9 +830,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeFree} icon={olButtonsRoeFree}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Free: The unit will fire at any detected enemy in range Free: The unit will fire at any detected enemy in range
</div> </div>
@@ -850,25 +838,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
<div className="flex gap-4"> <div className="flex gap-4">
<div className="my-auto"> <div className="my-auto">
<FaExclamationCircle <FaExclamationCircle
className={` className={`animate-bounce text-xl`}
animate-bounce text-xl
`}
/> />
</div> </div>
<div> <div>
Currently, DCS blue and red ground units do not respect{" "} Currently, DCS blue and red ground units do not respect{" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeReturn} icon={olButtonsRoeReturn}
className={` className={`my-auto text-white`}
my-auto text-white
`}
/>{" "} />{" "}
and{" "} and{" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsRoeDesignated} icon={olButtonsRoeDesignated}
className={` className={`my-auto text-white`}
my-auto text-white
`}
/>{" "} />{" "}
rules of engagement, so be careful, they may start shooting when you don't want them to. Use neutral units for finer rules of engagement, so be careful, they may start shooting when you don't want them to. Use neutral units for finer
control. control.
@@ -929,9 +911,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsThreatNone} icon={olButtonsThreatNone}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
No reaction: The unit will not react in any circumstance No reaction: The unit will not react in any circumstance
</div> </div>
@@ -939,9 +919,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsThreatPassive} icon={olButtonsThreatPassive}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Passive: The unit will use counter-measures, but will not alter its course Passive: The unit will use counter-measures, but will not alter its course
</div> </div>
@@ -949,9 +927,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsThreatManoeuvre} icon={olButtonsThreatManoeuvre}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Manouevre: The unit will try to evade the threat using manoeuvres, but no counter-measures Manouevre: The unit will try to evade the threat using manoeuvres, but no counter-measures
</div> </div>
@@ -959,9 +935,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsThreatEvade} icon={olButtonsThreatEvade}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Full evasion: the unit will try to evade the threat both manoeuvering and using counter-measures Full evasion: the unit will try to evade the threat both manoeuvering and using counter-measures
</div> </div>
@@ -1016,9 +990,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsEmissionsSilent} icon={olButtonsEmissionsSilent}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Radio silence: No radar or ECM will be used Radio silence: No radar or ECM will be used
</div> </div>
@@ -1026,9 +998,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsEmissionsDefend} icon={olButtonsEmissionsDefend}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Defensive: The unit will turn radar and ECM on only when threatened Defensive: The unit will turn radar and ECM on only when threatened
</div> </div>
@@ -1036,9 +1006,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsEmissionsAttack} icon={olButtonsEmissionsAttack}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Attack: The unit will use radar and ECM when engaging other units Attack: The unit will use radar and ECM when engaging other units
</div> </div>
@@ -1046,9 +1014,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{" "} {" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={olButtonsEmissionsFree} icon={olButtonsEmissionsFree}
className={` className={`my-auto min-w-8 text-white`}
my-auto min-w-8 text-white
`}
/>{" "} />{" "}
Free: the unit will use the radar and ECM all the time Free: the unit will use the radar and ECM all the time
</div> </div>
@@ -1269,9 +1235,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
<div className="flex gap-4"> <div className="flex gap-4">
<div className="my-auto"> <div className="my-auto">
<FaExclamationCircle <FaExclamationCircle
className={` className={`animate-bounce text-xl`}
animate-bounce text-xl
`}
/> />
</div> </div>
<div> <div>
@@ -1451,9 +1415,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{/* ============== Operate as toggle START ============== */} {/* ============== Operate as toggle START ============== */}
{selectedUnits.every((unit) => unit.getCoalition() === "neutral") && ( {selectedUnits.every((unit) => unit.getCoalition() === "neutral") && (
<div <div
className={` className={`flex content-center justify-between`}
flex content-center justify-between
`}
> >
<span <span
className={` className={`
@@ -1495,17 +1457,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
> >
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Barrel height:{" "} Barrel height:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
decimalPlaces={1} decimalPlaces={1}
className={` className={`ml-auto`}
ml-auto
`}
value={barrelHeight} value={barrelHeight}
min={0} min={0}
max={100} max={100}
@@ -1520,26 +1478,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m m
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Muzzle velocity:{" "} Muzzle velocity:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
decimalPlaces={0} decimalPlaces={0}
className={` className={`ml-auto`}
ml-auto
`}
value={muzzleVelocity} value={muzzleVelocity}
min={0} min={0}
max={10000} max={10000}
@@ -1554,26 +1506,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m/s m/s
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Aim time:{" "} Aim time:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
decimalPlaces={2} decimalPlaces={2}
className={` className={`ml-auto`}
ml-auto
`}
value={aimTime} value={aimTime}
min={0} min={0}
max={100} max={100}
@@ -1588,25 +1534,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
s s
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Shots to fire:{" "} Shots to fire:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
className={` className={`ml-auto`}
ml-auto
`}
value={shotsToFire} value={shotsToFire}
min={0} min={0}
max={100} max={100}
@@ -1623,17 +1563,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Shots base interval:{" "} Shots base interval:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
decimalPlaces={2} decimalPlaces={2}
className={` className={`ml-auto`}
ml-auto
`}
value={shotsBaseInterval} value={shotsBaseInterval}
min={0} min={0}
max={100} max={100}
@@ -1648,26 +1584,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
s s
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Shots base scatter:{" "} Shots base scatter:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
decimalPlaces={2} decimalPlaces={2}
className={` className={`ml-auto`}
ml-auto
`}
value={shotsBaseScatter} value={shotsBaseScatter}
min={0} min={0}
max={50} max={50}
@@ -1682,25 +1612,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
deg deg
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Engagement range:{" "} Engagement range:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
className={` className={`ml-auto`}
ml-auto
`}
value={engagementRange} value={engagementRange}
min={0} min={0}
max={100000} max={100000}
@@ -1715,25 +1639,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m m
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Targeting range:{" "} Targeting range:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
className={` className={`ml-auto`}
ml-auto
`}
value={targetingRange} value={targetingRange}
min={0} min={0}
max={100000} max={100000}
@@ -1748,25 +1666,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m m
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Aim method range:{" "} Aim method range:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
className={` className={`ml-auto`}
ml-auto
`}
value={aimMethodRange} value={aimMethodRange}
min={0} min={0}
max={100000} max={100000}
@@ -1781,25 +1693,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m m
</div> </div>
</div> </div>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
Acquisition range:{" "} Acquisition range:{" "}
</div> </div>
<OlNumberInput <OlNumberInput
className={` className={`ml-auto`}
ml-auto
`}
value={acquisitionRange} value={acquisitionRange}
min={0} min={0}
max={100000} max={100000}
@@ -1814,9 +1720,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
}} }}
></OlNumberInput> ></OlNumberInput>
<div <div
className={` className={`my-auto`}
my-auto
`}
> >
m m
</div> </div>

View File

@@ -60,6 +60,7 @@ import * as turf from "@turf/turf";
import { Carrier } from "../mission/carrier"; import { Carrier } from "../mission/carrier";
import { import {
ContactsUpdatedEvent, ContactsUpdatedEvent,
CoordinatesFreezeEvent,
HiddenTypesChangedEvent, HiddenTypesChangedEvent,
MapOptionsChangedEvent, MapOptionsChangedEvent,
UnitContextMenuRequestEvent, UnitContextMenuRequestEvent,
@@ -1611,6 +1612,7 @@ export abstract class Unit extends CustomMarker {
} }
#onLeftShortClick(e: any) { #onLeftShortClick(e: any) {
CoordinatesFreezeEvent.dispatch();
DomEvent.stop(e); DomEvent.stop(e);
DomEvent.preventDefault(e); DomEvent.preventDefault(e);
e.originalEvent.stopImmediatePropagation(); e.originalEvent.stopImmediatePropagation();