feat: completed measure handling

This commit is contained in:
Davide Passoni
2025-02-03 17:27:19 +01:00
parent 627c4b5584
commit 52606b8d57
24 changed files with 869 additions and 75 deletions

View File

@@ -3,7 +3,7 @@ import { getApp } from "../olympusapp";
import { BoxSelect } from "./boxselect";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
import { areaContains, bearing, bearingAndDistanceToLatLng, deepCopyTable, deg2rad, getGroundElevation, mToFt, mToNm, nmToM, rad2deg } from "../other/utils";
import { areaContains, deepCopyTable, deg2rad, getGroundElevation } from "../other/utils";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import {
@@ -67,6 +67,9 @@ import {
import { ContextActionSet } from "../unit/contextactionset";
import { SmokeMarker } from "./markers/smokemarker";
import { MeasureMarker } from "./markers/measuremarker";
import { MeasureStartMarker } from "./markers/measurestartmarker";
import { MeasureEndMarker } from "./markers/measureendmarker";
import { Measure } from "./measure";
/* Register the handler for the box selection */
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
@@ -107,6 +110,7 @@ export class Map extends L.Map {
#isDragging: boolean = false;
#isSelecting: boolean = false;
#originalMouseClickLatLng: L.LatLng | null = null;
#debounceTimeout: number | null = null;
#isLeftMouseDown: boolean = false;
#isRightMouseDown: boolean = false;
@@ -157,9 +161,11 @@ export class Map extends L.Map {
#IPToTargetLine: L.Polygon | null = null;
/* Measure tool */
#measureReference: L.LatLng | null = null;
#measureLines: L.Polyline[] = [];
#measureMarkers: MeasureMarker[] = [];
#measures: Measure[] = [];
/* State variables */
#previousAppState: OlympusState = OlympusState.IDLE;
#previousAppSubstate: OlympusSubState = NO_SUBSTATE;
/**
*
@@ -338,6 +344,15 @@ export class Map extends L.Map {
shiftKey: false,
altKey: false,
ctrlKey: false,
}).addShortcut("clearMeasures", {
label: "Clear measures",
keyUpCallback: () => {
this.clearMeasures();
},
code: "KeyComma",
shiftKey: false,
altKey: false,
ctrlKey: false,
})
.addShortcut("toggleUnitLabels", {
label: "Hide/show labels",
@@ -854,7 +869,24 @@ export class Map extends L.Map {
this.getContainer().classList.remove(`explosion-cursor`);
["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`));
this.getContainer().classList.remove(`plus-cursor`);
this.getContainer().classList.remove(`measure-cursor`);
/* Clear the last measure if the state is changed */
if (this.#previousAppState === OlympusState.MEASURE) {
if (this.#measures.length > 0 && this.#measures[this.#measures.length - 1].isActive()) {
this.#measures[this.#measures.length - 1].remove();
this.#measures.pop();
if (this.#measures.length > 0) {
if (this.#measures[this.#measures.length - 1].getDistance() < 1) {
this.#measures[this.#measures.length - 1].remove();
this.#measures.pop();
} else {
this.#measures[this.#measures.length - 1].showEndMarker();
}
}
}
}
/* Operations to perform when entering a state */
if (state === OlympusState.IDLE) {
getApp().getUnitsManager()?.deselectAllUnits();
@@ -883,7 +915,12 @@ export class Map extends L.Map {
console.log(this.#contextAction);
} else if (state === OlympusState.DRAW) {
if (subState === DrawSubState.DRAW_CIRCLE || subState === DrawSubState.DRAW_POLYGON) this.getContainer().classList.add(`plus-cursor`);
} else if (state === OlympusState.MEASURE) {
this.getContainer().classList.add(`measure-cursor`);
}
this.#previousAppState = state;
this.#previousAppSubstate = subState;
}
#onDragStart(e: any) {
@@ -918,18 +955,10 @@ export class Map extends L.Map {
}
#onMouseDown(e: any) {
this.#originalMouseClickLatLng = e.latlng;
if (e.originalEvent?.button === 0) {
this.#isLeftMouseDown = true;
this.#leftMouseDownEpoch = Date.now();
/* If we are in the measure state there can only be a short click so immediately perform the action */
if (getApp().getState() === OlympusState.MEASURE) {
if (this.#measureLines.length > 0 && this.#measureReference)
this.#measureLines[this.#measureLines.length - 1].setLatLngs([this.#measureReference, e.latlng]);
this.#measureReference = e.latlng;
this.#measureLines.push(new L.Polyline([this.#measureReference, e.latlng], { color: "magenta" }).addTo(this));
this.#measureMarkers.push(new MeasureMarker(e.latlng, "", 0).addTo(this));
}
} else if (e.originalEvent?.button === 2) {
this.#isRightMouseDown = true;
this.#rightMouseDownEpoch = Date.now();
@@ -947,8 +976,6 @@ export class Map extends L.Map {
}
#onLeftShortClick(e: L.LeafletMouseEvent) {
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) {
this.#debounceTimeout = window.setTimeout(() => {
if (!this.#isSelecting) {
@@ -1060,12 +1087,25 @@ export class Map extends L.Map {
else if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
else getApp().setState(OlympusState.UNIT_CONTROL);
} else if (getApp().getState() === OlympusState.MEASURE) {
/* Do nothing, we already clicked on the mouse down callback */
const newMeasure = new Measure(this);
const previousMeasure = this.#measures[this.#measures.length - 1];
this.#measures.push(newMeasure);
newMeasure.onClick(e.latlng);
if (previousMeasure && previousMeasure.isActive()) {
previousMeasure.finish();
previousMeasure.hideEndMarker();
newMeasure.onMarkerMoved = (startLatLng, endLatLng) => {
previousMeasure.moveMarkers(null, startLatLng);
};
}
} else {
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
else getApp().setState(OlympusState.UNIT_CONTROL);
}
}
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
this.#debounceTimeout = null;
}, DEBOUNCE_MILLISECONDS);
}
}
@@ -1128,18 +1168,13 @@ export class Map extends L.Map {
if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
} else if (getApp().getState() === OlympusState.MEASURE) {
if (this.#measureLines.length > 0) this.#measureLines[this.#measureLines.length - 1].setLatLngs([this.#measureReference, e.latlng]);
if (this.#measureMarkers.length > 0 && this.#measureReference) {
const distance = this.#measureReference.distanceTo(e.latlng);
let distanceString = ""
if (distance > nmToM(1)) distanceString = `${mToNm(distance).toFixed(2)} NM`;
else distanceString = `${mToFt(distance).toFixed(2)} ft`;
const bearingTo = deg2rad(bearing(this.#measureReference.lat, this.#measureReference.lng, e.latlng.lat, e.latlng.lng, false));
const halfPoint = bearingAndDistanceToLatLng(this.#measureReference.lat, this.#measureReference.lng, bearingTo, distance/2);
const bearingString = `${(Math.floor(rad2deg(bearingTo) + 360) % 360)}°`;
this.#measureMarkers[this.#measureMarkers.length - 1].setLatLng(halfPoint);
this.#measureMarkers[this.#measureMarkers.length - 1].setRotationAngle(bearingTo + Math.PI / 2);
this.#measureMarkers[this.#measureMarkers.length - 1].setTextValue(`${distanceString} - ${bearingString}`);
if (this.#debounceTimeout === null) {
this.#measures[this.#measures.length - 1]?.onMouseMove(e.latlng);
let totalLength = 0;
this.#measures.forEach((measure) => {
measure.setTotalDistance(totalLength);
totalLength += measure.getDistance();
});
}
}
} else {
@@ -1208,11 +1243,6 @@ export class Map extends L.Map {
});
}
#clearMeasures() {
this.#measureLines.forEach((line) => line.removeFrom(this));
this.#measureMarkers.forEach((marker) => marker.removeFrom(this));
}
/* */
#panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
@@ -1294,4 +1324,9 @@ export class Map extends L.Map {
});
}
}
clearMeasures() {
this.#measures.forEach((measure) => measure.remove());
this.#measures = [];
}
}

