Skip to content

Commit 4f54bfc

Browse files
Merge pull request #1 from DavidLMS/save-port-on-every-change
[FRONTEND] Add real-time configuration (ports and calibration) persistence
2 parents a3ea9bd + df04e9d commit 4f54bfc

File tree

4 files changed

+241
-41
lines changed

4 files changed

+241
-41
lines changed

src/components/landing/DirectFollowerModal.tsx

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
import { Settings } from "lucide-react";
2121
import PortDetectionModal from "@/components/ui/PortDetectionModal";
2222
import PortDetectionButton from "@/components/ui/PortDetectionButton";
23+
import { useApi } from "@/contexts/ApiContext";
24+
import { useAutoSave } from "@/hooks/useAutoSave";
2325

2426
interface DirectFollowerModalProps {
2527
open: boolean;
@@ -44,28 +46,40 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
4446
isLoadingConfigs,
4547
onStart,
4648
}) => {
49+
const { baseUrl, fetchWithHeaders } = useApi();
50+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
4751
const [showPortDetection, setShowPortDetection] = useState(false);
4852

49-
// Load saved follower port on component mount
53+
// Load saved follower port and configuration on component mount
5054
useEffect(() => {
51-
const loadSavedPort = async () => {
55+
const loadSavedData = async () => {
5256
try {
53-
const followerResponse = await fetch(
54-
"http://localhost:8000/robot-port/follower"
57+
// Load follower port
58+
const followerResponse = await fetchWithHeaders(
59+
`${baseUrl}/robot-port/follower`
5560
);
5661
const followerData = await followerResponse.json();
5762
if (followerData.status === "success" && followerData.default_port) {
5863
setFollowerPort(followerData.default_port);
5964
}
65+
66+
// Load follower configuration
67+
const followerConfigResponse = await fetchWithHeaders(
68+
`${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
69+
);
70+
const followerConfigData = await followerConfigResponse.json();
71+
if (followerConfigData.status === "success" && followerConfigData.default_config) {
72+
setFollowerConfig(followerConfigData.default_config);
73+
}
6074
} catch (error) {
61-
console.error("Error loading saved follower port:", error);
75+
console.error("Error loading saved data:", error);
6276
}
6377
};
6478

65-
if (open) {
66-
loadSavedPort();
79+
if (open && followerConfigs.length > 0) {
80+
loadSavedData();
6781
}
68-
}, [open, setFollowerPort]);
82+
}, [open, setFollowerPort, setFollowerConfig, followerConfigs, baseUrl, fetchWithHeaders]);
6983

7084
const handlePortDetection = () => {
7185
setShowPortDetection(true);
@@ -75,6 +89,20 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
7589
setFollowerPort(port);
7690
};
7791

92+
// Enhanced port change handler that saves automatically
93+
const handleFollowerPortChange = (value: string) => {
94+
setFollowerPort(value);
95+
// Auto-save with debouncing to avoid excessive API calls
96+
debouncedSavePort("follower", value);
97+
};
98+
99+
// Enhanced config change handler that saves automatically
100+
const handleFollowerConfigChange = (value: string) => {
101+
setFollowerConfig(value);
102+
// Auto-save with debouncing to avoid excessive API calls
103+
debouncedSaveConfig("follower", value);
104+
};
105+
78106
return (
79107
<Dialog open={open} onOpenChange={onOpenChange}>
80108
<DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[500px] p-8">
@@ -103,7 +131,7 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
103131
<Input
104132
id="followerPort"
105133
value={followerPort}
106-
onChange={(e) => setFollowerPort(e.target.value)}
134+
onChange={(e) => handleFollowerPortChange(e.target.value)}
107135
placeholder="/dev/tty.usbmodem5A460816621"
108136
className="bg-gray-800 border-gray-700 text-white flex-1"
109137
/>
@@ -121,7 +149,7 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
121149
>
122150
Follower Calibration Config
123151
</Label>
124-
<Select value={followerConfig} onValueChange={setFollowerConfig}>
152+
<Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
125153
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
126154
<SelectValue
127155
placeholder={

src/components/landing/RecordingModal.tsx

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import PortDetectionModal from "@/components/ui/PortDetectionModal";
2121
import PortDetectionButton from "@/components/ui/PortDetectionButton";
2222
import QrCodeModal from "@/components/recording/QrCodeModal";
2323
import { useApi } from "@/contexts/ApiContext";
24+
import { useAutoSave } from "@/hooks/useAutoSave";
2425
interface RecordingModalProps {
2526
open: boolean;
2627
onOpenChange: (open: boolean) => void;
@@ -66,16 +67,54 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
6667
onStart,
6768
}) => {
6869
const { baseUrl, fetchWithHeaders } = useApi();
70+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
6971
const [showPortDetection, setShowPortDetection] = useState(false);
7072
const [detectionRobotType, setDetectionRobotType] = useState<
7173
"leader" | "follower"
7274
>("leader");
7375
const [showQrCodeModal, setShowQrCodeModal] = useState(false);
7476
const [sessionId, setSessionId] = useState("");
7577

76-
// Load saved ports on component mount
78+
const handlePortDetection = (robotType: "leader" | "follower") => {
79+
setDetectionRobotType(robotType);
80+
setShowPortDetection(true);
81+
};
82+
const handlePortDetected = (port: string) => {
83+
if (detectionRobotType === "leader") {
84+
setLeaderPort(port);
85+
} else {
86+
setFollowerPort(port);
87+
}
88+
};
89+
90+
// Enhanced port change handlers that save automatically
91+
const handleLeaderPortChange = (value: string) => {
92+
setLeaderPort(value);
93+
// Auto-save with debouncing to avoid excessive API calls
94+
debouncedSavePort("leader", value);
95+
};
96+
97+
const handleFollowerPortChange = (value: string) => {
98+
setFollowerPort(value);
99+
// Auto-save with debouncing to avoid excessive API calls
100+
debouncedSavePort("follower", value);
101+
};
102+
103+
// Enhanced config change handlers that save automatically
104+
const handleLeaderConfigChange = (value: string) => {
105+
setLeaderConfig(value);
106+
// Auto-save with debouncing to avoid excessive API calls
107+
debouncedSaveConfig("leader", value);
108+
};
109+
110+
const handleFollowerConfigChange = (value: string) => {
111+
setFollowerConfig(value);
112+
// Auto-save with debouncing to avoid excessive API calls
113+
debouncedSaveConfig("follower", value);
114+
};
115+
// Load saved ports and configurations on component mount
77116
useEffect(() => {
78-
const loadSavedPorts = async () => {
117+
const loadSavedData = async () => {
79118
try {
80119
// Load leader port
81120
const leaderResponse = await fetchWithHeaders(
@@ -94,25 +133,34 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
94133
if (followerData.status === "success" && followerData.default_port) {
95134
setFollowerPort(followerData.default_port);
96135
}
136+
137+
// Load leader configuration
138+
const leaderConfigResponse = await fetchWithHeaders(
139+
`${baseUrl}/robot-config/leader?available_configs=${leaderConfigs.join(',')}`
140+
);
141+
const leaderConfigData = await leaderConfigResponse.json();
142+
if (leaderConfigData.status === "success" && leaderConfigData.default_config) {
143+
setLeaderConfig(leaderConfigData.default_config);
144+
}
145+
146+
// Load follower configuration
147+
const followerConfigResponse = await fetchWithHeaders(
148+
`${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
149+
);
150+
const followerConfigData = await followerConfigResponse.json();
151+
if (followerConfigData.status === "success" && followerConfigData.default_config) {
152+
setFollowerConfig(followerConfigData.default_config);
153+
}
97154
} catch (error) {
98-
console.error("Error loading saved ports:", error);
155+
console.error("Error loading saved data:", error);
99156
}
100157
};
101-
if (open) {
102-
loadSavedPorts();
103-
}
104-
}, [open, setLeaderPort, setFollowerPort]);
105-
const handlePortDetection = (robotType: "leader" | "follower") => {
106-
setDetectionRobotType(robotType);
107-
setShowPortDetection(true);
108-
};
109-
const handlePortDetected = (port: string) => {
110-
if (detectionRobotType === "leader") {
111-
setLeaderPort(port);
112-
} else {
113-
setFollowerPort(port);
158+
159+
if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
160+
loadSavedData();
114161
}
115-
};
162+
}, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
163+
116164
const handleQrCodeClick = () => {
117165
// Generate a session ID for this recording session
118166
const newSessionId = `recording_${Date.now()}_${Math.random()
@@ -175,7 +223,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
175223
<Input
176224
id="recordLeaderPort"
177225
value={leaderPort}
178-
onChange={(e) => setLeaderPort(e.target.value)}
226+
onChange={(e) => handleLeaderPortChange(e.target.value)}
179227
placeholder="/dev/tty.usbmodem5A460816421"
180228
className="bg-gray-800 border-gray-700 text-white flex-1"
181229
/>
@@ -194,7 +242,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
194242
</Label>
195243
<Select
196244
value={leaderConfig}
197-
onValueChange={setLeaderConfig}
245+
onValueChange={handleLeaderConfigChange}
198246
>
199247
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
200248
<SelectValue
@@ -229,7 +277,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
229277
<Input
230278
id="recordFollowerPort"
231279
value={followerPort}
232-
onChange={(e) => setFollowerPort(e.target.value)}
280+
onChange={(e) => handleFollowerPortChange(e.target.value)}
233281
placeholder="/dev/tty.usbmodem5A460816621"
234282
className="bg-gray-800 border-gray-700 text-white flex-1"
235283
/>
@@ -248,7 +296,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
248296
</Label>
249297
<Select
250298
value={followerConfig}
251-
onValueChange={setFollowerConfig}
299+
onValueChange={handleFollowerConfigChange}
252300
>
253301
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
254302
<SelectValue

src/components/landing/TeleoperationModal.tsx

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Settings } from "lucide-react";
2020
import PortDetectionModal from "@/components/ui/PortDetectionModal";
2121
import PortDetectionButton from "@/components/ui/PortDetectionButton";
2222
import { useApi } from "@/contexts/ApiContext";
23+
import { useAutoSave } from "@/hooks/useAutoSave";
2324

2425
interface TeleoperationModalProps {
2526
open: boolean;
@@ -55,14 +56,15 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
5556
onStart,
5657
}) => {
5758
const { baseUrl, fetchWithHeaders } = useApi();
59+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
5860
const [showPortDetection, setShowPortDetection] = useState(false);
5961
const [detectionRobotType, setDetectionRobotType] = useState<
6062
"leader" | "follower"
6163
>("leader");
6264

63-
// Load saved ports on component mount
65+
// Load saved ports and configurations on component mount
6466
useEffect(() => {
65-
const loadSavedPorts = async () => {
67+
const loadSavedData = async () => {
6668
try {
6769
// Load leader port
6870
const leaderResponse = await fetchWithHeaders(
@@ -81,15 +83,33 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
8183
if (followerData.status === "success" && followerData.default_port) {
8284
setFollowerPort(followerData.default_port);
8385
}
86+
87+
// Load leader configuration
88+
const leaderConfigResponse = await fetchWithHeaders(
89+
`${baseUrl}/robot-config/leader?available_configs=${leaderConfigs.join(',')}`
90+
);
91+
const leaderConfigData = await leaderConfigResponse.json();
92+
if (leaderConfigData.status === "success" && leaderConfigData.default_config) {
93+
setLeaderConfig(leaderConfigData.default_config);
94+
}
95+
96+
// Load follower configuration
97+
const followerConfigResponse = await fetchWithHeaders(
98+
`${baseUrl}/robot-config/follower?available_configs=${followerConfigs.join(',')}`
99+
);
100+
const followerConfigData = await followerConfigResponse.json();
101+
if (followerConfigData.status === "success" && followerConfigData.default_config) {
102+
setFollowerConfig(followerConfigData.default_config);
103+
}
84104
} catch (error) {
85-
console.error("Error loading saved ports:", error);
105+
console.error("Error loading saved data:", error);
86106
}
87107
};
88108

89-
if (open) {
90-
loadSavedPorts();
109+
if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
110+
loadSavedData();
91111
}
92-
}, [open, setLeaderPort, setFollowerPort]);
112+
}, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
93113

94114
const handlePortDetection = (robotType: "leader" | "follower") => {
95115
setDetectionRobotType(robotType);
@@ -103,6 +123,32 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
103123
setFollowerPort(port);
104124
}
105125
};
126+
127+
// Enhanced port change handlers that save automatically
128+
const handleLeaderPortChange = (value: string) => {
129+
setLeaderPort(value);
130+
// Auto-save with debouncing to avoid excessive API calls
131+
debouncedSavePort("leader", value);
132+
};
133+
134+
const handleFollowerPortChange = (value: string) => {
135+
setFollowerPort(value);
136+
// Auto-save with debouncing to avoid excessive API calls
137+
debouncedSavePort("follower", value);
138+
};
139+
140+
// Enhanced config change handlers that save automatically
141+
const handleLeaderConfigChange = (value: string) => {
142+
setLeaderConfig(value);
143+
// Auto-save with debouncing to avoid excessive API calls
144+
debouncedSaveConfig("leader", value);
145+
};
146+
147+
const handleFollowerConfigChange = (value: string) => {
148+
setFollowerConfig(value);
149+
// Auto-save with debouncing to avoid excessive API calls
150+
debouncedSaveConfig("follower", value);
151+
};
106152
return (
107153
<Dialog open={open} onOpenChange={onOpenChange}>
108154
<DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
@@ -132,7 +178,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
132178
<Input
133179
id="leaderPort"
134180
value={leaderPort}
135-
onChange={(e) => setLeaderPort(e.target.value)}
181+
onChange={(e) => handleLeaderPortChange(e.target.value)}
136182
placeholder="/dev/tty.usbmodem5A460816421"
137183
className="bg-gray-800 border-gray-700 text-white flex-1"
138184
/>
@@ -150,7 +196,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
150196
>
151197
Leader Calibration Config
152198
</Label>
153-
<Select value={leaderConfig} onValueChange={setLeaderConfig}>
199+
<Select value={leaderConfig} onValueChange={handleLeaderConfigChange}>
154200
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
155201
<SelectValue
156202
placeholder={
@@ -185,7 +231,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
185231
<Input
186232
id="followerPort"
187233
value={followerPort}
188-
onChange={(e) => setFollowerPort(e.target.value)}
234+
onChange={(e) => handleFollowerPortChange(e.target.value)}
189235
placeholder="/dev/tty.usbmodem5A460816621"
190236
className="bg-gray-800 border-gray-700 text-white flex-1"
191237
/>
@@ -203,7 +249,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
203249
>
204250
Follower Calibration Config
205251
</Label>
206-
<Select value={followerConfig} onValueChange={setFollowerConfig}>
252+
<Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
207253
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
208254
<SelectValue
209255
placeholder={

0 commit comments

Comments
 (0)