mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'release-candidate' into features/redgreen-unit
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { LatLng } from "leaflet";
|
||||
import { ConvertDDToDMS, 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 MGRS = latLngToMGRS(props.location.lat, props.location.lng, 6);
|
||||
if (referenceSystem === "MGRS") {
|
||||
@@ -17,6 +17,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
||||
? props.onClick
|
||||
: (ev) => {
|
||||
setReferenceSystem("LatLngDec");
|
||||
props.onRefSystemChange ? props.onRefSystemChange("LatLngDec") : null;
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +45,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
||||
? props.onClick
|
||||
: (ev) => {
|
||||
setReferenceSystem("LatLngDMS");
|
||||
props.onRefSystemChange ? props.onRefSystemChange("LatLngDMS") : null;
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -82,7 +84,8 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
||||
props.onClick
|
||||
? props.onClick
|
||||
: (ev) => {
|
||||
setReferenceSystem("MGRS");
|
||||
setReferenceSystem("LatLngDDM");
|
||||
props.onRefSystemChange ? props.onRefSystemChange("LatLngDDM") : null;
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
@@ -109,6 +112,46 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (referenceSystem === "LatLngDDM") {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
${props.className ?? ""}
|
||||
my-auto flex cursor-pointer justify-between gap-2 bg-olympus-400 p-2
|
||||
text-white
|
||||
`}
|
||||
onClick={
|
||||
props.onClick
|
||||
? props.onClick
|
||||
: (ev) => {
|
||||
setReferenceSystem("MGRS");
|
||||
props.onRefSystemChange ? props.onRefSystemChange("MGRS") : null;
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<span
|
||||
className={`
|
||||
w-5 rounded-sm bg-white text-center font-bold text-olympus-700
|
||||
`}
|
||||
>
|
||||
{props.location.lat >= 0 ? "N" : "S"}
|
||||
</span>
|
||||
{DDToDDM(props.location.lat)}
|
||||
</div>
|
||||
<div className="flex w-[50%] gap-2">
|
||||
<span
|
||||
className={`
|
||||
w-5 rounded-sm bg-white text-center font-bold text-olympus-700
|
||||
`}
|
||||
>
|
||||
{props.location.lng >= 0 ? "E" : "W"}
|
||||
</span>
|
||||
{DDToDDM(props.location.lng)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,14 @@ export function MapContextMenu(props: {}) {
|
||||
});
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
||||
MapContextMenuRequestEvent.on((latlng) => {
|
||||
setUnit(null);
|
||||
setLatLng(latlng);
|
||||
const containerPoint = getApp().getMap().latLngToContainerPoint(latlng);
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
|
||||
});
|
||||
UnitContextMenuRequestEvent.on((unit) => {
|
||||
setLatLng(null);
|
||||
setUnit(unit);
|
||||
const containerPoint = getApp().getMap().latLngToContainerPoint(unit.getPosition());
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
@@ -100,8 +102,8 @@ export function MapContextMenu(props: {}) {
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex min-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
absolute flex min-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -62,7 +62,7 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
|
||||
return (
|
||||
<Modal open={props.open} size={"sm"}>
|
||||
<div className="flex flex-col gap-4 h-full w-full">
|
||||
<div className="flex h-full w-full flex-col gap-4">
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
@@ -97,7 +97,7 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
className={`flex flex-wrap gap-2 font-bold text-orange-600`}
|
||||
>
|
||||
{inUseShortcuts.map((shortcut) => (
|
||||
<span>{shortcut.getOptions().label}</span>
|
||||
<span key={shortcut.getId()}>{shortcut.getOptions().label}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end mt-auto ">
|
||||
<div className="mt-auto flex justify-end">
|
||||
{shortcut && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -91,14 +91,14 @@ export function ControlsPanel(props: {}) {
|
||||
target: faFighterJet,
|
||||
text: "Show unit actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||
text: "Activate group movement",
|
||||
});
|
||||
controls.push({
|
||||
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||
text: "Rotate formation",
|
||||
});
|
||||
//controls.push({
|
||||
// actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||
// text: "Activate group movement",
|
||||
//});
|
||||
//controls.push({
|
||||
// actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||
// text: "Rotate formation",
|
||||
//});
|
||||
} else if (appState === OlympusState.SPAWN) {
|
||||
controls = [
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlLocation } from "../components/ollocation";
|
||||
import { LatLng } from "leaflet";
|
||||
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain } from "react-icons/fa6";
|
||||
import { BullseyesDataChangedEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
||||
import { computeBearingRangeString, mToFt } from "../../other/utils";
|
||||
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain, FaCopy, FaXmark } from "react-icons/fa6";
|
||||
import { BullseyesDataChangedEvent, CoordinatesFreezeEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
||||
import { computeBearingRangeString, ConvertDDToDMS, DDToDDM, isTrustedEnvironment, latLngToMGRS, mToFt, zeroAppend } from "../../other/utils";
|
||||
import { Bullseye } from "../../mission/bullseye";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { getApp } from "../../olympusapp";
|
||||
|
||||
export function CoordinatesPanel(props: {}) {
|
||||
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 [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
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(() => {
|
||||
MouseMovedEvent.on((latlng, elevation) => {
|
||||
setLatlng(latlng);
|
||||
@@ -23,18 +27,43 @@ export function CoordinatesPanel(props: {}) {
|
||||
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
||||
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 (
|
||||
<div
|
||||
className={`
|
||||
flex w-full flex-col items-center justify-between gap-2 rounded-lg
|
||||
bg-gray-200 px-3 py-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
flex w-full flex-col justify-between gap-2 rounded-lg bg-gray-200 px-3
|
||||
py-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
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 ? (
|
||||
<FaChevronDown className="cursor-pointer" />
|
||||
) : (
|
||||
@@ -44,11 +73,7 @@ export function CoordinatesPanel(props: {}) {
|
||||
)}
|
||||
</div>
|
||||
{open && bullseyes && (
|
||||
<div
|
||||
className={`
|
||||
flex w-full flex-col items-start justify-start gap-2
|
||||
`}
|
||||
>
|
||||
<div className={`flex w-full flex-col items-start justify-start gap-2`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex min-w-64 max-w-64 items-start justify-between gap-2
|
||||
@@ -98,26 +123,74 @@ export function CoordinatesPanel(props: {}) {
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`
|
||||
flex w-full items-center justify-between pointer-events-all
|
||||
`}
|
||||
className={`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>
|
||||
|
||||
{open && (
|
||||
<div className="flex w-full items-center justify-start">
|
||||
<span
|
||||
className={`
|
||||
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
|
||||
text-olympus-700
|
||||
`}
|
||||
{open && [
|
||||
<div
|
||||
className={`
|
||||
flex w-full min-w-64 max-w-64 items-center justify-between
|
||||
`}
|
||||
>
|
||||
<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>
|
||||
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className={`
|
||||
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@ export function InfoBar(props: {}) {
|
||||
<div
|
||||
key={idx}
|
||||
className={`
|
||||
absolute w-fit translate-x-[-50%] gap-2 text-nowrap rounded-full
|
||||
absolute w-[250px] translate-x-[-50%] gap-2 rounded-full
|
||||
bg-olympus-800/90 px-4 py-2 text-center text-sm text-white
|
||||
shadow-md backdrop-blur-lg backdrop-grayscale
|
||||
sm:w-fit sm:text-nowrap
|
||||
`}
|
||||
style={{ top: `${idx * 20}px` }}
|
||||
>
|
||||
|
||||
@@ -98,6 +98,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
onClick={() => {
|
||||
if (referenceSystem === "MGRS") setReferenceSystem("LatLngDec");
|
||||
else if (referenceSystem === "LatLngDec") setReferenceSystem("LatLngDMS");
|
||||
else if (referenceSystem === "LatLngDMS") setReferenceSystem("LatLngDDM");
|
||||
else setReferenceSystem("MGRS");
|
||||
}}
|
||||
referenceSystem={referenceSystem}
|
||||
@@ -133,6 +134,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
onClick={() => {
|
||||
if (referenceSystem === "MGRS") setReferenceSystem("LatLngDec");
|
||||
else if (referenceSystem === "LatLngDec") setReferenceSystem("LatLngDMS");
|
||||
else if (referenceSystem === "LatLngDMS") setReferenceSystem("LatLngDDM");
|
||||
else setReferenceSystem("MGRS");
|
||||
}}
|
||||
referenceSystem={referenceSystem}
|
||||
@@ -168,6 +170,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
onClick={() => {
|
||||
if (referenceSystem === "MGRS") setReferenceSystem("LatLngDec");
|
||||
else if (referenceSystem === "LatLngDec") setReferenceSystem("LatLngDMS");
|
||||
else if (referenceSystem === "LatLngDMS") setReferenceSystem("LatLngDDM");
|
||||
else setReferenceSystem("MGRS");
|
||||
}}
|
||||
referenceSystem={referenceSystem}
|
||||
|
||||
@@ -140,12 +140,16 @@ export function MapToolBar(props: {}) {
|
||||
{!scrolledTop && (
|
||||
<FaChevronUp
|
||||
className={`
|
||||
absolute top-0 h-6 w-full rounded-lg px-3.5 py-1 text-gray-200
|
||||
absolute top-0 h-6 w-full rounded-lg bg-red-500 px-3.5 py-1
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className={`flex flex-col gap-2 overflow-y-auto no-scrollbar p-2`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
<div className={`
|
||||
pointer-events-auto flex flex-col gap-2 overflow-y-auto no-scrollbar
|
||||
p-2
|
||||
`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
|
||||
@@ -103,15 +103,13 @@ export function UI() {
|
||||
<GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<UnitExplosionMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
{/*}<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AWACSMenu open={appState === OlympusState.AWACS} onClose={() => getApp().setState(OlympusState.IDLE)} />{*/}
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
|
||||
|
||||
|
||||
<SideBar />
|
||||
<InfoBar />
|
||||
@@ -131,15 +129,15 @@ export function UI() {
|
||||
backdrop-blur-sm
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
flex w-[400px] flex-col items-center justify-center gap-4
|
||||
`}>
|
||||
<div
|
||||
className={`
|
||||
flex w-[400px] flex-col items-center justify-center gap-4
|
||||
`}
|
||||
>
|
||||
<div className="bouncing-ball">
|
||||
<img
|
||||
src="images/olympus-500x500.png"
|
||||
alt="Olympus Logo"
|
||||
className={`ball-logo`}
|
||||
/>
|
||||
<img src="images/olympus-500x500.png" alt="Olympus Logo" className={`
|
||||
ball-logo
|
||||
`} />
|
||||
</div>
|
||||
{!connectedOnce && <div>Establishing connection</div>}
|
||||
{connectedOnce && <div>Connection lost</div>}
|
||||
|
||||
Reference in New Issue
Block a user