leLab / src /components /control /DirectFollowerControlPanel.tsx
jurmy24's picture
mega big updates pre-demo
9a9d18a
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
type Joint = {
name: string;
label: string;
min: number;
max: number;
step: number;
default: number;
};
const JOINTS: Joint[] = [
{ name: "shoulder_pan.pos", label: "Shoulder Pan", min: -180, max: 180, step: 1, default: 0 },
{ name: "shoulder_lift.pos", label: "Shoulder Lift", min: -180, max: 180, step: 1, default: 0 },
{ name: "elbow_flex.pos", label: "Elbow Flex", min: -180, max: 180, step: 1, default: 0 },
{ name: "wrist_flex.pos", label: "Wrist Flex", min: -180, max: 180, step: 1, default: 0 },
{ name: "wrist_roll.pos", label: "Wrist Roll", min: -180, max: 180, step: 1, default: 0 },
{ name: "gripper.pos", label: "Gripper", min: 0, max: 100, step: 1, default: 0 },
];
type Props = {
onSendAction?: (values: Record<string, number>) => void;
className?: string;
};
const initialJointState = () => {
const state: Record<string, number> = {};
JOINTS.forEach(j => (state[j.name] = j.default));
return state;
};
const DirectFollowerControlPanel: React.FC<Props> = ({ onSendAction, className }) => {
const [jointValues, setJointValues] = useState<Record<string, number>>(initialJointState);
// Handler for slider changes
const handleSliderChange = (joint: Joint, value: number[]) => {
setJointValues(v => ({ ...v, [joint.name]: value[0] }));
};
// Quick action examples
const handleHome = () => {
const home: Record<string, number> = {};
JOINTS.forEach(j => (home[j.name] = 0));
setJointValues(home);
};
const handleSend = () => {
// You'd call the backend API here. For now, just call prop if present.
if (onSendAction) onSendAction(jointValues);
// TODO: Real API integration
fetch("http://localhost:8000/send-follower-command", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(jointValues),
}).then(res => {
// Optionally handle response
// Could use a toast here
});
};
return (
<div className={`w-full max-w-lg mx-auto p-6 bg-gray-900 rounded-lg shadow space-y-6 ${className || ""}`}>
<h2 className="text-xl font-bold text-white mb-4 text-center">Direct Follower Control</h2>
<div className="space-y-4">
{JOINTS.map(joint => (
<div key={joint.name} className="flex flex-col">
<label className="text-gray-300 text-sm mb-1 flex justify-between">
<span>{joint.label}</span>
<span className="ml-2 font-mono text-gray-400">{jointValues[joint.name]}</span>
</label>
<Slider
min={joint.min}
max={joint.max}
step={joint.step}
value={[jointValues[joint.name]]}
onValueChange={value => handleSliderChange(joint, value)}
className="my-1"
/>
<div className="flex justify-between text-xs text-gray-500 select-none">
<span>{joint.min}</span>
<span>{joint.max}</span>
</div>
</div>
))}
</div>
<div className="flex gap-3 justify-center pt-4">
<Button variant="secondary" onClick={handleHome}>Home</Button>
<Button variant="default" className="px-8" onClick={handleSend}>
Send Action
</Button>
</div>
</div>
);
};
export default DirectFollowerControlPanel;