From 51c1235b684374ceef4fb1abeebef9f74e9f44d7 Mon Sep 17 00:00:00 2001 From: Samardeep Singh Date: Wed, 13 May 2026 18:08:08 -0600 Subject: [PATCH 1/2] add loading animation between capture and image load --- src/App.jsx | 94 ++++++++++++++++++++++++--------------- src/components/Canvas.jsx | 3 +- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index dbe2359..fb3725a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,15 +8,16 @@ import { } from './components/ui/card' import { TableBody, TableCell, TableHead, TableHeader, TableRow } from './components/ui/table.jsx'; import { ScrollArea } from './components/ui/scroll-area'; -import { Input } from './components/ui/input'; import { Button } from './components/ui/button'; import Sliders from './Sliders.jsx'; import { Tabs, TabsList, TabsTrigger } from './components/ui/tabs.jsx'; import Canvas from './components/Canvas.jsx'; +import { Spinner } from './components/ui/spinner.jsx'; import { AddTargetDialog } from './components/Dialog.jsx'; import { saveTarget } from './targets.js'; import { DistanceTable } from './components/DistancesTable.jsx'; import TargetViewer from './components/TargetViewer.jsx'; +import { Navbar } from './components/Navbar.jsx'; function App() { const [uavStatus, setUavStatus] = useState({ @@ -29,8 +30,36 @@ function App() { const [logs, setLogs] = useState([]); const wsConnRef = useRef(new UAVConnection()); const [isConnected, setIsConnected] = useState(false); - wsConnRef.current.onWSOpen = () => { + const [autoReconnect, setAutoReconnect] = useState(false); + const [ledOn, setLedOn] = useState(false); + const autoReconnectRef = useRef(false); + const lastUrlRef = useRef(null); + const manualDisconnectRef = useRef(false); + + const handleConnect = (url) => { + lastUrlRef.current = url; + manualDisconnectRef.current = false; + wsConnRef.current.connect(url); + }; + + const handleDisconnect = () => { + manualDisconnectRef.current = true; + wsConnRef.current.disconnect(); + }; + + const handleAutoReconnectChange = (value) => { + setAutoReconnect(value); + autoReconnectRef.current = value; + }; + const handleLedChange = (value) => { + setLedOn(value); + if (isConnected) { + wsConnRef.current.sendMessage({ type: "led", state: value }); + } + }; + + wsConnRef.current.onWSOpen = () => { setUavStatus(prev => ({ ...prev, connection: "yes", @@ -39,13 +68,21 @@ function App() { setIsConnected(true); }; wsConnRef.current.onWSClose = () => { - setUavStatus(prev => ({ ...prev, connection: "no", mode: "null" })); setIsConnected(false); + + if (autoReconnectRef.current && !manualDisconnectRef.current && lastUrlRef.current) { + setTimeout(() => { + if (autoReconnectRef.current && lastUrlRef.current) { + wsConnRef.current.connect(lastUrlRef.current); + } + }, 3000); + } + manualDisconnectRef.current = false; }; const messageHandler = useCallback((json) => { @@ -112,12 +149,17 @@ function App() { }, []); return ( -
- + -
+
@@ -128,34 +170,10 @@ function App() {
-
); } -function ConnectComponent({ isConnected, connect, disconnect }) { - const [url, setUrl] = useState(""); - const connectBtnTxt = isConnected ? "Disconnect" : "Connect"; - const handleSubmit = (e) => { - e.preventDefault(); - if (!url) { - return; - } - if (isConnected) { - disconnect(); - } - else { - connect(url); - } - }; - return ( -
- - setUrl(e.target.value)} placeholder="http://127.0.0.1:800" /> - -
- ); -} function UAVStatusComponent({ label = "", value }) { return ( @@ -189,6 +207,7 @@ function UAVStatus({ status }) { function ImageLayout({ filename, isConnected, sendFunc }) { const dialogPendingRef = useRef(null); const [dialogOpen, setDialogOpen] = useState(false); + const [isImageLoading, setIsImageLoading] = useState(false); const imageUriToId = (uri) => { const parts = uri.split("/").filter(Boolean); @@ -210,7 +229,7 @@ function ImageLayout({ filename, isConnected, sendFunc }) { return; } - //request image from UAV + setIsImageLoading(true); sendFunc({ type: "image", message: "capture" @@ -263,12 +282,17 @@ function ImageLayout({ filename, isConnected, sendFunc }) { {tabPick === "image" && ( - + {dialogOpen && setDialogOpen(false)} setFormData={data => { dialogPendingRef.current?.resolve(data) dialogPendingRef.current = null; }}>} - + {isImageLoading && ( +
+ +
+ )} + setIsImageLoading(false)} className="object-contain max-w-full max-h-full">
)} diff --git a/src/components/Canvas.jsx b/src/components/Canvas.jsx index 5c415cd..5256e19 100644 --- a/src/components/Canvas.jsx +++ b/src/components/Canvas.jsx @@ -3,7 +3,7 @@ import { useEffect, useRef } from "react"; const MAG_LENS_CSS = 60; const MAG_ZOOM = 2.5; -function Canvas({ imgSrc, onPointsClicked, className }) { +function Canvas({ imgSrc, onPointsClicked, onImageLoaded, className }) { const canvasRef = useRef(null); const magnifierRef = useRef(null); const containerRef = useRef(null); @@ -82,6 +82,7 @@ function Canvas({ imgSrc, onPointsClicked, className }) { canvas.height = image.height; ctx.drawImage(image, 0, 0); bitmapReady = true; + onImageLoaded?.(); }; image.src = imgSrc; From c19a669d1df0c31c98a34505bf634029ff2d5d23 Mon Sep 17 00:00:00 2001 From: Samardeep Singh Date: Wed, 13 May 2026 18:08:25 -0600 Subject: [PATCH 2/2] add navbar with connection settings, auto-reconnect, and LED toggle --- mock-uav-server/server.js | 10 ++++++++ src/components/Navbar.jsx | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/components/Navbar.jsx diff --git a/mock-uav-server/server.js b/mock-uav-server/server.js index ce874db..e320bd7 100644 --- a/mock-uav-server/server.js +++ b/mock-uav-server/server.js @@ -120,6 +120,16 @@ wss.on("connection", (ws) => { message: `aim vertical=${msg.vertical} horizontal=${msg.horizontal}`, severity: "normal", }); + return; + } + + if (msg.type === "led") { + sendJson(ws, { + type: "log", + message: `LED ${msg.state ? "on" : "off"}`, + severity: "normal", + }); + return; } }); }); diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx new file mode 100644 index 0000000..eab90fd --- /dev/null +++ b/src/components/Navbar.jsx @@ -0,0 +1,49 @@ +import { useState } from 'react'; +import { Input } from './ui/input'; +import { Button } from './ui/button'; +import { Switch } from './ui/switch'; +import { Separator } from './ui/separator'; + +export function Navbar({ isConnected, connect, disconnect, autoReconnect, onAutoReconnectChange, ledOn, onLedChange }) { + const [url, setUrl] = useState(""); + + const handleSubmit = (e) => { + e.preventDefault(); + if (!url) return; + if (isConnected) { + disconnect(); + } else { + connect(url); + } + }; + + return ( + + ); +}