feat: Add vision controls, function menu, and custom alert dialogs
- Add Vision menu with Camera (QRCode) and Barcode (Keyence) controls - Add Function menu with Manage, Log Viewer, and folder navigation - Add quick action buttons (Manual, Light, Print, Cancel) to header - Replace browser alert() with custom AlertDialog component - Add MachineBridge methods for vision, lighting, folders, and manual operations - Add WebSocketServer handlers for all new commands - Add communication layer methods for frontend-backend integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
80
FrontEnd/components/AlertDialog.tsx
Normal file
80
FrontEnd/components/AlertDialog.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { AlertTriangle, CheckCircle, Info, XCircle } from 'lucide-react';
|
||||
|
||||
interface AlertDialogProps {
|
||||
isOpen: boolean;
|
||||
type: 'success' | 'error' | 'warning' | 'info';
|
||||
title: string;
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const AlertDialog: React.FC<AlertDialogProps> = ({ isOpen, type, title, message, onClose }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const getIcon = () => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return <CheckCircle className="w-12 h-12 text-green-500" />;
|
||||
case 'error':
|
||||
return <XCircle className="w-12 h-12 text-red-500" />;
|
||||
case 'warning':
|
||||
return <AlertTriangle className="w-12 h-12 text-amber-500" />;
|
||||
case 'info':
|
||||
return <Info className="w-12 h-12 text-blue-500" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getBorderColor = () => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return 'border-green-500';
|
||||
case 'error':
|
||||
return 'border-red-500';
|
||||
case 'warning':
|
||||
return 'border-amber-500';
|
||||
case 'info':
|
||||
return 'border-blue-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Dialog */}
|
||||
<div
|
||||
className={`relative bg-black/95 backdrop-blur-md border-2 ${getBorderColor()} rounded-lg shadow-2xl max-w-md w-full mx-4 animate-fadeIn`}
|
||||
>
|
||||
{/* Icon and Title */}
|
||||
<div className="flex items-center gap-4 p-6 border-b border-white/10">
|
||||
{getIcon()}
|
||||
<h2 className="text-xl font-tech font-bold text-white uppercase tracking-wider">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div className="p-6">
|
||||
<p className="text-slate-300 font-mono text-sm whitespace-pre-wrap leading-relaxed">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Button */}
|
||||
<div className="flex justify-end p-6 border-t border-white/10">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-2 bg-neon-blue/20 hover:bg-neon-blue/30 border border-neon-blue text-neon-blue font-tech font-bold rounded transition-colors shadow-glow-blue"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
140
FrontEnd/components/FunctionMenu.tsx
Normal file
140
FrontEnd/components/FunctionMenu.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Package, X, ChevronRight } from 'lucide-react';
|
||||
import { comms } from '../communication';
|
||||
import { useAlert } from '../contexts/AlertContext';
|
||||
|
||||
interface FunctionMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const FunctionMenu: React.FC<FunctionMenuProps> = ({ isOpen, onClose }) => {
|
||||
const [activeSubmenu, setActiveSubmenu] = useState<string | null>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const { showAlert } = useAlert();
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleCommand = async (commandFn: () => Promise<{ success: boolean; message: string }>, actionName: string) => {
|
||||
try {
|
||||
const result = await commandFn();
|
||||
if (result.success) {
|
||||
console.log(`[FunctionMenu] ${actionName}: ${result.message}`);
|
||||
} else {
|
||||
console.error(`[FunctionMenu] ${actionName} failed: ${result.message}`);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Failed`,
|
||||
message: result.message
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[FunctionMenu] ${actionName} error:`, error);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Error`,
|
||||
message: error.message || 'Unknown error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="absolute top-20 left-1/2 -translate-x-1/2 z-50 bg-black/95 backdrop-blur-md border border-neon-blue/50 rounded-lg shadow-glow-blue min-w-[300px]"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-white/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<Package className="w-5 h-5 text-neon-blue" />
|
||||
<h3 className="font-tech font-bold text-lg text-white">FUNCTION</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Menu Items */}
|
||||
<div className="p-2">
|
||||
{/* Manage */}
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openManage(), 'Manage')}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors font-tech"
|
||||
>
|
||||
<span>Manage</span>
|
||||
</button>
|
||||
|
||||
{/* Log Viewer */}
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openLogViewer(), 'Log Viewer')}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors font-tech"
|
||||
>
|
||||
<span>Log Viewer</span>
|
||||
</button>
|
||||
|
||||
{/* Open Folder */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onMouseEnter={() => setActiveSubmenu('folder')}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors font-tech"
|
||||
>
|
||||
<span>Open Folder</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Folder Submenu */}
|
||||
{activeSubmenu === 'folder' && (
|
||||
<div
|
||||
className="absolute left-full top-0 ml-1 bg-black/95 backdrop-blur-md border border-neon-blue/50 rounded-lg shadow-glow-blue min-w-[200px] p-2"
|
||||
onMouseLeave={() => setActiveSubmenu(null)}
|
||||
>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openProgramFolder(), 'Program Folder')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Program
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openLogFolder(), 'Log Folder')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Log
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openScreenshotFolder(), 'Screenshot Folder')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Screenshot
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openSavedDataFolder(), 'Saved Data Folder')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Saved Data
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Camera, ChevronRight, X } from 'lucide-react';
|
||||
import { comms } from '../communication';
|
||||
import { useAlert } from '../contexts/AlertContext';
|
||||
|
||||
interface VisionMenuProps {
|
||||
isOpen: boolean;
|
||||
@@ -9,6 +11,7 @@ interface VisionMenuProps {
|
||||
export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
const [activeSubmenu, setActiveSubmenu] = useState<string | null>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const { showAlert } = useAlert();
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -28,14 +31,27 @@ export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleQRCodeCamera = () => {
|
||||
console.log('[VisionMenu] Camera (QRCode) clicked');
|
||||
// TODO: Implement QR Code camera functionality
|
||||
};
|
||||
|
||||
const handleKeyenceBarcode = () => {
|
||||
console.log('[VisionMenu] Barcode (Keyence) clicked');
|
||||
// TODO: Implement Keyence barcode functionality
|
||||
const handleVisionCommand = async (commandFn: () => Promise<{ success: boolean; message: string }>, actionName: string) => {
|
||||
try {
|
||||
const result = await commandFn();
|
||||
if (result.success) {
|
||||
console.log(`[VisionMenu] ${actionName}: ${result.message}`);
|
||||
} else {
|
||||
console.error(`[VisionMenu] ${actionName} failed: ${result.message}`);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Failed`,
|
||||
message: result.message
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[VisionMenu] ${actionName} error:`, error);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Error`,
|
||||
message: error.message || 'Unknown error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -63,7 +79,6 @@ export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
<div className="relative">
|
||||
<button
|
||||
onMouseEnter={() => setActiveSubmenu('qrcode')}
|
||||
onClick={handleQRCodeCamera}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors font-tech"
|
||||
>
|
||||
<span>Camera (QRCode)</span>
|
||||
@@ -77,26 +92,26 @@ export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
onMouseLeave={() => setActiveSubmenu(null)}
|
||||
>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] QRCode - Connect')}
|
||||
onClick={() => handleVisionCommand(() => comms.cameraConnect(), 'Camera Connect')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<div className="h-px bg-white/10 my-1" />
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] QRCode - Get Image')}
|
||||
onClick={() => handleVisionCommand(() => comms.cameraGetImage(), 'Camera Get Image')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Get Image
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] QRCode - Live View')}
|
||||
onClick={() => handleVisionCommand(() => comms.cameraLiveView(), 'Camera Live View')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Live View
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] QRCode - Read Test')}
|
||||
onClick={() => handleVisionCommand(() => comms.cameraReadTest(), 'Camera Read Test')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Read Test
|
||||
@@ -109,7 +124,6 @@ export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
<div className="relative">
|
||||
<button
|
||||
onMouseEnter={() => setActiveSubmenu('keyence')}
|
||||
onClick={handleKeyenceBarcode}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors font-tech"
|
||||
>
|
||||
<span>Barcode (Keyence)</span>
|
||||
@@ -123,26 +137,26 @@ export const VisionMenu: React.FC<VisionMenuProps> = ({ isOpen, onClose }) => {
|
||||
onMouseLeave={() => setActiveSubmenu(null)}
|
||||
>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] Keyence - Get Image')}
|
||||
onClick={() => handleVisionCommand(() => comms.keyenceGetImage(), 'Keyence Get Image')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Get Image
|
||||
</button>
|
||||
<div className="h-px bg-white/10 my-1" />
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] Keyence - Trigger On')}
|
||||
onClick={() => handleVisionCommand(() => comms.keyenceTriggerOn(), 'Keyence Trigger On')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Trigger On
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] Keyence - Trigger Off')}
|
||||
onClick={() => handleVisionCommand(() => comms.keyenceTriggerOff(), 'Keyence Trigger Off')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Trigger Off
|
||||
</button>
|
||||
<button
|
||||
onClick={() => console.log('[VisionMenu] Keyence - Save Image')}
|
||||
onClick={() => handleVisionCommand(() => comms.keyenceSaveImage(), 'Keyence Save Image')}
|
||||
className="w-full px-4 py-2 text-left text-slate-300 hover:bg-neon-blue/10 hover:text-neon-blue rounded transition-colors text-sm"
|
||||
>
|
||||
Save Image
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Activity, Settings, Move, Camera, Layers, Cpu, Target } from 'lucide-react';
|
||||
import { Activity, Settings, Move, Camera, Layers, Cpu, Target, Lightbulb, Printer, XCircle, Package, BookOpen } from 'lucide-react';
|
||||
import { VisionMenu } from '../VisionMenu';
|
||||
import { FunctionMenu } from '../FunctionMenu';
|
||||
import { comms } from '../../communication';
|
||||
import { useAlert } from '../../contexts/AlertContext';
|
||||
|
||||
interface HeaderProps {
|
||||
currentTime: Date;
|
||||
@@ -14,13 +17,40 @@ export const Header: React.FC<HeaderProps> = ({ currentTime, onTabChange, active
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [showVisionMenu, setShowVisionMenu] = useState(false);
|
||||
const [showFunctionMenu, setShowFunctionMenu] = useState(false);
|
||||
const { showAlert } = useAlert();
|
||||
|
||||
const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview;
|
||||
const isIOPage = location.pathname === '/io-monitor';
|
||||
|
||||
const handleCommand = async (commandFn: () => Promise<{ success: boolean; message: string }>, actionName: string) => {
|
||||
try {
|
||||
const result = await commandFn();
|
||||
if (result.success) {
|
||||
console.log(`[Header] ${actionName}: ${result.message}`);
|
||||
// Show success message if needed
|
||||
} else {
|
||||
console.error(`[Header] ${actionName} failed: ${result.message}`);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Failed`,
|
||||
message: result.message
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[Header] ${actionName} error:`, error);
|
||||
showAlert({
|
||||
type: 'error',
|
||||
title: `${actionName} Error`,
|
||||
message: error.message || 'Unknown error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<VisionMenu isOpen={showVisionMenu} onClose={() => setShowVisionMenu(false)} />
|
||||
<FunctionMenu isOpen={showFunctionMenu} onClose={() => setShowFunctionMenu(false)} />
|
||||
<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"
|
||||
@@ -48,15 +78,50 @@ export const Header: React.FC<HeaderProps> = ({ currentTime, onTabChange, active
|
||||
|
||||
{/* Top Navigation */}
|
||||
<div className="flex items-center gap-2 pointer-events-auto">
|
||||
{/* IO Tab Switcher (only on IO page) */}
|
||||
|
||||
{/* Quick Action Buttons */}
|
||||
<div className="bg-black/40 backdrop-blur-md p-1 rounded-2xl border border-white/10 flex gap-1 mr-2">
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openManual(), 'Manual')}
|
||||
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-blue-400 hover:bg-white/5"
|
||||
title="Open Manual"
|
||||
>
|
||||
<BookOpen className="w-5 h-5" />
|
||||
<span className="leading-tight">MANUAL</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.toggleLight(), 'Toggle Light')}
|
||||
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-amber-400 hover:bg-white/5"
|
||||
title="Toggle Room Light"
|
||||
>
|
||||
<Lightbulb className="w-5 h-5" />
|
||||
<span className="leading-tight">LIGHT</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.openManualPrint(), 'Manual Print')}
|
||||
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-cyan-400 hover:bg-white/5"
|
||||
title="Open Manual Print"
|
||||
>
|
||||
<Printer className="w-5 h-5" />
|
||||
<span className="leading-tight">PRINT</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCommand(() => comms.cancelJob(), 'Cancel Job')}
|
||||
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-red-400 hover:bg-white/5"
|
||||
title="Cancel Current Job"
|
||||
>
|
||||
<XCircle className="w-5 h-5" />
|
||||
<span className="leading-tight">CANCEL</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Main Navigation */}
|
||||
<div className="bg-black/40 backdrop-blur-md p-1 rounded-2xl 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: 'function', icon: Package, label: 'FUNCTION', path: '/' },
|
||||
{ id: 'setting', icon: Settings, label: 'CONFIG', path: '/' },
|
||||
{ id: 'initialize', icon: Target, label: 'INITIALIZE', path: '/' }
|
||||
].map(item => {
|
||||
@@ -72,20 +137,35 @@ export const Header: React.FC<HeaderProps> = ({ currentTime, onTabChange, active
|
||||
navigate('/io-monitor');
|
||||
onTabChange(null);
|
||||
setShowVisionMenu(false);
|
||||
setShowFunctionMenu(false);
|
||||
} else if (item.id === 'camera') {
|
||||
setShowVisionMenu(!showVisionMenu);
|
||||
onTabChange(null);
|
||||
// Camera button does nothing on click
|
||||
} else if (item.id === 'function') {
|
||||
// Function button does nothing on click
|
||||
} else {
|
||||
if (location.pathname !== '/') {
|
||||
navigate('/');
|
||||
}
|
||||
setShowVisionMenu(false);
|
||||
setShowFunctionMenu(false);
|
||||
onTabChange(activeTab === item.id ? null : item.id as any);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (item.id === 'camera') {
|
||||
setShowVisionMenu(true);
|
||||
setShowFunctionMenu(false);
|
||||
} else if (item.id === 'function') {
|
||||
setShowFunctionMenu(true);
|
||||
setShowVisionMenu(false);
|
||||
} else {
|
||||
setShowVisionMenu(false);
|
||||
setShowFunctionMenu(false);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px]
|
||||
${isActive
|
||||
${isActive || (item.id === 'camera' && showVisionMenu) || (item.id === 'function' && showFunctionMenu)
|
||||
? 'bg-neon-blue/10 text-neon-blue border-neon-blue shadow-glow-blue'
|
||||
: 'text-slate-400 hover:text-white hover:bg-white/5'}
|
||||
`}
|
||||
|
||||
Reference in New Issue
Block a user