import Cocoa
import WebKit
import SwiftUI
class WebRuleEditorViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
private var webView: WKWebView!
private var editingRule: YabaiRule?
var onSave: ((YabaiRule) -> Void)?
var onCancel: (() -> Void)?
init(editingRule: YabaiRule? = nil) {
self.editingRule = editingRule
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
let userController = WKUserContentController()
// Add script message handlers for communication
userController.add(self, name: "saveRule")
userController.add(self, name: "cancelRule")
webConfiguration.userContentController = userController
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
// Enable keyboard input and focus
webView.setValue(false, forKey: "drawsBackground")
self.view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// Load the HTML content
if let htmlPath = Bundle.main.path(forResource: "rule-editor", ofType: "html") {
let htmlUrl = URL(fileURLWithPath: htmlPath)
webView.loadFileURL(htmlUrl, allowingReadAccessTo: htmlUrl.deletingLastPathComponent())
} else {
// Fallback: inline HTML
loadInlineHTML()
}
// Ensure the web view can receive keyboard input
webView.becomeFirstResponder()
}
private func loadInlineHTML() {
let html = """
"""
webView.loadHTMLString(html, baseURL: nil)
}
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case "saveRule":
if let ruleDict = message.body as? [String: Any] {
// Extract values to make type-checking easier
let app = ruleDict["app"] as? String
let title = ruleDict["title"] as? String
let role = ruleDict["role"] as? String
let subrole = ruleDict["subrole"] as? String
let manage = (ruleDict["manage"] as? Bool ?? true) ? YabaiRule.ManageState.on : YabaiRule.ManageState.off
let sticky = (ruleDict["sticky"] as? Bool ?? false) ? YabaiRule.StickyState.on : YabaiRule.StickyState.off
let mouseFollowsFocus = ruleDict["mouse_follows_focus"] as? Bool ?? false
let layerString = ruleDict["layer"] as? String ?? "normal"
let layer = YabaiRule.WindowLayer(rawValue: layerString) ?? YabaiRule.WindowLayer.normal
let opacity = ruleDict["opacity"] as? Double ?? 1.0
let border = (ruleDict["border"] as? Bool ?? false) ? YabaiRule.BorderState.on : YabaiRule.BorderState.off
let rule = YabaiRule(
app: app,
title: title,
role: role,
subrole: subrole,
manage: manage,
sticky: sticky,
mouseFollowsFocus: mouseFollowsFocus,
layer: layer,
opacity: opacity,
border: border
)
onSave?(rule)
dismissController()
}
case "cancelRule":
onCancel?()
dismissController()
default:
break
}
}
private func dismissController() {
if let window = view.window {
window.close()
}
}
// MARK: - WKNavigationDelegate
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Web view loaded, ensure it can receive keyboard input
DispatchQueue.main.async {
webView.window?.makeFirstResponder(webView)
}
}
}
// MARK: - Window Controller
class WebRuleEditorWindowController: NSWindowController, NSWindowDelegate {
private var webViewController: WebRuleEditorViewController
init(editingRule: YabaiRule? = nil) {
webViewController = WebRuleEditorViewController(editingRule: editingRule)
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 500, height: 600),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
super.init(window: window)
window.center()
window.title = editingRule == nil ? "Create Rule" : "Edit Rule"
window.contentViewController = webViewController
window.isReleasedWhenClosed = false
window.delegate = self
webViewController.onSave = { [weak self] rule in
// Forward to parent window controller if needed
self?.onSave?(rule)
}
webViewController.onCancel = { [weak self] in
self?.onCancel?()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var onSave: ((YabaiRule) -> Void)?
var onCancel: (() -> Void)?
func showWindow() {
window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
// Ensure web view gets focus
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if let webView = self.webViewController.view as? WKWebView {
self.window?.makeFirstResponder(webView)
}
}
}
// NSWindowDelegate method
func windowWillClose(_ notification: Notification) {
onCancel?()
}
}