- 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>
141 lines
6.3 KiB
TypeScript
141 lines
6.3 KiB
TypeScript
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>
|
|
);
|
|
};
|