- Implement WebView2-based HMI frontend with React + TypeScript + Vite - Add C# .NET backend with WebSocket communication layer - Separate UI components into modular structure: * RecipePanel: Recipe selection and management * IOPanel: I/O monitoring and control (32 inputs/outputs) * MotionPanel: Servo control for X/Y/Z axes * CameraPanel: Vision system feed with HUD overlay * SettingsModal: System configuration management - Create reusable UI components (CyberPanel, TechButton, PanelHeader) - Implement dual-mode communication (WebView2 native + WebSocket fallback) - Add 3D visualization with Three.js/React Three Fiber - Fix JSON parsing bug in configuration save handler - Include comprehensive .gitignore for .NET and Node.js projects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
68 lines
3.5 KiB
TypeScript
68 lines
3.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Move } from 'lucide-react';
|
|
import { RobotTarget, AxisPosition } from '../types';
|
|
import { PanelHeader } from './common/PanelHeader';
|
|
import { TechButton } from './common/TechButton';
|
|
|
|
const MOCK_POSITIONS: AxisPosition[] = [
|
|
{ id: 'p1', name: 'Home Position', axis: 'X', value: 0, speed: 100, acc: 100, dec: 100 },
|
|
{ id: 'p2', name: 'Pick Pos A', axis: 'X', value: -1.5, speed: 500, acc: 200, dec: 200 },
|
|
{ id: 'p3', name: 'Place Pos B', axis: 'X', value: 1.5, speed: 500, acc: 200, dec: 200 },
|
|
{ id: 'p4', name: 'Scan Index', axis: 'X', value: 0.5, speed: 300, acc: 100, dec: 100 },
|
|
{ id: 'py1', name: 'Rear Limit', axis: 'Y', value: -1.5, speed: 200, acc: 100, dec: 100 },
|
|
{ id: 'pz1', name: 'Safe Height', axis: 'Z', value: 0, speed: 50, acc: 50, dec: 50 },
|
|
];
|
|
|
|
interface MotionPanelProps {
|
|
robotTarget: RobotTarget;
|
|
onMove: (axis: 'X' | 'Y' | 'Z', val: number) => void;
|
|
}
|
|
|
|
export const MotionPanel: React.FC<MotionPanelProps> = ({ robotTarget, onMove }) => {
|
|
const [axis, setAxis] = useState<'X' | 'Y' | 'Z'>('X');
|
|
|
|
return (
|
|
<div className="h-full flex flex-col">
|
|
<PanelHeader title="Servo Control" icon={Move} />
|
|
<div className="flex gap-2 mb-4">
|
|
{['X', 'Y', 'Z'].map(a => (
|
|
<button
|
|
key={a}
|
|
onClick={() => setAxis(a as any)}
|
|
className={`flex-1 py-2 font-tech font-bold text-lg border-b-2 transition-all ${axis === a ? 'text-neon-blue border-neon-blue bg-neon-blue/10' : 'text-slate-500 border-slate-700 hover:text-slate-300'}`}
|
|
>
|
|
{a}-AXIS
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<div className="bg-black/40 p-4 rounded border border-white/10 mb-4 text-center">
|
|
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-1">Current Position</div>
|
|
<div className="font-mono text-3xl text-neon-blue text-shadow-glow-blue">
|
|
{robotTarget[axis.toLowerCase() as 'x' | 'y' | 'z'].toFixed(3)}
|
|
<span className="text-sm text-slate-500 ml-2">mm</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2 overflow-y-auto flex-1 pr-2 custom-scrollbar">
|
|
{MOCK_POSITIONS.filter(p => p.axis === axis).map(p => (
|
|
<div key={p.id} className="flex items-center justify-between p-3 bg-white/5 border border-white/5 hover:border-neon-blue/50 transition-all group">
|
|
<span className="text-xs font-bold text-slate-300 group-hover:text-white">{p.name}</span>
|
|
<button
|
|
onClick={() => onMove(axis, p.value)}
|
|
className="px-3 py-1 bg-slate-800 hover:bg-neon-blue hover:text-black text-xs font-mono transition-colors"
|
|
>
|
|
GO {p.value}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex gap-2 mt-4">
|
|
<TechButton className="flex-1 font-mono text-lg" onClick={() => onMove(axis, robotTarget[axis.toLowerCase() as 'x' | 'y' | 'z'] - 0.1)}>-</TechButton>
|
|
<TechButton className="flex-1 font-mono text-lg" onClick={() => onMove(axis, robotTarget[axis.toLowerCase() as 'x' | 'y' | 'z'] + 0.1)}>+</TechButton>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|