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 { 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)}
|
||||||
|
|||||||
Reference in New Issue
Block a user