- {/* IO Tab Switcher (only on IO page) */}
-
+ {/* Quick Action Buttons */}
+
{[
{ 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
= ({ 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'}
`}
diff --git a/FrontEnd/contexts/AlertContext.tsx b/FrontEnd/contexts/AlertContext.tsx
new file mode 100644
index 0000000..c3fec67
--- /dev/null
+++ b/FrontEnd/contexts/AlertContext.tsx
@@ -0,0 +1,118 @@
+import React, { createContext, useContext, useState, ReactNode } from 'react';
+
+interface AlertConfig {
+ type: 'success' | 'error' | 'warning' | 'info';
+ title: string;
+ message: string;
+}
+
+interface AlertContextType {
+ showAlert: (config: AlertConfig) => void;
+}
+
+const AlertContext = createContext(undefined);
+
+export const useAlert = () => {
+ const context = useContext(AlertContext);
+ if (!context) {
+ throw new Error('useAlert must be used within AlertProvider');
+ }
+ return context;
+};
+
+interface AlertProviderProps {
+ children: ReactNode;
+}
+
+export const AlertProvider: React.FC = ({ children }) => {
+ const [alertConfig, setAlertConfig] = useState<(AlertConfig & { isOpen: boolean }) | null>(null);
+
+ const showAlert = (config: AlertConfig) => {
+ setAlertConfig({ ...config, isOpen: true });
+ };
+
+ const closeAlert = () => {
+ setAlertConfig(null);
+ };
+
+ return (
+
+ {children}
+ {alertConfig && (
+
+ {/* Backdrop */}
+
+
+ {/* Dialog */}
+
+ {/* Icon and Title */}
+
+ {getIcon(alertConfig.type)}
+
+ {alertConfig.title}
+
+
+
+ {/* Message */}
+
+
+ {alertConfig.message}
+
+
+
+ {/* Button */}
+
+
+
+
+
+ )}
+
+ );
+};
+
+const getIcon = (type: 'success' | 'error' | 'warning' | 'info') => {
+ const icons = {
+ success: (
+
+ ),
+ error: (
+
+ ),
+ warning: (
+
+ ),
+ info: (
+
+ ),
+ };
+ return icons[type];
+};
+
+const getBorderColor = (type: 'success' | 'error' | 'warning' | 'info') => {
+ const colors = {
+ success: 'border-green-500',
+ error: 'border-red-500',
+ warning: 'border-amber-500',
+ info: 'border-blue-500',
+ };
+ return colors[type];
+};
diff --git a/FrontEnd/pages/HomePage.tsx b/FrontEnd/pages/HomePage.tsx
index 05e9cdb..7593840 100644
--- a/FrontEnd/pages/HomePage.tsx
+++ b/FrontEnd/pages/HomePage.tsx
@@ -21,6 +21,7 @@ interface HomePageProps {
doorStates: { front: boolean; right: boolean; left: boolean; back: boolean };
isLowPressure: boolean;
isEmergencyStop: boolean;
+ isHostConnected: boolean;
activeTab: 'recipe' | 'motion' | 'camera' | 'setting' | 'initialize' | null;
onSelectRecipe: (r: Recipe) => void;
onMove: (axis: 'X' | 'Y' | 'Z', val: number) => void;
@@ -39,6 +40,7 @@ export const HomePage: React.FC = ({
doorStates,
isLowPressure,
isEmergencyStop,
+ isHostConnected,
activeTab,
onSelectRecipe,
onMove,
@@ -56,6 +58,16 @@ export const HomePage: React.FC = ({
{/* Center Alarms */}
+ {!isHostConnected && !isEmergencyStop && (
+
+
+
+
HOST DISCONNECTED
+
WAITING FOR CONNECTION...
+
PLEASE CHECK THE HANDLER PROGRAM
+
+
+ )}
{isEmergencyStop && (
@@ -65,7 +77,7 @@ export const HomePage: React.FC = ({
)}
- {isLowPressure && !isEmergencyStop && (
+ {isLowPressure && !isEmergencyStop && isHostConnected && (
diff --git a/FrontEnd/tailwind.config.js b/FrontEnd/tailwind.config.js
index 29ecaba..43448f9 100644
--- a/FrontEnd/tailwind.config.js
+++ b/FrontEnd/tailwind.config.js
@@ -37,6 +37,7 @@ export default {
'gradient': 'gradient 15s ease infinite',
'scan': 'scan 4s linear infinite',
'spin-slow': 'spin 10s linear infinite',
+ 'fadeIn': 'fadeIn 0.2s ease-out',
},
keyframes: {
glow: {
@@ -51,6 +52,10 @@ export default {
scan: {
'0%': { transform: 'translateY(-100%)' },
'100%': { transform: 'translateY(100%)' },
+ },
+ fadeIn: {
+ '0%': { opacity: '0', transform: 'scale(0.95)' },
+ '100%': { opacity: '1', transform: 'scale(1)' },
}
}
}
diff --git a/Handler/Project/WebUI/MachineBridge.cs b/Handler/Project/WebUI/MachineBridge.cs
index f97fe98..6b89f7e 100644
--- a/Handler/Project/WebUI/MachineBridge.cs
+++ b/Handler/Project/WebUI/MachineBridge.cs
@@ -949,5 +949,452 @@ namespace Project.WebUI
return JsonConvert.SerializeObject(new List