feat: Enhance PathRecorder component; implement path playback functionality and improve path loading logic
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTurtleStore } from '../store/turtleStore';
|
||||
import './PathRecorder.css';
|
||||
|
||||
const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
@@ -10,6 +11,9 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
const [selectedPath, setSelectedPath] = useState(null);
|
||||
const [message, setMessage] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [playingBack, setPlayingBack] = useState(false);
|
||||
|
||||
const sendCommand = useTurtleStore((state) => state.sendCommand);
|
||||
|
||||
useEffect(() => {
|
||||
loadPaths();
|
||||
@@ -21,7 +25,7 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
const response = await fetch(`${apiUrl}/api/paths`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setPaths(data);
|
||||
setPaths(Array.isArray(data) ? data : []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load paths:', error);
|
||||
@@ -59,31 +63,18 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Create the path
|
||||
const pathResponse = await fetch(`${apiUrl}/api/paths`, {
|
||||
const response = await fetch(`${apiUrl}/api/paths`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: currentPath.name,
|
||||
turtleId: currentPath.turtleId,
|
||||
description: currentPath.description
|
||||
pathName: currentPath.name,
|
||||
pathData: currentPath.waypoints
|
||||
})
|
||||
});
|
||||
|
||||
if (!pathResponse.ok) {
|
||||
throw new Error('Failed to create 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)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save path');
|
||||
}
|
||||
|
||||
showMessage(`Path saved with ${currentPath.waypoints.length} waypoints!`, 'success');
|
||||
@@ -132,9 +123,71 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const playbackPath = (path) => {
|
||||
showMessage(`Playback not yet implemented for path: ${path.name}`, 'info');
|
||||
// TODO: Implement playback by sending commands to turtle
|
||||
const playbackPath = async (path) => {
|
||||
if (!selectedTurtle) {
|
||||
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) => {
|
||||
@@ -276,8 +329,9 @@ const PathRecorder = ({ turtles, selectedTurtle, apiUrl }) => {
|
||||
onClick={() => playbackPath(path)}
|
||||
className="play-btn"
|
||||
title="Playback path"
|
||||
disabled={playingBack}
|
||||
>
|
||||
▶️ Play
|
||||
{playingBack ? '⏳ Playing...' : '▶️ Play'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deletePath(path.pathId)}
|
||||
|
||||
Reference in New Issue
Block a user