View File

@@ -0,0 +1,57 @@
import { DivIcon, LatLngExpression, Map, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class MeasureEndMarker extends CustomMarker {
#rotationAngle: number = 0;
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, 16],
className: "leaflet-measure-end-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-measure-end-icon");
var img = document.createElement("img");
img.src = "images/markers/measure-end.svg";
img.onload = () => {
SVGInjector(img);
this.#applyRotation();
}
el.appendChild(img);
this.getElement()?.appendChild(el);
}
setRotationAngle(angle: number) {
this.#rotationAngle = angle;
this.#applyRotation();
}
getRotationAngle() {
return this.#rotationAngle;
}
onAdd(map: Map): this {
super.onAdd(map);
this.#applyRotation();
return this;
}
#applyRotation() {
const element = this.getElement();
if (element) {
const svg = element.querySelector("svg");
if (svg) svg.style.transform = `rotate(${this.#rotationAngle - Math.PI / 2}rad)`;
}
}
}

View File

@@ -2,9 +2,7 @@ import { Marker, LatLng, DivIcon, Map } from "leaflet";
export class MeasureMarker extends Marker {
#textValue: string;
#isEditable: boolean = false;
#rotationAngle: number; // Rotation angle in radians
#previousValue: string;
onValueUpdated: (value: number) => void = () => {};
onDeleteButtonClicked: () => void = () => {};
@@ -56,9 +54,7 @@ export class MeasureMarker extends Marker {
*/
setRotationAngle(angle: number) {
this.#rotationAngle = angle;
if (!this.#isEditable) {
this.#updateRotation();
}
this.#updateRotation();
}
/**

View File

@@ -0,0 +1,30 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class MeasureStartMarker 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, 16],
className: "leaflet-measure-start-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-measure-start-icon");
var img = document.createElement("img");
img.src = "images/markers/measure-start.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
}

View File

@@ -26,7 +26,7 @@ export class SmokeMarker extends CustomMarker {
);
var el = document.createElement("div");
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
el.style.fill = this.#color;
var img = document.createElement("img");
img.src = "images/markers/smoke.svg";
img.onload = () => SVGInjector(img);

View File

@@ -6,6 +6,7 @@
position: relative;
width: 40px;
height: 40px;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .4));
}
.airbase-icon svg {

View File

@@ -7,6 +7,7 @@
width: 100%;
height: 100%;
fill: white;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .4));
}
.bullseye-icon[data-coalition="red"] svg * {

View File

@@ -4,23 +4,36 @@
display: flex !important;
justify-content: center;
align-items: center;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .4));
width: fit-content !important;
height: fit-content !important;
margin: 0 !important;
translate: -50% -50%;
}
/* Container for the measure marker content */
.leaflet-measure-marker .container {
min-width: 150px;
transform-origin: center;
background-color: var(--background-steel);
color: white;
background-color: white;
color: #272727;
border-radius: 999px;
font-size: 13px;
align-content: center;
border: 2px solid transparent;
padding: 4px;
}
/* Text inside the measure marker */
.leaflet-measure-marker .text {
margin-left: 12px;
display: block;
margin-top: auto;
margin-bottom: auto;
font-weight: bolder;
text-wrap: nowrap;
padding-left: 5px;
padding-right: 5px;
}
.leaflet-measure-start-marker, .leaflet-measure-end-marker {
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .4));
}

