diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 28de5ad2..71f1896b 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -360,6 +360,7 @@ export enum OlympusState { MEASURE = "Measure", TRAINING = "Training", ADMIN = "Admin", + IMPORT_IMAGE_OVERLAY = "Import image overlay" } export const NO_SUBSTATE = "No substate"; diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index bb34eb8a..ed3cdacc 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -443,7 +443,7 @@ export class Map extends L.Map { ctrlKey: false, }); - for (let contextActionName in ContextActions) { + for (const contextActionName of Object.keys(ContextActions) as Array) { const contextAction = ContextActions[contextActionName] as ContextAction; if (contextAction.getOptions().code) { getApp() @@ -631,13 +631,13 @@ export class Map extends L.Map { return this.#spawnHeading; } - addStarredSpawnRequestTable(key, spawnRequestTable: SpawnRequestTable, quickAccessName: string) { + addStarredSpawnRequestTable(key: string, spawnRequestTable: SpawnRequestTable, quickAccessName: string) { this.#starredSpawnRequestTables[key] = spawnRequestTable; this.#starredSpawnRequestTables[key].quickAccessName = quickAccessName; StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables); } - removeStarredSpawnRequestTable(key) { + removeStarredSpawnRequestTable(key: string) { if (key in this.#starredSpawnRequestTables) delete this.#starredSpawnRequestTables[key]; StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables); } @@ -678,7 +678,7 @@ export class Map extends L.Map { } setHiddenType(key: string, value: boolean) { - this.#hiddenTypes[key] = value; + this.#hiddenTypes[key as keyof MapHiddenTypes] = value; HiddenTypesChangedEvent.dispatch(this.#hiddenTypes); } @@ -788,13 +788,13 @@ export class Map extends L.Map { return smokeMarker; } - setOption(key, value) { + setOption(key: K, value: MapOptions[K]) { this.#options[key] = value; - MapOptionsChangedEvent.dispatch(this.#options, key); + MapOptionsChangedEvent.dispatch(this.#options, key as keyof MapOptions); } - setOptions(options) { - this.#options = { ...options }; + setOptions(options: Partial) { + this.#options = { ...this.#options, ...options } as MapOptions; MapOptionsChangedEvent.dispatch(this.#options); } @@ -1071,7 +1071,7 @@ export class Map extends L.Map { false, undefined, undefined, - (hash) => { + (hash: string) => { this.addTemporaryMarker( e.latlng, this.#spawnRequestTable?.unit.unitType ?? "unknown", @@ -1239,7 +1239,7 @@ export class Map extends L.Map { this.#lastMouseCoordinates = e.latlng; MouseMovedEvent.dispatch(e.latlng); - getGroundElevation(e.latlng, (elevation) => { + getGroundElevation(e.latlng, (elevation: number) => { MouseMovedEvent.dispatch(e.latlng, elevation); }); @@ -1366,8 +1366,8 @@ export class Map extends L.Map { .filter((unit) => !unit.getHuman()); Object.keys(this.#destinationPreviewMarkers).forEach((ID) => { - this.#destinationPreviewMarkers[ID].removeFrom(this); - delete this.#destinationPreviewMarkers[ID]; + this.#destinationPreviewMarkers[parseInt(ID)].removeFrom(this); + delete this.#destinationPreviewMarkers[parseInt(ID)]; }); if (this.#keepRelativePositions) { @@ -1385,7 +1385,7 @@ export class Map extends L.Map { #moveDestinationPreviewMarkers() { if (this.#keepRelativePositions) { Object.entries(getApp().getUnitsManager().computeGroupDestination(this.#destinationRotationCenter, this.#destinationRotation)).forEach(([ID, latlng]) => { - this.#destinationPreviewMarkers[ID]?.setLatLng(latlng); + this.#destinationPreviewMarkers[parseInt(ID)]?.setLatLng(latlng); }); } else { Object.values(this.#destinationPreviewMarkers).forEach((marker) => { diff --git a/frontend/react/src/ui/components/olnumberinput.tsx b/frontend/react/src/ui/components/olnumberinput.tsx index 0f981a4f..d10d9594 100644 --- a/frontend/react/src/ui/components/olnumberinput.tsx +++ b/frontend/react/src/ui/components/olnumberinput.tsx @@ -8,6 +8,7 @@ export function OlNumberInput(props: { max: number; minLength?: number; className?: string; + internalClassName?: string; tooltip?: string | (() => JSX.Element | JSX.Element[]); tooltipPosition?: string; tooltipRelativeToParent?: boolean; @@ -34,7 +35,10 @@ export function OlNumberInput(props: { `} >
{ setHoverTimeout( diff --git a/frontend/react/src/ui/modals/imageoverlaymodal.tsx b/frontend/react/src/ui/modals/imageoverlaymodal.tsx new file mode 100644 index 00000000..9a353a7d --- /dev/null +++ b/frontend/react/src/ui/modals/imageoverlaymodal.tsx @@ -0,0 +1,180 @@ +import React, { useEffect, useState } from "react"; +import { Modal } from "./components/modal"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { getApp } from "../../olympusapp"; +import { NO_SUBSTATE, OlympusState } from "../../constants/constants"; +import { AppStateChangedEvent } from "../../events"; +import { ImageOverlay, LatLng, LatLngBounds } from "leaflet"; +import { OlNumberInput } from "../components/olnumberinput"; +import { OlStringInput } from "../components/olstringinput"; + +export function ImageOverlayModal(props: { open: boolean }) { + const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); + const [appSubState, setAppSubState] = useState(NO_SUBSTATE); + const [bound1Lat, setBound1Lat] = useState("0"); + const [bound1Lon, setBound1Lon] = useState("0"); + const [bound2Lat, setBound2Lat] = useState("0"); + const [bound2Lon, setBound2Lon] = useState("0"); + const [importData, setImportData] = useState(""); + const [showWarning, setShowWarning] = useState(false); + + useEffect(() => { + AppStateChangedEvent.on((appState, appSubState) => { + setAppState(appState); + setAppSubState(appSubState); + }); + }, []); + + useEffect(() => { + if (appState !== OlympusState.IMPORT_IMAGE_OVERLAY) return; + + setImportData(""); + var input = document.createElement("input"); + input.type = "file"; + + input.onchange = async (e) => { + // @ts-ignore TODO + var file = e.target?.files[0]; + var reader = new FileReader(); + // Read the file content as image data URL + reader.readAsDataURL(file); + reader.onload = (readerEvent) => { + // @ts-ignore TODO + var content = readerEvent.target.result; + if (content) { + setImportData(content as string); + } + }; + }; + + input.click(); + }, [appState, appSubState]); + + return ( + +
+
+ + Import Image Overlay + + + Enter the corner coordinates of the image overlay to be imported. +
+
+
Corner 1 latitude
+
+ { + setBound1Lat(ev.target.value); + }} + /> +
+
Corner 1 longitude
+
+ { + setBound1Lon(ev.target.value); + }} + /> +
+
+
+
Corner 2 latitude
+
+ { + setBound2Lat(ev.target.value); + }} + /> +
+
Corner 2 longitude
+
+ { + setBound2Lon(ev.target.value); + }} + /> +
+
+
+ Please enter valid latitude and longitude values in decimal degrees format (e.g. 37.7749, -122.4194). Latitude must be between -90 and 90, and longitude must be between -180 and 180. +
+
+
+ +
+ + + +
+
+
+ ); +} diff --git a/frontend/react/src/ui/panels/mainmenu.tsx b/frontend/react/src/ui/panels/mainmenu.tsx index 92c5fa72..781d514b 100644 --- a/frontend/react/src/ui/panels/mainmenu.tsx +++ b/frontend/react/src/ui/panels/mainmenu.tsx @@ -142,6 +142,31 @@ export function MainMenu(props: { open: boolean; onClose: () => void; children?: />
+
{ + getApp().setState(OlympusState.IMPORT_IMAGE_OVERLAY); + }} + > + {/**/} + Import image overlay +
+ +
+
); diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx index 65c33705..858b5425 100644 --- a/frontend/react/src/ui/ui.tsx +++ b/frontend/react/src/ui/ui.tsx @@ -31,6 +31,7 @@ import { ImportExportModal } from "./modals/importexportmodal"; import { WarningModal } from "./modals/warningmodal"; import { TrainingModal } from "./modals/trainingmodal"; import { AdminModal } from "./modals/adminmodal"; +import { ImageOverlayModal } from "./modals/imageoverlaymodal"; export function UI() { const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); @@ -74,6 +75,7 @@ export function UI() { + )}