From 9c5f0d67d6a754ab19ac45b91806c560faa9f737 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Fri, 17 Jul 2026 20:09:38 -0400 Subject: [PATCH 1/7] feat: move preset source to input_node --- src/components/VideoCustomPresetForm.tsx | 10 +- src/components/VideoPresetsPanel.tsx | 101 ------------------- src/components/panels/VideoControls.tsx | 120 +++++++++++++++++------ 3 files changed, 94 insertions(+), 137 deletions(-) delete mode 100644 src/components/VideoPresetsPanel.tsx diff --git a/src/components/VideoCustomPresetForm.tsx b/src/components/VideoCustomPresetForm.tsx index 9fc3e87..076b1ab 100644 --- a/src/components/VideoCustomPresetForm.tsx +++ b/src/components/VideoCustomPresetForm.tsx @@ -2,11 +2,11 @@ import React, { useState } from "react"; import CameraSourceDropdown from "./CameraSourceDropdown"; -import { VideoOutRequest, VideoSource } from "./panels/VideoControls"; +import { VideoSource } from "./panels/VideoControls"; interface CustomPresetFormProps { - onSubmit: (preset: VideoOutRequest) => void; + onSubmit: (id: string, sources: VideoSource[]) => void; } const VideoCustomPresetForm: React.FC = ({ onSubmit }) => { @@ -38,11 +38,7 @@ const VideoCustomPresetForm: React.FC = ({ onSubmit }) => const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const preset: VideoOutRequest = { - num_sources: sources.length, - sources, - }; - onSubmit(preset); + onSubmit("Custom", sources); }; const fieldStyle: React.CSSProperties = { display: "flex", diff --git a/src/components/VideoPresetsPanel.tsx b/src/components/VideoPresetsPanel.tsx deleted file mode 100644 index e00677a..0000000 --- a/src/components/VideoPresetsPanel.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import React from "react"; -import { VideoOutRequest } from "./panels/VideoControls"; - -interface VideoPresetsPanelProps { - onPresetSelect: (presetName: string, preset: VideoOutRequest) => void; -} - -const VideoPresetsPanel: React.FC = ({ - onPresetSelect, -}) => { - const presets: { name: string; preset: VideoOutRequest }[] = [ - { - name: "Drive", - preset: { - num_sources: 1, - sources: [{ name: "Drive", width: 100, height: 100, origin_x: 0, origin_y: 0 }], - }, - }, - { - name: "EEF", - preset: { - num_sources: 1, - sources: [{ name: "EndEffector", width: 100, height: 100, origin_x: 0, origin_y: 0 }], - }, - }, - { - name: "Science", - preset: { - num_sources: 4, - sources: [ - { name: "Microscope", width: 50, height: 50, origin_x: 0, origin_y: 0 }, - { name: "Strip", width: 50, height: 50, origin_x: 50, origin_y: 0 }, - { name: "Benedict", width: 50, height: 50, origin_x: 0, origin_y: 50 }, - { name: "HCL", width: 50, height: 50, origin_x: 50, origin_y: 50 }, - ], - }, - }, - { - name: "Belly", - preset: { - num_sources: 1, - sources: [{ name: "Bottom", width: 100, height: 100, origin_x: 0, origin_y: 0 }], - }, - }, - { - name: "Drive + EEF", - preset: { - num_sources: 2, - sources: [ - { name: "Drive", width: 100, height: 100, origin_x: 0, origin_y: 0 }, - { name: "EndEffector", width: 30, height: 30, origin_x: 70, origin_y: 0 }, - ], - }, - }, - { - name: "EEF + Drive", - preset: { - num_sources: 2, - sources: [ - { name: "EndEffector", width: 100, height: 100, origin_x: 0, origin_y: 0 }, - { name: "Drive", width: 30, height: 30, origin_x: 70, origin_y: 0 }, - ], - }, - }, - ]; - - const buttonStyle = (): React.CSSProperties => ({ - border: "1px solid #444", - borderRadius: "4px", - backgroundColor: "#1e1e1e", - color: "#f1f1f1", - padding: "0.45rem 0.6rem", - fontSize: "0.85rem", - whiteSpace: "nowrap", - }); - - return ( -
- {presets.map(({ name, preset }) => ( - - ))} -
- ); -}; - -export default VideoPresetsPanel; diff --git a/src/components/panels/VideoControls.tsx b/src/components/panels/VideoControls.tsx index ca1c735..3f01932 100644 --- a/src/components/panels/VideoControls.tsx +++ b/src/components/panels/VideoControls.tsx @@ -1,9 +1,8 @@ 'use client'; -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import VideoCustomPresetForm from "../VideoCustomPresetForm"; import ROSLIB from "roslib"; import { useROS } from "@/ros/ROSContext"; -import VideoPresetsPanel from "../VideoPresetsPanel"; import RtpStats from "../RtpStats"; export interface VideoSource { @@ -14,20 +13,18 @@ export interface VideoSource { origin_y: number; } -export interface VideoOutRequest { - num_sources: number; - sources: VideoSource[]; -} - interface VideoOutResponse { success: boolean; } -interface VideoOutResponse { - success: boolean; +type Preset = { + id: string; + name: string; } + const VideoControls: React.FC = () => { const { ros, connectionStatus: rosStatus } = useROS(); + const [presets, setPresets] = useState([]); const triggerIFrame = () => { if (!ros || rosStatus !== "connected") return; @@ -51,23 +48,6 @@ const VideoControls: React.FC = () => { console.log(`Setting bitrate to: ${bitrate} bps`); }; - const newPreset = (presetName: string, camRequest: VideoOutRequest) => { - if (!ros || rosStatus !== "connected") return; - - const startVideoSrv = new ROSLIB.Service({ - ros, - name: "/start_video", - serviceType: "interfaces/srv/VideoOut", - }); - - startVideoSrv.callService( - new ROSLIB.ServiceRequest(camRequest), - (response: VideoOutResponse) => { - console.log(response.success ? "Success" : "Failed"); - }, - ); - }; - const onRestart = () => { if (!ros || rosStatus !== "connected") return; const topic = new ROSLIB.Topic({ @@ -78,6 +58,7 @@ const VideoControls: React.FC = () => { topic.publish(new ROSLIB.Message({})); console.log("Stream restart triggered"); }; + const callVideoCaptureService = (serviceName: string, filename: string = "") => { if (!ros || rosStatus !== "connected") return; @@ -155,6 +136,43 @@ const VideoControls: React.FC = () => { console.log(`Setting framerate to: ${framerate} fps`); }; + const setPreset = (id: string) => { + if (!ros || rosStatus !== "connected") return; + const setPresetClient = new ROSLIB.Service({ + ros, + name: "/start_video", + serviceType: "interfaces/srv/VideoOut", + }); + const request = new ROSLIB.ServiceRequest({ name: id }); + setPresetClient.callService(request, (result: VideoOutResponse) => { + if (result.success) { + console.log(`Preset successfully set to ${id}`); + } else { + console.error("Failed to set preset", result); + } + }); + console.log(`Setting preset to: ${id}`); + }; + + const newPreset = (name: string, sources: VideoSource[]) => { + if (!ros || rosStatus !== "connected") return; + const setPresetClient = new ROSLIB.Service({ + ros, + name: "/input_node/new_preset", + serviceType: "interfaces/srv/VideoPreset", + }); + const request = new ROSLIB.ServiceRequest({ id: name.replaceAll(/\s/g, ''), name: name, sources: sources }); + setPresetClient.callService(request, (result: VideoOutResponse) => { + if (!result.success) { + console.log("Added new preset:", name); + setPresets((prev) => ([...prev, { id: name.replaceAll(/\s/g, ''), name: name }])) + } else { + console.error("Failed to add preset:", result); + } + }); + console.log("Adding new preset:", name); + }; + const connected = !!ros && rosStatus === "connected"; // Shift + s for snapshot useEffect(() => { @@ -181,6 +199,32 @@ const VideoControls: React.FC = () => { return () => document.removeEventListener("keydown", handleKeyDown); }, [connected]); + useEffect(() => { + if (!ros) { + console.error("ROS connection is not established."); + return; + } + const getParamsClient = new ROSLIB.Service({ + ros: ros, + name: "/input_node/get_parameters", + serviceType: "rcl_interfaces/srv/GetParameters", // adjust service type if it's differently named + }); + + const request = new ROSLIB.ServiceRequest({ names: ["presets"] }); + getParamsClient.callService(request, (response) => { + if (response) { + const namesRequest = new ROSLIB.ServiceRequest({ names: response.values[0].string_array_value.map((name: string) => (name + ".name"))}); + getParamsClient.callService(namesRequest, (namesResponse) => { + var newPresets: Preset[] = []; + for (const [idx, i] of namesResponse.values.entries()) { + newPresets.push({ id: response.values[0].string_array_value[idx], name: i.string_value }) + } + setPresets(newPresets); + }); + } + }); + }, [ros]); + const buttonStyle = (enabled: boolean) => ({ border: "1px solid #444", borderRadius: "4px", @@ -274,18 +318,36 @@ const VideoControls: React.FC = () => {
- newPreset(name, preset)} /> +
+ {presets.map(({ id, name }) => ( + + ))} +
- newPreset("Custom", preset)} /> + newPreset(id, sources)} />
); }; -export default VideoControls; \ No newline at end of file +export default VideoControls; From c393da034f780368da6624038ec8f5c2a657ee73 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Fri, 24 Jul 2026 19:25:48 -0400 Subject: [PATCH 2/7] new preset API --- src/app/dashboard/page.tsx | 4 +- src/components/VideoCustomPresetForm.tsx | 9 +- src/components/panels/VideoControls.tsx | 102 +++++++++++------------ 3 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index c92830f..726b082 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,7 +1,8 @@ "use client"; -import dynamic from 'next/dynamic'; import Layout from '@/components/Layout'; +import { Toaster } from "react-hot-toast"; +import dynamic from 'next/dynamic'; const MosaicDashboard = dynamic(() => import('@/components/panels/MosaicDashboard'), { ssr: false }); @@ -9,6 +10,7 @@ const Dashboard = () => { return ( + ); }; diff --git a/src/components/VideoCustomPresetForm.tsx b/src/components/VideoCustomPresetForm.tsx index 076b1ab..7ad1b4a 100644 --- a/src/components/VideoCustomPresetForm.tsx +++ b/src/components/VideoCustomPresetForm.tsx @@ -2,11 +2,11 @@ import React, { useState } from "react"; import CameraSourceDropdown from "./CameraSourceDropdown"; -import { VideoSource } from "./panels/VideoControls"; +import { VideoOutRequest, VideoSource } from "./panels/VideoControls"; interface CustomPresetFormProps { - onSubmit: (id: string, sources: VideoSource[]) => void; + onSubmit: (preset: VideoOutRequest) => void; } const VideoCustomPresetForm: React.FC = ({ onSubmit }) => { @@ -38,7 +38,10 @@ const VideoCustomPresetForm: React.FC = ({ onSubmit }) => const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - onSubmit("Custom", sources); + const preset: VideoOutRequest = { + sources, + }; + onSubmit(preset); }; const fieldStyle: React.CSSProperties = { display: "flex", diff --git a/src/components/panels/VideoControls.tsx b/src/components/panels/VideoControls.tsx index 3f01932..bba4665 100644 --- a/src/components/panels/VideoControls.tsx +++ b/src/components/panels/VideoControls.tsx @@ -4,6 +4,7 @@ import VideoCustomPresetForm from "../VideoCustomPresetForm"; import ROSLIB from "roslib"; import { useROS } from "@/ros/ROSContext"; import RtpStats from "../RtpStats"; +import toast from "react-hot-toast"; export interface VideoSource { name: string; @@ -13,18 +14,18 @@ export interface VideoSource { origin_y: number; } -interface VideoOutResponse { - success: boolean; +export interface VideoOutRequest { + sources: VideoSource[]; } -type Preset = { - id: string; - name: string; +interface VideoOutResponse { + success: boolean; } const VideoControls: React.FC = () => { const { ros, connectionStatus: rosStatus } = useROS(); - const [presets, setPresets] = useState([]); + const [presets, setPresets] = useState([]); + const [customSources, setCustomSources] = useState({ sources: []}); const triggerIFrame = () => { if (!ros || rosStatus !== "connected") return; @@ -136,42 +137,46 @@ const VideoControls: React.FC = () => { console.log(`Setting framerate to: ${framerate} fps`); }; - const setPreset = (id: string) => { + const setPreset = (name: string) => { if (!ros || rosStatus !== "connected") return; - const setPresetClient = new ROSLIB.Service({ - ros, - name: "/start_video", - serviceType: "interfaces/srv/VideoOut", - }); - const request = new ROSLIB.ServiceRequest({ name: id }); - setPresetClient.callService(request, (result: VideoOutResponse) => { - if (result.success) { - console.log(`Preset successfully set to ${id}`); - } else { - console.error("Failed to set preset", result); - } - }); - console.log(`Setting preset to: ${id}`); + if (name === "Custom") { + callCustom(customSources); + }else { + const setPresetClient = new ROSLIB.Service({ + ros, + name: "/request_preset", + serviceType: "interfaces/srv/VideoPreset", + }) + const request = new ROSLIB.ServiceRequest({ name: name }); + setPresetClient.callService(request, (response) => { + if (!response.success) { + toast.error("Failed to select preset: " + name) + } + }) + } }; - const newPreset = (name: string, sources: VideoSource[]) => { + const customPreset = (sources: VideoOutRequest) => { + if (!presets.includes("Custom")) { + setPresets((prev) => [...prev, "Custom"]); + } + + setCustomSources(sources); + callCustom(sources); + } + + const callCustom = (sources: VideoOutRequest) => { if (!ros || rosStatus !== "connected") return; - const setPresetClient = new ROSLIB.Service({ + const setCamClient = new ROSLIB.Service({ ros, - name: "/input_node/new_preset", - serviceType: "interfaces/srv/VideoPreset", + name: "/start_video", + serviceType: "interfaces/srv/VideoOut", }); - const request = new ROSLIB.ServiceRequest({ id: name.replaceAll(/\s/g, ''), name: name, sources: sources }); - setPresetClient.callService(request, (result: VideoOutResponse) => { - if (!result.success) { - console.log("Added new preset:", name); - setPresets((prev) => ([...prev, { id: name.replaceAll(/\s/g, ''), name: name }])) - } else { - console.error("Failed to add preset:", result); - } + const request = new ROSLIB.ServiceRequest(sources); + setCamClient.callService(request, (response: VideoOutResponse) => { + console.log("Setting custom preset:", response.success ? "Succeeded" : "Failed"); }); - console.log("Adding new preset:", name); - }; + } const connected = !!ros && rosStatus === "connected"; // Shift + s for snapshot @@ -204,23 +209,16 @@ const VideoControls: React.FC = () => { console.error("ROS connection is not established."); return; } - const getParamsClient = new ROSLIB.Service({ + const getPresetsClient = new ROSLIB.Service({ ros: ros, - name: "/input_node/get_parameters", - serviceType: "rcl_interfaces/srv/GetParameters", // adjust service type if it's differently named + name: "/list_presets", + serviceType: "unterfaces/srv/GetPresets", }); - const request = new ROSLIB.ServiceRequest({ names: ["presets"] }); - getParamsClient.callService(request, (response) => { + const request = new ROSLIB.ServiceRequest({}); + getPresetsClient.callService(request, (response) => { if (response) { - const namesRequest = new ROSLIB.ServiceRequest({ names: response.values[0].string_array_value.map((name: string) => (name + ".name"))}); - getParamsClient.callService(namesRequest, (namesResponse) => { - var newPresets: Preset[] = []; - for (const [idx, i] of namesResponse.values.entries()) { - newPresets.push({ id: response.values[0].string_array_value[idx], name: i.string_value }) - } - setPresets(newPresets); - }); + setPresets(response.presets); } }); }, [ros]); @@ -326,12 +324,12 @@ const VideoControls: React.FC = () => { gap: "0.5rem", }} > - {presets.map(({ id, name }) => ( + {presets.map((name) => ( @@ -343,7 +341,7 @@ const VideoControls: React.FC = () => {
- newPreset(id, sources)} /> +
From 73088e9d6d9a2ed5891f586718b13d3e113c8188 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Fri, 24 Jul 2026 19:27:26 -0400 Subject: [PATCH 3/7] formatting --- src/components/VideoCustomPresetForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoCustomPresetForm.tsx b/src/components/VideoCustomPresetForm.tsx index 7ad1b4a..96dfe8f 100644 --- a/src/components/VideoCustomPresetForm.tsx +++ b/src/components/VideoCustomPresetForm.tsx @@ -41,7 +41,7 @@ const VideoCustomPresetForm: React.FC = ({ onSubmit }) => const preset: VideoOutRequest = { sources, }; - onSubmit(preset); + onSubmit(preset); }; const fieldStyle: React.CSSProperties = { display: "flex", From 97a66282cb4c1a7845d9b1bf19f378f41a8e8388 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Thu, 30 Jul 2026 17:32:58 -0400 Subject: [PATCH 4/7] New PresetNode API --- src/components/VideoPresetsPanel.tsx | 73 ++++++++++++++++++++ src/components/panels/VideoControls.tsx | 92 +++++-------------------- 2 files changed, 89 insertions(+), 76 deletions(-) create mode 100644 src/components/VideoPresetsPanel.tsx diff --git a/src/components/VideoPresetsPanel.tsx b/src/components/VideoPresetsPanel.tsx new file mode 100644 index 0000000..ff1cd78 --- /dev/null +++ b/src/components/VideoPresetsPanel.tsx @@ -0,0 +1,73 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { VideoOutRequest, VideoSource } from "./panels/VideoControls"; +import ROSLIB from "roslib"; +import { useROS } from "@/ros/ROSContext"; + +interface VideoPresetsPanelProps { + onPresetSelect: (presetName: string, preset: VideoOutRequest) => void; +} + +type VideoPreset = { + name: string; + sources: VideoSource[]; +} + +const VideoPresetsPanel: React.FC = ({ + onPresetSelect, +}) => { + const { ros, connectionStatus: rosStatus } = useROS(); + const [presets, setPresets] = useState([]); + + const buttonStyle = (): React.CSSProperties => ({ + border: "1px solid #444", + borderRadius: "4px", + backgroundColor: "#1e1e1e", + color: "#f1f1f1", + padding: "0.45rem 0.6rem", + fontSize: "0.85rem", + whiteSpace: "nowrap", + }); + + useEffect(() => { + if (!ros) { + console.error("ROS connection is not established."); + return; + } + const getPresetsClient = new ROSLIB.Service({ + ros: ros, + name: "/list_presets", + serviceType: "interfaces/srv/GetPresets", + }); + + const request = new ROSLIB.ServiceRequest({}); + getPresetsClient.callService(request, (response) => { + if (response) { + setPresets(response.presets); + } + }); + }, [ros]); + return ( +
+ {presets.map(({ name, sources }) => ( + + ))} +
+ ); +}; + +export default VideoPresetsPanel; diff --git a/src/components/panels/VideoControls.tsx b/src/components/panels/VideoControls.tsx index bba4665..0a775cc 100644 --- a/src/components/panels/VideoControls.tsx +++ b/src/components/panels/VideoControls.tsx @@ -1,8 +1,9 @@ 'use client'; -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import VideoCustomPresetForm from "../VideoCustomPresetForm"; import ROSLIB from "roslib"; import { useROS } from "@/ros/ROSContext"; +import VideoPresetsPanel from "../VideoPresetsPanel"; import RtpStats from "../RtpStats"; import toast from "react-hot-toast"; @@ -24,8 +25,6 @@ interface VideoOutResponse { const VideoControls: React.FC = () => { const { ros, connectionStatus: rosStatus } = useROS(); - const [presets, setPresets] = useState([]); - const [customSources, setCustomSources] = useState({ sources: []}); const triggerIFrame = () => { if (!ros || rosStatus !== "connected") return; @@ -137,46 +136,24 @@ const VideoControls: React.FC = () => { console.log(`Setting framerate to: ${framerate} fps`); }; - const setPreset = (name: string) => { + const newPreset = (presetName: string, camRequest: VideoOutRequest) => { if (!ros || rosStatus !== "connected") return; - if (name === "Custom") { - callCustom(customSources); - }else { - const setPresetClient = new ROSLIB.Service({ - ros, - name: "/request_preset", - serviceType: "interfaces/srv/VideoPreset", - }) - const request = new ROSLIB.ServiceRequest({ name: name }); - setPresetClient.callService(request, (response) => { - if (!response.success) { - toast.error("Failed to select preset: " + name) - } - }) - } - }; - - const customPreset = (sources: VideoOutRequest) => { - if (!presets.includes("Custom")) { - setPresets((prev) => [...prev, "Custom"]); - } - - setCustomSources(sources); - callCustom(sources); - } - const callCustom = (sources: VideoOutRequest) => { - if (!ros || rosStatus !== "connected") return; - const setCamClient = new ROSLIB.Service({ + const startVideoSrv = new ROSLIB.Service({ ros, name: "/start_video", serviceType: "interfaces/srv/VideoOut", }); - const request = new ROSLIB.ServiceRequest(sources); - setCamClient.callService(request, (response: VideoOutResponse) => { - console.log("Setting custom preset:", response.success ? "Succeeded" : "Failed"); - }); - } + + startVideoSrv.callService( + new ROSLIB.ServiceRequest(camRequest), + (response: VideoOutResponse) => { + if (!response.success) { + toast.error("Failed to select preset: " + presetName) + } + }, + ); + }; const connected = !!ros && rosStatus === "connected"; // Shift + s for snapshot @@ -204,25 +181,6 @@ const VideoControls: React.FC = () => { return () => document.removeEventListener("keydown", handleKeyDown); }, [connected]); - useEffect(() => { - if (!ros) { - console.error("ROS connection is not established."); - return; - } - const getPresetsClient = new ROSLIB.Service({ - ros: ros, - name: "/list_presets", - serviceType: "unterfaces/srv/GetPresets", - }); - - const request = new ROSLIB.ServiceRequest({}); - getPresetsClient.callService(request, (response) => { - if (response) { - setPresets(response.presets); - } - }); - }, [ros]); - const buttonStyle = (enabled: boolean) => ({ border: "1px solid #444", borderRadius: "4px", @@ -316,32 +274,14 @@ const VideoControls: React.FC = () => {
-
- {presets.map((name) => ( - - ))} -
+ newPreset(name, preset)} />
- + newPreset("Custom", preset)} />
From c5f31db49f025d4667da6b9a05c02589f3e31a91 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Thu, 30 Jul 2026 17:34:38 -0400 Subject: [PATCH 5/7] fix --- src/components/panels/VideoControls.tsx | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/panels/VideoControls.tsx b/src/components/panels/VideoControls.tsx index 0a775cc..de60086 100644 --- a/src/components/panels/VideoControls.tsx +++ b/src/components/panels/VideoControls.tsx @@ -48,6 +48,25 @@ const VideoControls: React.FC = () => { console.log(`Setting bitrate to: ${bitrate} bps`); }; + const newPreset = (presetName: string, camRequest: VideoOutRequest) => { + if (!ros || rosStatus !== "connected") return; + + const startVideoSrv = new ROSLIB.Service({ + ros, + name: "/start_video", + serviceType: "interfaces/srv/VideoOut", + }); + + startVideoSrv.callService( + new ROSLIB.ServiceRequest(camRequest), + (response: VideoOutResponse) => { + if (!response.success) { + toast.error("Failed to select preset: " + presetName) + } + }, + ); + }; + const onRestart = () => { if (!ros || rosStatus !== "connected") return; const topic = new ROSLIB.Topic({ @@ -136,25 +155,6 @@ const VideoControls: React.FC = () => { console.log(`Setting framerate to: ${framerate} fps`); }; - const newPreset = (presetName: string, camRequest: VideoOutRequest) => { - if (!ros || rosStatus !== "connected") return; - - const startVideoSrv = new ROSLIB.Service({ - ros, - name: "/start_video", - serviceType: "interfaces/srv/VideoOut", - }); - - startVideoSrv.callService( - new ROSLIB.ServiceRequest(camRequest), - (response: VideoOutResponse) => { - if (!response.success) { - toast.error("Failed to select preset: " + presetName) - } - }, - ); - }; - const connected = !!ros && rosStatus === "connected"; // Shift + s for snapshot useEffect(() => { From b9a38f0434d15e3024deee4f5b7df4be8edf917b Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Thu, 30 Jul 2026 17:36:07 -0400 Subject: [PATCH 6/7] fix pt. 2 --- src/components/panels/VideoControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/panels/VideoControls.tsx b/src/components/panels/VideoControls.tsx index de60086..c8540b7 100644 --- a/src/components/panels/VideoControls.tsx +++ b/src/components/panels/VideoControls.tsx @@ -66,7 +66,7 @@ const VideoControls: React.FC = () => { }, ); }; - + const onRestart = () => { if (!ros || rosStatus !== "connected") return; const topic = new ROSLIB.Topic({ From bd873fb54a0cbf190178621eb066f6bc0818db99 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Thu, 30 Jul 2026 17:42:01 -0400 Subject: [PATCH 7/7] fix pt. 3 --- src/app/dashboard/page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index e27af38..351017b 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,5 +1,3 @@ -"use client"; - import Layout from '@/components/Layout'; import { Toaster } from "react-hot-toast"; import type { Metadata } from "next"; @@ -17,4 +15,4 @@ export default function Dashboard() { ); -} \ No newline at end of file +}