Initial commit: Industrial HMI system with component architecture
- 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>
This commit is contained in:
50
frontend/components/IOPanel.tsx
Normal file
50
frontend/components/IOPanel.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Activity } from 'lucide-react';
|
||||
import { IOPoint } from '../types';
|
||||
import { PanelHeader } from './common/PanelHeader';
|
||||
import { TechButton } from './common/TechButton';
|
||||
|
||||
interface IOPanelProps {
|
||||
ioPoints: IOPoint[];
|
||||
onToggle: (id: number, type: 'input' | 'output') => void;
|
||||
}
|
||||
|
||||
export const IOPanel: React.FC<IOPanelProps> = ({ ioPoints, onToggle }) => {
|
||||
const [tab, setTab] = useState<'in' | 'out'>('in');
|
||||
const points = ioPoints.filter(p => p.type === (tab === 'in' ? 'input' : 'output'));
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<PanelHeader title="I/O Monitor" icon={Activity} />
|
||||
<div className="flex gap-2 mb-4">
|
||||
<TechButton active={tab === 'in'} onClick={() => setTab('in')} className="flex-1">Inputs</TechButton>
|
||||
<TechButton active={tab === 'out'} onClick={() => setTab('out')} className="flex-1" variant="blue">Outputs</TechButton>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 overflow-y-auto pr-2 custom-scrollbar pb-4">
|
||||
{points.map(p => (
|
||||
<div
|
||||
key={p.id}
|
||||
onClick={() => onToggle(p.id, p.type)}
|
||||
className={`
|
||||
aspect-square flex flex-col items-center justify-center p-1 cursor-pointer transition-all border
|
||||
clip-tech
|
||||
${p.state
|
||||
? (p.type === 'output'
|
||||
? 'bg-neon-green/10 border-neon-green text-neon-green shadow-[0_0_10px_rgba(10,255,0,0.3)]'
|
||||
: 'bg-neon-yellow/10 border-neon-yellow text-neon-yellow shadow-[0_0_10px_rgba(255,230,0,0.3)]')
|
||||
: 'bg-slate-900/50 border-slate-700 text-slate-600 hover:border-slate-500'}
|
||||
`}
|
||||
>
|
||||
<div className={`w-2 h-2 rounded-full mb-2 ${p.state ? (p.type === 'output' ? 'bg-neon-green' : 'bg-neon-yellow') : 'bg-slate-800'}`}></div>
|
||||
<span className="font-mono text-[10px] font-bold">
|
||||
{p.type === 'input' ? 'I' : 'Q'}{p.id.toString().padStart(2, '0')}
|
||||
</span>
|
||||
<span className="text-[8px] text-center uppercase leading-tight mt-1 opacity-80 truncate w-full px-1">
|
||||
{p.name.replace(/(Sensor|Door|Lamp)/g, '')}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user