File size: 3,485 Bytes
9a9d18a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

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;