feat: Initialize Turtle Control Center with React and WebSocket server
- Added index.html for the main entry point of the client application. - Created package.json for client dependencies and scripts. - Implemented App component with view controls and layout. - Added CSS styles for the application and components. - Developed ControlPanel component for turtle management and commands. - Created Map3D component for 3D visualization of turtles. - Established Zustand store for state management of turtles and WebSocket connection. - Set up server with Express and WebSocket for handling turtle updates and commands. - Added REST API endpoints for turtle status updates and command handling. - Implemented start.sh script for setting up and running the application.
This commit is contained in:
104
client/src/store/turtleStore.js
Normal file
104
client/src/store/turtleStore.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
const WS_URL = 'ws://localhost:3002';
|
||||
const API_URL = 'http://localhost:3001';
|
||||
|
||||
export const useTurtleStore = create((set, get) => ({
|
||||
// State
|
||||
turtles: {},
|
||||
selectedTurtleId: null,
|
||||
connected: false,
|
||||
ws: null,
|
||||
|
||||
// WebSocket connection
|
||||
connect: () => {
|
||||
const ws = new WebSocket(WS_URL);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('✅ Connected to server');
|
||||
set({ connected: true, ws });
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'initial_state') {
|
||||
const turtlesMap = {};
|
||||
data.turtles.forEach(turtle => {
|
||||
turtlesMap[turtle.turtleID] = turtle;
|
||||
});
|
||||
set({ turtles: turtlesMap });
|
||||
} else if (data.type === 'turtle_update') {
|
||||
set(state => ({
|
||||
turtles: {
|
||||
...state.turtles,
|
||||
[data.turtle.turtleID]: data.turtle
|
||||
}
|
||||
}));
|
||||
} else if (data.type === 'turtle_disconnected') {
|
||||
set(state => {
|
||||
const newTurtles = { ...state.turtles };
|
||||
delete newTurtles[data.turtleID];
|
||||
return { turtles: newTurtles };
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('❌ Disconnected from server');
|
||||
set({ connected: false, ws: null });
|
||||
|
||||
// Attempt reconnect after 3 seconds
|
||||
setTimeout(() => {
|
||||
get().connect();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
},
|
||||
|
||||
// Actions
|
||||
selectTurtle: (turtleId) => {
|
||||
set({ selectedTurtleId: turtleId });
|
||||
},
|
||||
|
||||
sendCommand: async (turtleId, command, param = null) => {
|
||||
const { ws } = get();
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'command',
|
||||
turtleID: turtleId,
|
||||
command,
|
||||
param
|
||||
}));
|
||||
} else {
|
||||
// Fallback to REST API
|
||||
try {
|
||||
await fetch(`${API_URL}/api/turtle/${turtleId}/command`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ command, param })
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending command:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Helper getters
|
||||
getTurtleArray: () => {
|
||||
return Object.values(get().turtles);
|
||||
},
|
||||
|
||||
getSelectedTurtle: () => {
|
||||
const { turtles, selectedTurtleId } = get();
|
||||
return selectedTurtleId ? turtles[selectedTurtleId] : null;
|
||||
}
|
||||
}));
|
||||
Reference in New Issue
Block a user