using System; using System.Collections.Generic; using System.IO; using System.Windows.Forms; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; using Newtonsoft.Json; using Project.WebUI; namespace Project.Dialog { public partial class fWebView : Form { private WebView2 webView; private Timer plcTimer; private WebSocketServer _wsServer; // Machine State (Simulated PLC Memory) private double currX = 0, currY = 0, currZ = 0; private double targetX = 0, targetY = 0, targetZ = 0; private bool[] inputs = new bool[32]; private bool[] outputs = new bool[32]; private string systemState = "IDLE"; private string currentRecipeId = "1"; // Default recipe public fWebView() { InitializeComponent(); InitializeWebView(); // Start WebSocket Server for HMR/Dev try { _wsServer = new WebSocketServer("http://localhost:8081/", this); } catch (Exception ex) { MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message); } // Set default inputs (Pressure OK, Estop OK) inputs[4] = true; inputs[6] = true; // Load event handler this.Load += FWebView_Load; } private void InitializeComponent() { this.SuspendLayout(); // Form this.ClientSize = new System.Drawing.Size(1200, 800); this.Text = "STD Label Attach - Web UI"; this.Name = "fWebView"; this.StartPosition = FormStartPosition.CenterScreen; this.ResumeLayout(false); } private async void InitializeWebView() { // 1. Setup Virtual Host (http://hmi.local) pointing to FrontEnd/dist folder // Navigate up from bin/debug/ to project root, then to FrontEnd/dist string projectRoot = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\..\..")); string wwwroot = Path.Combine(projectRoot, @"FrontEnd\dist"); this.Text = $"STD Label Attach - {wwwroot}"; webView = new WebView2(); webView.Dock = DockStyle.Fill; this.Controls.Add(webView); try { await webView.EnsureCoreWebView2Async(); if (!Directory.Exists(wwwroot)) { MessageBox.Show($"Could not find frontend build at:\n{wwwroot}\n\nPlease run 'npm run build' in the FrontEnd folder.", "Frontend Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning); // Fallback to local wwwroot if needed, or just allow it to fail gracefully Directory.CreateDirectory(wwwroot); } webView.CoreWebView2.SetVirtualHostNameToFolderMapping( "hmi.local", wwwroot, CoreWebView2HostResourceAccessKind.Allow ); // 2. Inject Native Object webView.CoreWebView2.AddHostObjectToScript("machine", new MachineBridge(this)); // 3. Load UI webView.Source = new Uri("http://hmi.local/index.html"); // Disable default browser keys (F5, F12 etc) if needed for kiosk mode webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; } catch (Exception ex) { MessageBox.Show($"WebView2 초기화 실패: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // --- Logic Loop --- private void PlcTimer_Tick(object sender, EventArgs e) { // 1. Simulate Motion (Move Current towards Target) currX = Lerp(currX, targetX, 0.1); currY = Lerp(currY, targetY, 0.1); currZ = Lerp(currZ, targetZ, 0.1); // 2. Prepare Data Packet var payload = new { type = "STATUS_UPDATE", sysState = systemState, position = new { x = currX, y = currY, z = currZ }, ioState = GetChangedIOs() // Function to return array of IO states }; string json = JsonConvert.SerializeObject(payload); // 3. Send to React via PostMessage (WebView2) if (webView != null && webView.CoreWebView2 != null) { webView.CoreWebView2.PostWebMessageAsJson(json); } // 4. Broadcast to WebSocket (Dev/HMR) _wsServer?.Broadcast(json); } private List GetChangedIOs() { // Simply return list of all active IOs or just send all for simplicity var list = new List(); for (int i = 0; i < 32; i++) { list.Add(new { id = i, type = "input", state = inputs[i] }); list.Add(new { id = i, type = "output", state = outputs[i] }); } return list; } private void FWebView_Load(object sender, EventArgs e) { // Setup Simulation Timer (50ms) plcTimer = new Timer(); plcTimer.Interval = 50; plcTimer.Tick += PlcTimer_Tick; plcTimer.Start(); } // --- Helper Methods called by Bridge --- public void SetTargetPosition(string axis, double val) { if (axis == "X") targetX = val; if (axis == "Y") targetY = val; if (axis == "Z") targetZ = val; } public void SetOutput(int id, bool state) { if (id < 32) outputs[id] = state; } public void HandleCommand(string cmd) { systemState = (cmd == "START") ? "RUNNING" : (cmd == "STOP") ? "PAUSED" : "IDLE"; } public void SetCurrentRecipe(string recipeId) { currentRecipeId = recipeId; Console.WriteLine($"[fWebView] Current recipe set to: {recipeId}"); // In real app, load recipe parameters here } public string GetCurrentRecipe() { return currentRecipeId; } private double Lerp(double a, double b, double t) => a + (b - a) * t; // JavaScript 실행 메서드 public async void ExecuteScriptAsync(string script) { try { string result = await webView.CoreWebView2.ExecuteScriptAsync(script); Console.WriteLine($"Script result: {result}"); } catch (Exception ex) { Console.WriteLine($"Script execution failed: {ex.Message}"); } } // C#에서 JavaScript로 메시지 전송 public void PostMessageToWeb(string message) { try { webView.CoreWebView2.PostWebMessageAsString(message); } catch (Exception ex) { Console.WriteLine($"PostMessage failed: {ex.Message}"); } } } }