Spaces:
Running
Running
David
commited on
Commit
·
df04e9d
1
Parent(s):
a3ea9bd
save port and calibration config on every change
Browse files
src/components/landing/DirectFollowerModal.tsx
CHANGED
@@ -20,6 +20,8 @@ import {
|
|
20 |
import { Settings } from "lucide-react";
|
21 |
import PortDetectionModal from "@/components/ui/PortDetectionModal";
|
22 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
|
|
|
|
23 |
|
24 |
interface DirectFollowerModalProps {
|
25 |
open: boolean;
|
@@ -44,28 +46,40 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
|
|
44 |
isLoadingConfigs,
|
45 |
onStart,
|
46 |
}) => {
|
|
|
|
|
47 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
48 |
|
49 |
-
// Load saved follower port on component mount
|
50 |
useEffect(() => {
|
51 |
-
const
|
52 |
try {
|
53 |
-
|
54 |
-
|
|
|
55 |
);
|
56 |
const followerData = await followerResponse.json();
|
57 |
if (followerData.status === "success" && followerData.default_port) {
|
58 |
setFollowerPort(followerData.default_port);
|
59 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
} catch (error) {
|
61 |
-
console.error("Error loading saved
|
62 |
}
|
63 |
};
|
64 |
|
65 |
-
if (open) {
|
66 |
-
|
67 |
}
|
68 |
-
}, [open, setFollowerPort]);
|
69 |
|
70 |
const handlePortDetection = () => {
|
71 |
setShowPortDetection(true);
|
@@ -75,6 +89,20 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
|
|
75 |
setFollowerPort(port);
|
76 |
};
|
77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
return (
|
79 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
80 |
<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> = ({
|
|
103 |
<Input
|
104 |
id="followerPort"
|
105 |
value={followerPort}
|
106 |
-
onChange={(e) =>
|
107 |
placeholder="/dev/tty.usbmodem5A460816621"
|
108 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
109 |
/>
|
@@ -121,7 +149,7 @@ const DirectFollowerModal: React.FC<DirectFollowerModalProps> = ({
|
|
121 |
>
|
122 |
Follower Calibration Config
|
123 |
</Label>
|
124 |
-
<Select value={followerConfig} onValueChange={
|
125 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
126 |
<SelectValue
|
127 |
placeholder={
|
|
|
20 |
import { Settings } from "lucide-react";
|
21 |
import PortDetectionModal from "@/components/ui/PortDetectionModal";
|
22 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
23 |
+
import { useApi } from "@/contexts/ApiContext";
|
24 |
+
import { useAutoSave } from "@/hooks/useAutoSave";
|
25 |
|
26 |
interface DirectFollowerModalProps {
|
27 |
open: boolean;
|
|
|
46 |
isLoadingConfigs,
|
47 |
onStart,
|
48 |
}) => {
|
49 |
+
const { baseUrl, fetchWithHeaders } = useApi();
|
50 |
+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
|
51 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
52 |
|
53 |
+
// Load saved follower port and configuration on component mount
|
54 |
useEffect(() => {
|
55 |
+
const loadSavedData = async () => {
|
56 |
try {
|
57 |
+
// Load follower port
|
58 |
+
const followerResponse = await fetchWithHeaders(
|
59 |
+
`${baseUrl}/robot-port/follower`
|
60 |
);
|
61 |
const followerData = await followerResponse.json();
|
62 |
if (followerData.status === "success" && followerData.default_port) {
|
63 |
setFollowerPort(followerData.default_port);
|
64 |
}
|
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 |
+
}
|
74 |
} catch (error) {
|
75 |
+
console.error("Error loading saved data:", error);
|
76 |
}
|
77 |
};
|
78 |
|
79 |
+
if (open && followerConfigs.length > 0) {
|
80 |
+
loadSavedData();
|
81 |
}
|
82 |
+
}, [open, setFollowerPort, setFollowerConfig, followerConfigs, baseUrl, fetchWithHeaders]);
|
83 |
|
84 |
const handlePortDetection = () => {
|
85 |
setShowPortDetection(true);
|
|
|
89 |
setFollowerPort(port);
|
90 |
};
|
91 |
|
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 |
+
|
106 |
return (
|
107 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
108 |
<DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[500px] p-8">
|
|
|
131 |
<Input
|
132 |
id="followerPort"
|
133 |
value={followerPort}
|
134 |
+
onChange={(e) => handleFollowerPortChange(e.target.value)}
|
135 |
placeholder="/dev/tty.usbmodem5A460816621"
|
136 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
137 |
/>
|
|
|
149 |
>
|
150 |
Follower Calibration Config
|
151 |
</Label>
|
152 |
+
<Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
|
153 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
154 |
<SelectValue
|
155 |
placeholder={
|
src/components/landing/RecordingModal.tsx
CHANGED
@@ -21,6 +21,7 @@ import PortDetectionModal from "@/components/ui/PortDetectionModal";
|
|
21 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
22 |
import QrCodeModal from "@/components/recording/QrCodeModal";
|
23 |
import { useApi } from "@/contexts/ApiContext";
|
|
|
24 |
interface RecordingModalProps {
|
25 |
open: boolean;
|
26 |
onOpenChange: (open: boolean) => void;
|
@@ -66,6 +67,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
66 |
onStart,
|
67 |
}) => {
|
68 |
const { baseUrl, fetchWithHeaders } = useApi();
|
|
|
69 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
70 |
const [detectionRobotType, setDetectionRobotType] = useState<
|
71 |
"leader" | "follower"
|
@@ -73,9 +75,46 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
73 |
const [showQrCodeModal, setShowQrCodeModal] = useState(false);
|
74 |
const [sessionId, setSessionId] = useState("");
|
75 |
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
useEffect(() => {
|
78 |
-
const
|
79 |
try {
|
80 |
// Load leader port
|
81 |
const leaderResponse = await fetchWithHeaders(
|
@@ -94,25 +133,34 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
94 |
if (followerData.status === "success" && followerData.default_port) {
|
95 |
setFollowerPort(followerData.default_port);
|
96 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
} catch (error) {
|
98 |
-
console.error("Error loading saved
|
99 |
}
|
100 |
};
|
101 |
-
|
102 |
-
|
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);
|
114 |
}
|
115 |
-
};
|
|
|
116 |
const handleQrCodeClick = () => {
|
117 |
// Generate a session ID for this recording session
|
118 |
const newSessionId = `recording_${Date.now()}_${Math.random()
|
@@ -175,7 +223,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
175 |
<Input
|
176 |
id="recordLeaderPort"
|
177 |
value={leaderPort}
|
178 |
-
onChange={(e) =>
|
179 |
placeholder="/dev/tty.usbmodem5A460816421"
|
180 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
181 |
/>
|
@@ -194,7 +242,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
194 |
</Label>
|
195 |
<Select
|
196 |
value={leaderConfig}
|
197 |
-
onValueChange={
|
198 |
>
|
199 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
200 |
<SelectValue
|
@@ -229,7 +277,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
229 |
<Input
|
230 |
id="recordFollowerPort"
|
231 |
value={followerPort}
|
232 |
-
onChange={(e) =>
|
233 |
placeholder="/dev/tty.usbmodem5A460816621"
|
234 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
235 |
/>
|
@@ -248,7 +296,7 @@ const RecordingModal: React.FC<RecordingModalProps> = ({
|
|
248 |
</Label>
|
249 |
<Select
|
250 |
value={followerConfig}
|
251 |
-
onValueChange={
|
252 |
>
|
253 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
254 |
<SelectValue
|
|
|
21 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
22 |
import QrCodeModal from "@/components/recording/QrCodeModal";
|
23 |
import { useApi } from "@/contexts/ApiContext";
|
24 |
+
import { useAutoSave } from "@/hooks/useAutoSave";
|
25 |
interface RecordingModalProps {
|
26 |
open: boolean;
|
27 |
onOpenChange: (open: boolean) => void;
|
|
|
67 |
onStart,
|
68 |
}) => {
|
69 |
const { baseUrl, fetchWithHeaders } = useApi();
|
70 |
+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
|
71 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
72 |
const [detectionRobotType, setDetectionRobotType] = useState<
|
73 |
"leader" | "follower"
|
|
|
75 |
const [showQrCodeModal, setShowQrCodeModal] = useState(false);
|
76 |
const [sessionId, setSessionId] = useState("");
|
77 |
|
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
|
116 |
useEffect(() => {
|
117 |
+
const loadSavedData = async () => {
|
118 |
try {
|
119 |
// Load leader port
|
120 |
const leaderResponse = await fetchWithHeaders(
|
|
|
133 |
if (followerData.status === "success" && followerData.default_port) {
|
134 |
setFollowerPort(followerData.default_port);
|
135 |
}
|
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 |
+
}
|
154 |
} catch (error) {
|
155 |
+
console.error("Error loading saved data:", error);
|
156 |
}
|
157 |
};
|
158 |
+
|
159 |
+
if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
|
160 |
+
loadSavedData();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
}
|
162 |
+
}, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
|
163 |
+
|
164 |
const handleQrCodeClick = () => {
|
165 |
// Generate a session ID for this recording session
|
166 |
const newSessionId = `recording_${Date.now()}_${Math.random()
|
|
|
223 |
<Input
|
224 |
id="recordLeaderPort"
|
225 |
value={leaderPort}
|
226 |
+
onChange={(e) => handleLeaderPortChange(e.target.value)}
|
227 |
placeholder="/dev/tty.usbmodem5A460816421"
|
228 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
229 |
/>
|
|
|
242 |
</Label>
|
243 |
<Select
|
244 |
value={leaderConfig}
|
245 |
+
onValueChange={handleLeaderConfigChange}
|
246 |
>
|
247 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
248 |
<SelectValue
|
|
|
277 |
<Input
|
278 |
id="recordFollowerPort"
|
279 |
value={followerPort}
|
280 |
+
onChange={(e) => handleFollowerPortChange(e.target.value)}
|
281 |
placeholder="/dev/tty.usbmodem5A460816621"
|
282 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
283 |
/>
|
|
|
296 |
</Label>
|
297 |
<Select
|
298 |
value={followerConfig}
|
299 |
+
onValueChange={handleFollowerConfigChange}
|
300 |
>
|
301 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
302 |
<SelectValue
|
src/components/landing/TeleoperationModal.tsx
CHANGED
@@ -20,6 +20,7 @@ import { Settings } from "lucide-react";
|
|
20 |
import PortDetectionModal from "@/components/ui/PortDetectionModal";
|
21 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
22 |
import { useApi } from "@/contexts/ApiContext";
|
|
|
23 |
|
24 |
interface TeleoperationModalProps {
|
25 |
open: boolean;
|
@@ -55,14 +56,15 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
55 |
onStart,
|
56 |
}) => {
|
57 |
const { baseUrl, fetchWithHeaders } = useApi();
|
|
|
58 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
59 |
const [detectionRobotType, setDetectionRobotType] = useState<
|
60 |
"leader" | "follower"
|
61 |
>("leader");
|
62 |
|
63 |
-
// Load saved ports on component mount
|
64 |
useEffect(() => {
|
65 |
-
const
|
66 |
try {
|
67 |
// Load leader port
|
68 |
const leaderResponse = await fetchWithHeaders(
|
@@ -81,15 +83,33 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
81 |
if (followerData.status === "success" && followerData.default_port) {
|
82 |
setFollowerPort(followerData.default_port);
|
83 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
} catch (error) {
|
85 |
-
console.error("Error loading saved
|
86 |
}
|
87 |
};
|
88 |
|
89 |
-
if (open) {
|
90 |
-
|
91 |
}
|
92 |
-
}, [open, setLeaderPort, setFollowerPort]);
|
93 |
|
94 |
const handlePortDetection = (robotType: "leader" | "follower") => {
|
95 |
setDetectionRobotType(robotType);
|
@@ -103,6 +123,32 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
103 |
setFollowerPort(port);
|
104 |
}
|
105 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
return (
|
107 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
108 |
<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> = ({
|
|
132 |
<Input
|
133 |
id="leaderPort"
|
134 |
value={leaderPort}
|
135 |
-
onChange={(e) =>
|
136 |
placeholder="/dev/tty.usbmodem5A460816421"
|
137 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
138 |
/>
|
@@ -150,7 +196,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
150 |
>
|
151 |
Leader Calibration Config
|
152 |
</Label>
|
153 |
-
<Select value={leaderConfig} onValueChange={
|
154 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
155 |
<SelectValue
|
156 |
placeholder={
|
@@ -185,7 +231,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
185 |
<Input
|
186 |
id="followerPort"
|
187 |
value={followerPort}
|
188 |
-
onChange={(e) =>
|
189 |
placeholder="/dev/tty.usbmodem5A460816621"
|
190 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
191 |
/>
|
@@ -203,7 +249,7 @@ const TeleoperationModal: React.FC<TeleoperationModalProps> = ({
|
|
203 |
>
|
204 |
Follower Calibration Config
|
205 |
</Label>
|
206 |
-
<Select value={followerConfig} onValueChange={
|
207 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
208 |
<SelectValue
|
209 |
placeholder={
|
|
|
20 |
import PortDetectionModal from "@/components/ui/PortDetectionModal";
|
21 |
import PortDetectionButton from "@/components/ui/PortDetectionButton";
|
22 |
import { useApi } from "@/contexts/ApiContext";
|
23 |
+
import { useAutoSave } from "@/hooks/useAutoSave";
|
24 |
|
25 |
interface TeleoperationModalProps {
|
26 |
open: boolean;
|
|
|
56 |
onStart,
|
57 |
}) => {
|
58 |
const { baseUrl, fetchWithHeaders } = useApi();
|
59 |
+
const { debouncedSavePort, debouncedSaveConfig } = useAutoSave();
|
60 |
const [showPortDetection, setShowPortDetection] = useState(false);
|
61 |
const [detectionRobotType, setDetectionRobotType] = useState<
|
62 |
"leader" | "follower"
|
63 |
>("leader");
|
64 |
|
65 |
+
// Load saved ports and configurations on component mount
|
66 |
useEffect(() => {
|
67 |
+
const loadSavedData = async () => {
|
68 |
try {
|
69 |
// Load leader port
|
70 |
const leaderResponse = await fetchWithHeaders(
|
|
|
83 |
if (followerData.status === "success" && followerData.default_port) {
|
84 |
setFollowerPort(followerData.default_port);
|
85 |
}
|
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 |
+
}
|
104 |
} catch (error) {
|
105 |
+
console.error("Error loading saved data:", error);
|
106 |
}
|
107 |
};
|
108 |
|
109 |
+
if (open && leaderConfigs.length > 0 && followerConfigs.length > 0) {
|
110 |
+
loadSavedData();
|
111 |
}
|
112 |
+
}, [open, setLeaderPort, setFollowerPort, setLeaderConfig, setFollowerConfig, leaderConfigs, followerConfigs, baseUrl, fetchWithHeaders]);
|
113 |
|
114 |
const handlePortDetection = (robotType: "leader" | "follower") => {
|
115 |
setDetectionRobotType(robotType);
|
|
|
123 |
setFollowerPort(port);
|
124 |
}
|
125 |
};
|
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 |
+
};
|
152 |
return (
|
153 |
<Dialog open={open} onOpenChange={onOpenChange}>
|
154 |
<DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
|
|
|
178 |
<Input
|
179 |
id="leaderPort"
|
180 |
value={leaderPort}
|
181 |
+
onChange={(e) => handleLeaderPortChange(e.target.value)}
|
182 |
placeholder="/dev/tty.usbmodem5A460816421"
|
183 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
184 |
/>
|
|
|
196 |
>
|
197 |
Leader Calibration Config
|
198 |
</Label>
|
199 |
+
<Select value={leaderConfig} onValueChange={handleLeaderConfigChange}>
|
200 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
201 |
<SelectValue
|
202 |
placeholder={
|
|
|
231 |
<Input
|
232 |
id="followerPort"
|
233 |
value={followerPort}
|
234 |
+
onChange={(e) => handleFollowerPortChange(e.target.value)}
|
235 |
placeholder="/dev/tty.usbmodem5A460816621"
|
236 |
className="bg-gray-800 border-gray-700 text-white flex-1"
|
237 |
/>
|
|
|
249 |
>
|
250 |
Follower Calibration Config
|
251 |
</Label>
|
252 |
+
<Select value={followerConfig} onValueChange={handleFollowerConfigChange}>
|
253 |
<SelectTrigger className="bg-gray-800 border-gray-700 text-white">
|
254 |
<SelectValue
|
255 |
placeholder={
|
src/hooks/useAutoSave.ts
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useCallback, useRef } from 'react';
|
2 |
+
import { useApi } from '@/contexts/ApiContext';
|
3 |
+
|
4 |
+
export const useAutoSave = () => {
|
5 |
+
const { baseUrl, fetchWithHeaders } = useApi();
|
6 |
+
const timeoutRefs = useRef<{ [key: string]: NodeJS.Timeout }>({});
|
7 |
+
const configTimeoutRefs = useRef<{ [key: string]: NodeJS.Timeout }>({});
|
8 |
+
|
9 |
+
const savePortAutomatically = useCallback(async (robotType: 'leader' | 'follower', port: string) => {
|
10 |
+
if (!port.trim()) return;
|
11 |
+
|
12 |
+
try {
|
13 |
+
await fetchWithHeaders(`${baseUrl}/save-robot-port`, {
|
14 |
+
method: 'POST',
|
15 |
+
headers: {
|
16 |
+
'Content-Type': 'application/json',
|
17 |
+
},
|
18 |
+
body: JSON.stringify({
|
19 |
+
robot_type: robotType,
|
20 |
+
port: port.trim(),
|
21 |
+
}),
|
22 |
+
});
|
23 |
+
console.log(`Auto-saved ${robotType} port: ${port}`);
|
24 |
+
} catch (error) {
|
25 |
+
console.error(`Error saving ${robotType} port:`, error);
|
26 |
+
}
|
27 |
+
}, [baseUrl, fetchWithHeaders]);
|
28 |
+
|
29 |
+
const saveConfigAutomatically = useCallback(async (robotType: 'leader' | 'follower', configName: string) => {
|
30 |
+
if (!configName.trim()) return;
|
31 |
+
|
32 |
+
try {
|
33 |
+
await fetchWithHeaders(`${baseUrl}/save-robot-config`, {
|
34 |
+
method: 'POST',
|
35 |
+
headers: {
|
36 |
+
'Content-Type': 'application/json',
|
37 |
+
},
|
38 |
+
body: JSON.stringify({
|
39 |
+
robot_type: robotType,
|
40 |
+
config_name: configName.trim(),
|
41 |
+
}),
|
42 |
+
});
|
43 |
+
console.log(`Auto-saved ${robotType} config: ${configName}`);
|
44 |
+
} catch (error) {
|
45 |
+
console.error(`Error saving ${robotType} config:`, error);
|
46 |
+
}
|
47 |
+
}, [baseUrl, fetchWithHeaders]);
|
48 |
+
|
49 |
+
const debouncedSavePort = useCallback((robotType: 'leader' | 'follower', port: string, delay: number = 1500) => {
|
50 |
+
// Clear existing timeout for this robotType
|
51 |
+
if (timeoutRefs.current[robotType]) {
|
52 |
+
clearTimeout(timeoutRefs.current[robotType]);
|
53 |
+
}
|
54 |
+
|
55 |
+
// Set new timeout
|
56 |
+
timeoutRefs.current[robotType] = setTimeout(() => {
|
57 |
+
savePortAutomatically(robotType, port);
|
58 |
+
delete timeoutRefs.current[robotType];
|
59 |
+
}, delay);
|
60 |
+
}, [savePortAutomatically]);
|
61 |
+
|
62 |
+
const debouncedSaveConfig = useCallback((robotType: 'leader' | 'follower', configName: string, delay: number = 1000) => {
|
63 |
+
const key = `${robotType}_config`;
|
64 |
+
|
65 |
+
// Clear existing timeout for this robotType config
|
66 |
+
if (configTimeoutRefs.current[key]) {
|
67 |
+
clearTimeout(configTimeoutRefs.current[key]);
|
68 |
+
}
|
69 |
+
|
70 |
+
// Set new timeout
|
71 |
+
configTimeoutRefs.current[key] = setTimeout(() => {
|
72 |
+
saveConfigAutomatically(robotType, configName);
|
73 |
+
delete configTimeoutRefs.current[key];
|
74 |
+
}, delay);
|
75 |
+
}, [saveConfigAutomatically]);
|
76 |
+
|
77 |
+
return { debouncedSavePort, debouncedSaveConfig };
|
78 |
+
};
|