View File

@@ -6,6 +6,7 @@
justify-content: center;
position: relative;
width: 100%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .4));
}
[data-object|="unit"].attack-cursor {

View File

@@ -0,0 +1,118 @@
import { LatLng, LeafletMouseEvent, Polyline } from "leaflet";
import { Map } from "./map";
import { MeasureMarker } from "./markers/measuremarker";
import { MeasureStartMarker } from "./markers/measurestartmarker";
import { MeasureEndMarker } from "./markers/measureendmarker";
import { bearing, deg2rad, midpoint, mToFt, mToNm, nmToM, rad2deg } from "../other/utils";
import { AppStateChangedEvent } from "../events";
import { OlympusState } from "../constants/constants";
export class Measure {
#active: boolean = false;
#map: Map;
#line: Polyline;
#measureMarker: MeasureMarker;
#startMarker: MeasureStartMarker;
#endMarker: MeasureEndMarker;
#totalDistance: number = 0;
onMarkerMoved: (startLatLng: LatLng, endLatLng: LatLng) => void = () => {};
constructor(map) {
this.#map = map;
}
onClick(latlng: LatLng) {
if (this.#startMarker === undefined) {
this.#startMarker = new MeasureStartMarker(latlng).addTo(this.#map);
this.#endMarker = new MeasureEndMarker(latlng).addTo(this.#map);
this.#line = new Polyline([this.#startMarker.getLatLng(), this.#endMarker.getLatLng()], { color: "#FFFFFF", dashArray: "5, 5" }).addTo(this.#map);
this.#measureMarker = new MeasureMarker(new LatLng(0, 0), "", 0).addTo(this.#map);
this.#startMarker.on("drag", (event) => {
this.#onMarkersMove();
});
this.#endMarker.on("drag", (event) => {
this.#onMarkersMove();
});
this.#active = true;
}
}
onMouseMove(latlng: LatLng) {
if (this.#endMarker !== undefined && this.isActive()) {
this.#endMarker.setLatLng(latlng);
this.#onMarkersMove();
}
}
remove() {
if (this.#startMarker !== undefined) this.#map.removeLayer(this.#startMarker);
if (this.#endMarker !== undefined) this.#map.removeLayer(this.#endMarker);
if (this.#line !== undefined) this.#map.removeLayer(this.#line);
if (this.#measureMarker !== undefined) this.#map.removeLayer(this.#measureMarker);
}
hideEndMarker() {
if (this.#endMarker !== undefined) this.#map.removeLayer(this.#endMarker);
}
showEndMarker() {
this.#onMarkersMove();
if (this.#endMarker !== undefined) this.#endMarker.addTo(this.#map);
}
moveMarkers(startLatLng: LatLng | null, endLatLng: LatLng | null) {
startLatLng && this.#startMarker.setLatLng(startLatLng);
endLatLng && this.#endMarker.setLatLng(endLatLng);
this.#onMarkersMove();
}
getDistance() {
return this.#startMarker.getLatLng().distanceTo(this.#endMarker.getLatLng());
}
finish() {
this.#active = false;
}
isActive() {
return this.#active;
}
setTotalDistance(distance: number) {
this.#totalDistance = distance;
}
#onMarkersMove() {
const distance = this.#startMarker.getLatLng().distanceTo(this.#endMarker.getLatLng());
let distanceString = "";
if (distance > nmToM(1)) distanceString = `${mToNm(distance).toFixed(distance < nmToM(10) ? 2 : 0)} NM`;
else distanceString = `${mToFt(distance).toFixed(0)} ft`;
const bearingTo = deg2rad(
bearing(this.#startMarker.getLatLng().lat, this.#startMarker.getLatLng().lng, this.#endMarker.getLatLng().lat, this.#endMarker.getLatLng().lng, false)
);
if (this.#totalDistance > 0) {
if (this.#totalDistance + this.getDistance() > nmToM(1)) distanceString += ` / ${mToNm(this.#totalDistance + this.getDistance()).toFixed(0)} NM`;
else distanceString += ` / ${mToFt(this.#totalDistance + this.getDistance()).toFixed(0)} ft`;
}
const halfPoint = midpoint(
this.#startMarker.getLatLng().lat,
this.#startMarker.getLatLng().lng,
this.#endMarker.getLatLng().lat,
this.#endMarker.getLatLng().lng
);
const bearingString = `${Math.floor(rad2deg(bearingTo) + 360) % 360}°`;
this.#measureMarker.setLatLng(halfPoint);
this.#measureMarker.setRotationAngle(bearingTo + Math.PI / 2);
this.#measureMarker.setTextValue(`${distanceString} - ${bearingString}`);
this.#endMarker.setRotationAngle(bearingTo);
this.#line.setLatLngs([this.#startMarker.getLatLng(), this.#endMarker.getLatLng()]);
this.onMarkerMoved(this.#startMarker.getLatLng(), this.#endMarker.getLatLng());
}
}

View File

@@ -139,12 +139,15 @@
background-image: url("/images/markers/target.svg");
height: 100%;
width: 100%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
}
.ol-spot-icon {
background-image: url("/images/markers/target.svg");
height: 100%;
width: 100%;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
}
.ol-text-icon {
@@ -159,26 +162,7 @@
.ol-smoke-icon {
opacity: 75%;
}
[data-color="white"].ol-smoke-icon {
fill: white;
}
[data-color="blue"].ol-smoke-icon {
fill: blue;
}
[data-color="red"].ol-smoke-icon {
fill: red;
}
[data-color="green"].ol-smoke-icon {
fill: green;
}
[data-color="orange"].ol-smoke-icon {
fill: orange;
filter: drop-shadow( 3px 3px 3px rgba(0, 0, 0, .2));
}
.ol-explosion-icon * {
@@ -225,6 +209,11 @@ path.leaflet-interactive:focus {
cursor: url("/images/cursors/plus.svg"), auto !important;
}
.measure-cursor {
cursor: url("/images/cursors/measure.svg"), auto !important;
}
#map-container.leaflet-grab {
cursor: url("/images/cursors/grab.svg") 16 16, auto;
}

View File

@@ -46,6 +46,32 @@ export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: numbe
return new LatLng(rad2deg(φ2), rad2deg(λ2));
}
export function midpoint(lat1: number, lon1: number, lat2: number, lon2: number, zoom: number = 10) {
const φ1 = deg2rad(lat1); // Convert latitude of point 1 from degrees to radians
const λ1 = deg2rad(lon1); // Convert longitude of point 1 from degrees to radians
const φ2 = deg2rad(lat2); // Convert latitude of point 2 from degrees to radians
const λ2 = deg2rad(lon2); // Convert longitude of point 2 from degrees to radians
// Convert point 1 to Mercator projection coordinates
const x1 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI + λ1);
const y1 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ1 / 2)));
// Convert point 2 to Mercator projection coordinates
const x2 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI + λ2);
const y2 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ2 / 2)));
// Calculate the midpoint in Mercator projection coordinates
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2;
// Convert the midpoint back to latitude and longitude
const λ = (2 * Math.PI * mx / Math.pow(2, zoom)) - Math.PI;
const φ = 2 * Math.atan(Math.exp(Math.PI - (2 * Math.PI * my) / Math.pow(2, zoom))) - Math.PI / 2;
// Return the midpoint as a LatLng object
return new LatLng(rad2deg(φ), rad2deg(λ));
}
export function ConvertDDToDMS(D: number, lng: boolean) {
var deg = 0 | (D < 0 ? (D = -D) : D);
var min = 0 | (((D += 1e-9) % 1) * 60);
@@ -93,6 +119,11 @@ export function latLngToMGRS(lat: number, lng: number, precision: number = 4): M
return undefined;
}
if (lng > 360 || lng < -180 || lat > 84 || lat < -80) {
console.error("latLngToMGRS: value outside of bounds");
return undefined;
}
const mgrs = new Converter({}).LLtoMGRS(lat, lng, precision);
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
if (match) {

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from "react";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { getApp } from "../../olympusapp";
import { NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
import { colors, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
import { OlStateButton } from "../components/olstatebutton";
import { faArrowLeft, faSmog } from "@fortawesome/free-solid-svg-icons";
import { LatLng } from "leaflet";
@@ -97,7 +97,7 @@ export function EffectSpawnMenu(props: { visible: boolean; compact: boolean; eff
<span className="my-auto text-white">Smoke color</span>
</div>
<div className="flex w-full gap-2">
{["white", "blue", "red", "green", "orange"].map((optionSmokeColor) => {
{[colors.WHITE, colors.BLUE, colors.RED, colors.GREEN, colors.ORANGE].map((optionSmokeColor) => {
return (
<OlStateButton
checked={smokeColor === optionSmokeColor}
@@ -132,7 +132,7 @@ export function EffectSpawnMenu(props: { visible: boolean; compact: boolean; eff
getApp().getServerManager().spawnSmoke(smokeColor, props.latlng);
getApp()
.getMap()
.addSmokeMarker(props.latlng, smokeColor ?? "white");
.addSmokeMarker(props.latlng, smokeColor ?? colors.WHITE);
}
}

View File

@@ -18,7 +18,7 @@ import {
SelectionEnabledChangedEvent,
ShortcutsChangedEvent,
} from "../../events";
import { faCopy, faObjectGroup, faPaste } from "@fortawesome/free-solid-svg-icons";
import { faCopy, faEraser, faObjectGroup, faPaste, faTape } from "@fortawesome/free-solid-svg-icons";
import { Shortcut } from "../../shortcut/shortcut";
import { ShortcutOptions, UnitData } from "../../interfaces";
import { Unit } from "../../unit/unit";
@@ -215,6 +215,40 @@ export function MapToolBar(props: {}) {
/>
</div>
)}
<div className="flex flex-col gap-1">
<OlStateButton
key={"measure"}
checked={appState === OlympusState.MEASURE}
icon={faTape}
tooltip={() => (
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["measure"]?.getOptions())}
<div className="my-auto">Enter measure mode</div>
</div>
)}
tooltipPosition="side"
onClick={() => {
getApp().setState(appState === OlympusState.MEASURE? OlympusState.IDLE : OlympusState.MEASURE);
}}
/>
</div>
<div className="flex flex-col gap-1">
<OlStateButton
key={"clearMeasures"}
checked={false}
icon={faEraser}
tooltip={() => (
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["clearMeasures"]?.getOptions())}
<div className="my-auto">Clear all measures</div>
</div>
)}
tooltipPosition="side"
onClick={() => {
getApp().getMap().clearMeasures();
}}
/>
</div>
</>
{reorderedActions.map((contextActionIt: ContextAction) => {

View File

@@ -5,6 +5,7 @@ import { RadioSink } from "../../audio/radiosink";
import { FaJetFighter, FaRadio, FaVolumeHigh } from "react-icons/fa6";
import { OlStateButton } from "../components/olstatebutton";
import { UnitSink } from "../../audio/unitsink";
import { colors } from "../../constants/constants";
export function RadiosSummaryPanel(props: {}) {
const [audioSinks, setAudioSinks] = useState([] as AudioSink[]);
@@ -41,7 +42,7 @@ export function RadiosSummaryPanel(props: {}) {
radioSink.setPtt(false);
}}
tooltip="Click to talk, lights up when receiving"
buttonColor={radioSink.getReceiving() ? "white" : null}
buttonColor={radioSink.getReceiving() ? colors.WHITE : undefined}
className="min-h-12 min-w-12"
>
<span className={`text-gray-200`}><FaRadio className={`

View File

@@ -77,9 +77,8 @@ 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/marker-icon.png",
shadowUrl: "images/markers/marker-shadow.png",
iconAnchor: [13, 41],
iconUrl: "images/markers/path.svg",
iconAnchor: [20, 43],
});
/**