feat: Implement recipe selection with backend integration
Backend changes (C#): - Add SelectRecipe method to MachineBridge for recipe selection - Add currentRecipeId tracking in MainForm - Implement SELECT_RECIPE handler in WebSocketServer Frontend changes (React/TypeScript): - Add selectRecipe method to communication layer - Update handleSelectRecipe to call backend and handle response - Recipe selection updates ModelInfoPanel automatically - Add error handling and logging for recipe operations Layout improvements: - Add Layout component with persistent Header and Footer - Create separate IOMonitorPage for full-screen I/O monitoring - Add dynamic IO tab switcher in Header (Inputs/Outputs) - Ensure consistent UI across all pages 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
100
frontend/components/layout/Header.tsx
Normal file
100
frontend/components/layout/Header.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Activity, Settings, Move, Camera, Layers, Cpu } from 'lucide-react';
|
||||
|
||||
interface HeaderProps {
|
||||
currentTime: Date;
|
||||
onTabChange: (tab: 'recipe' | 'motion' | 'camera' | 'setting' | null) => void;
|
||||
activeTab: 'recipe' | 'motion' | 'camera' | 'setting' | null;
|
||||
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({ currentTime, onTabChange, activeTab }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview;
|
||||
const isIOPage = location.pathname === '/io-monitor';
|
||||
|
||||
return (
|
||||
<header className="absolute top-0 left-0 right-0 h-20 px-6 flex items-center justify-between z-40 bg-gradient-to-b from-black/80 to-transparent pointer-events-none">
|
||||
<div
|
||||
className="flex items-center gap-4 pointer-events-auto cursor-pointer group"
|
||||
onClick={() => {
|
||||
navigate('/');
|
||||
onTabChange(null);
|
||||
}}
|
||||
>
|
||||
<div className="w-12 h-12 border-2 border-neon-blue flex items-center justify-center rounded shadow-glow-blue bg-black/50 backdrop-blur group-hover:bg-neon-blue/10 transition-colors">
|
||||
<Cpu className="text-neon-blue w-8 h-8 animate-pulse-slow" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-tech font-bold text-white tracking-widest uppercase italic text-shadow-glow-blue group-hover:text-neon-blue transition-colors">
|
||||
EQUI-HANDLER <span className="text-neon-blue text-sm not-italic">PRO</span>
|
||||
</h1>
|
||||
<div className="flex gap-2 text-[10px] text-neon-blue/70 font-mono">
|
||||
<span>SYS.VER 4.2.0</span>
|
||||
<span>|</span>
|
||||
<span className={isWebView ? "text-neon-green" : "text-amber-500"}>
|
||||
LINK: {isWebView ? "NATIVE" : "SIMULATION"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Navigation */}
|
||||
<div className="flex items-center gap-2 pointer-events-auto">
|
||||
{/* IO Tab Switcher (only on IO page) */}
|
||||
|
||||
|
||||
<div className="bg-black/40 backdrop-blur-md p-1 rounded-full border border-white/10 flex gap-1">
|
||||
{[
|
||||
{ id: 'recipe', icon: Layers, label: 'RECIPE', path: '/' },
|
||||
{ id: 'io', icon: Activity, label: 'I/O MONITOR', path: '/io-monitor' },
|
||||
{ id: 'motion', icon: Move, label: 'MOTION', path: '/' },
|
||||
{ id: 'camera', icon: Camera, label: 'VISION', path: '/' },
|
||||
{ id: 'setting', icon: Settings, label: 'CONFIG', path: '/' }
|
||||
].map(item => {
|
||||
const isActive = item.id === 'io'
|
||||
? location.pathname === '/io-monitor'
|
||||
: activeTab === item.id;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
if (item.id === 'io') {
|
||||
navigate('/io-monitor');
|
||||
onTabChange(null);
|
||||
} else {
|
||||
if (location.pathname !== '/') {
|
||||
navigate('/');
|
||||
}
|
||||
onTabChange(activeTab === item.id ? null : item.id as any);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
flex items-center gap-2 px-6 py-2 rounded-full font-tech font-bold text-sm transition-all border border-transparent
|
||||
${isActive
|
||||
? 'bg-neon-blue/10 text-neon-blue border-neon-blue shadow-glow-blue'
|
||||
: 'text-slate-400 hover:text-white hover:bg-white/5'}
|
||||
`}
|
||||
>
|
||||
<item.icon className="w-4 h-4" /> {item.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right pointer-events-auto">
|
||||
<div className="text-2xl font-mono font-bold text-white text-shadow-glow-blue">
|
||||
{currentTime.toLocaleTimeString('en-GB')}
|
||||
</div>
|
||||
<div className="text-xs font-tech text-slate-400 tracking-[0.3em]">
|
||||
{currentTime.toLocaleDateString().toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user