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:
2025-11-25 23:39:38 +09:00
parent 6219c4c60e
commit 8bbd76e670
13 changed files with 1398 additions and 112 deletions

View 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>
);
};