From 2a9723b9327b6de64d499794d3ca005d127bc8b0 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 23 Oct 2025 18:06:29 +0200 Subject: [PATCH] Add image overlay import modal and menu option Introduces ImageOverlayModal for importing image overlays with user-specified corner coordinates. Adds a menu item to trigger the modal and integrates it into the main UI component. Also updates OlNumberInput to support an internalClassName prop for styling flexibility. --- frontend/react/src/constants/constants.ts | 1 + .../react/src/ui/components/olnumberinput.tsx | 6 +- .../react/src/ui/modals/imageoverlaymodal.tsx | 180 ++++++++++++++++++ frontend/react/src/ui/panels/mainmenu.tsx | 25 +++ frontend/react/src/ui/ui.tsx | 2 + 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 frontend/react/src/ui/modals/imageoverlaymodal.tsx 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/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() { + )}