import SwiftUI import WebKit import AppKit final class WebModel: ObservableObject { @Published var progress: Double = 0 @Published var loaded = false @Published var failed = false var webView: WKWebView? func reload() { failed = false; loaded = false; progress = 0 if let wv = webView { if wv.url == nil { wv.load(URLRequest(url: Ka.url)) } else { wv.reload() } } } } struct WebView: NSViewRepresentable { @ObservedObject var model: WebModel func makeCoordinator() -> Coordinator { Coordinator(model: model) } func makeNSView(context: Context) -> WKWebView { let cfg = WKWebViewConfiguration() cfg.websiteDataStore = .default() // cookie de session persistant cfg.defaultWebpagePreferences.allowsContentJavaScript = true let wv = WKWebView(frame: .zero, configuration: cfg) wv.navigationDelegate = context.coordinator wv.uiDelegate = context.coordinator wv.setValue(false, forKey: "drawsBackground") // fond transparent → crème wv.customUserAgent = "AdminKa-macOS/1.0 (Groupe KA console)" context.coordinator.observe(wv) model.webView = wv wv.load(URLRequest(url: Ka.url)) return wv } func updateNSView(_ nsView: WKWebView, context: Context) {} final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { let model: WebModel private var obs: NSKeyValueObservation? init(model: WebModel) { self.model = model } func observe(_ wv: WKWebView) { obs = wv.observe(\.estimatedProgress, options: [.new]) { [weak self] wv, _ in DispatchQueue.main.async { self?.model.progress = wv.estimatedProgress } } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { DispatchQueue.main.async { withAnimation(.easeOut(duration: 0.35)) { self.model.loaded = true } self.model.failed = false } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { fail(error) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { fail(error) } private func fail(_ error: Error) { if (error as NSError).code == NSURLErrorCancelled { return } DispatchQueue.main.async { self.model.failed = true } } // liens externes → navigateur par défaut func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url, let host = url.host, navigationAction.navigationType == .linkActivated, host != Ka.url.host { NSWorkspace.shared.open(url); decisionHandler(.cancel); return } decisionHandler(.allow) } func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if let url = navigationAction.request.url { NSWorkspace.shared.open(url) } return nil } // --- Panneaux JS natifs (confirm() indispensable pour le mode BYPASS) --- func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let a = NSAlert(); a.messageText = "Admin-Ka"; a.informativeText = message; a.addButton(withTitle: "OK") a.runModal(); completionHandler() } func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let a = NSAlert(); a.messageText = "Admin-Ka"; a.informativeText = message a.addButton(withTitle: "OK"); a.addButton(withTitle: "Annuler") completionHandler(a.runModal() == .alertFirstButtonReturn) } func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let a = NSAlert(); a.messageText = "Admin-Ka"; a.informativeText = prompt a.addButton(withTitle: "OK"); a.addButton(withTitle: "Annuler") let tf = NSTextField(frame: NSRect(x: 0, y: 0, width: 260, height: 24)); tf.stringValue = defaultText ?? "" a.accessoryView = tf completionHandler(a.runModal() == .alertFirstButtonReturn ? tf.stringValue : nil) } } }