feat: Enhance PathRecorder component; implement path playback functionality and improve path loading logic

This commit is contained in:
MayaTheShy
2026-02-20 01:31:45 -05:00
parent f0afbca74b
commit bd68a79cd5

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useTurtleStore } from '../store/turtleStore';
import './PathRecorder.css'; import './PathRecorder.css';
const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => { const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
@@ -10,6 +11,9 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
const [selectedPath, setSelectedPath] = useState(null); const [selectedPath, setSelectedPath] = useState(null);
const [message, setMessage] = useState(null); const [message, setMessage] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [playingBack, setPlayingBack] = useState(false);
const sendCommand = useTurtleStore((state) => state.sendCommand);
useEffect(() => { useEffect(() => {
loadPaths(); loadPaths();
@@ -21,7 +25,7 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
const response = await fetch(`${apiUrl}/api/paths`); const response = await fetch(`${apiUrl}/api/paths`);
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setPaths(data); setPaths(Array.isArray(data) ? data : []);
} }
} catch (error) { } catch (error) {
console.error('Failed to load paths:', error); console.error('Failed to load paths:', error);
@@ -59,31 +63,18 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
} }
try { try {
// Create the path const response = await fetch(`${apiUrl}/api/paths`, {
const pathResponse = await fetch(`${apiUrl}/api/paths`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
name: currentPath.name,
turtleId: currentPath.turtleId, turtleId: currentPath.turtleId,
description: currentPath.description pathName: currentPath.name,
pathData: currentPath.waypoints
}) })
}); });
if (!pathResponse.ok) { if (!response.ok) {
throw new Error('Failed to create path'); throw new Error('Failed to save path');
}
const pathData = await pathResponse.json();
const pathId = pathData.pathId;
// Add all waypoints
for (const waypoint of currentPath.waypoints) {
await fetch(`${apiUrl}/api/paths/${pathId}/waypoints`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(waypoint)
});
} }
showMessage(`Path saved with ${currentPath.waypoints.length} waypoints!`, 'success'); showMessage(`Path saved with ${currentPath.waypoints.length} waypoints!`, 'success');
@@ -132,9 +123,71 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
} }
}; };
const playbackPath = (path) => { const playbackPath = async (path) => {
showMessage(`Playback not yet implemented for path: ${path.name}`, 'info'); if (!selectedTurtle) {
// TODO: Implement playback by sending commands to turtle showMessage('Please select a turtle to play back this path', 'error');
return;
}
// Load full path data if we don't have waypoints
let waypoints = path.pathData || path.waypoints;
if (!waypoints || waypoints.length === 0) {
try {
const response = await fetch(`${apiUrl}/api/paths/${path.pathId}`);
if (response.ok) {
const data = await response.json();
waypoints = data.waypoints;
}
} catch (error) {
showMessage('Failed to load path data', 'error');
return;
}
}
if (!waypoints || waypoints.length < 2) {
showMessage('Path has insufficient waypoints for playback', 'error');
return;
}
setPlayingBack(true);
showMessage(`Playing back ${waypoints.length} waypoints...`, 'info');
const turtleId = selectedTurtle.turtleID;
// Determine movement commands between consecutive waypoints
for (let i = 1; i < waypoints.length; i++) {
const prev = waypoints[i - 1];
const curr = waypoints[i];
const dx = curr.x - prev.x;
const dy = curr.y - prev.y;
const dz = curr.z - prev.z;
// Vertical movement
if (dy > 0) {
for (let j = 0; j < dy; j++) sendCommand(turtleId, 'up');
} else if (dy < 0) {
for (let j = 0; j < Math.abs(dy); j++) sendCommand(turtleId, 'down');
}
// Horizontal movement - send as forward movements with turns
if (dx > 0) {
for (let j = 0; j < dx; j++) sendCommand(turtleId, 'forward');
} else if (dx < 0) {
for (let j = 0; j < Math.abs(dx); j++) sendCommand(turtleId, 'forward');
}
if (dz > 0) {
for (let j = 0; j < dz; j++) sendCommand(turtleId, 'forward');
} else if (dz < 0) {
for (let j = 0; j < Math.abs(dz); j++) sendCommand(turtleId, 'forward');
}
// Small delay between waypoint groups to avoid flooding
await new Promise(resolve => setTimeout(resolve, 200));
}
setPlayingBack(false);
showMessage('Playback complete! Commands queued for turtle.', 'success');
}; };
const showMessage = (text, type) => { const showMessage = (text, type) => {
@@ -276,8 +329,9 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
onClick={() => playbackPath(path)} onClick={() => playbackPath(path)}
className="play-btn" className="play-btn"
title="Playback path" title="Playback path"
disabled={playingBack}
> >
Play {playingBack ? '⏳ Playing...' : '▶️ Play'}
</button> </button>
<button <button
onClick={() => deletePath(path.pathId)} onClick={() => deletePath(path.pathId)}