← back to Ticket System
Fix desktop ticket bar click and popup visibility
071635225db531dd09b6c139bbcf564e8d0def5f · 2026-09-02 16:05:24 -0700 · Steve Abrams
Files touched
M desktop-bar/TicketBar.swift
Diff
commit 071635225db531dd09b6c139bbcf564e8d0def5f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 16:05:24 2026 -0700
Fix desktop ticket bar click and popup visibility
---
desktop-bar/TicketBar.swift | 79 +++++++++++++++++++++++++++++++++++----------
1 file changed, 62 insertions(+), 17 deletions(-)
diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index 2a637307..f64bac03 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -18,7 +18,6 @@ private final class BarTile: NSButton {
var menuProvider: (() -> NSMenu)?
var clickHandler: (() -> Void)?
var dragHandler: ((String, CGFloat) -> Void)?
- private var dragged = false
var widthConstraint: NSLayoutConstraint!
init(key: String, label: NSTextField, width: CGFloat) {
@@ -48,20 +47,8 @@ private final class BarTile: NSButton {
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
- override func mouseDown(with event: NSEvent) {
- dragged = false
- let start = event.locationInWindow
- while let next = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp]) {
- if next.type == .leftMouseUp { break }
- if abs(next.locationInWindow.x - start.x) > 3 {
- dragged = true
- dragHandler?(key, next.locationInWindow.x)
- }
- }
- if !dragged { showDropdown() }
- }
@objc private func showDropdown() {
- guard !dragged else { return }
+ NSLog("TicketBar tile pressed: %@", key)
if let clickHandler { clickHandler(); return }
guard let menu = menuProvider?() else { return }
NSApp.activate(ignoringOtherApps: true)
@@ -75,7 +62,7 @@ private final class BarTile: NSButton {
}
}
-private final class BarController: NSObject, NSApplicationDelegate, WKNavigationDelegate {
+private final class BarController: NSObject, NSApplicationDelegate, NSWindowDelegate, WKNavigationDelegate {
private var panel: NSPanel!
private let counts = NSTextField(labelWithString: "Tickets · loading…")
private let working = NSTextField(labelWithString: "Working · —")
@@ -86,6 +73,9 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
private var latestDetail = "No activity yet"
private var ticketPopup: NSWindow?
private var timer: Timer?
+ private var dragMonitor: Any?
+ private var draggingKey: String?
+ private var dragStartX: CGFloat?
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
@@ -138,10 +128,35 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
for tile in tiles { tile.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) }
open.setContentHuggingPriority(.required, for: .horizontal)
panel.contentView = stack
+ installDragMonitor()
reposition()
panel.orderFrontRegardless()
}
+ private func installDragMonitor() {
+ guard dragMonitor == nil else { return }
+ dragMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .leftMouseDragged, .leftMouseUp]) { [weak self] event in
+ guard let self, event.window === self.panel else { return event }
+ switch event.type {
+ case .leftMouseDown:
+ self.draggingKey = self.tiles.first(where: {
+ $0.bounds.contains($0.convert(event.locationInWindow, from: nil))
+ })?.key
+ self.dragStartX = event.locationInWindow.x
+ case .leftMouseDragged:
+ if let key = self.draggingKey, let start = self.dragStartX,
+ abs(event.locationInWindow.x - start) > 3 {
+ self.moveTile(key, toward: event.locationInWindow.x)
+ }
+ case .leftMouseUp:
+ self.draggingKey = nil
+ self.dragStartX = nil
+ default: break
+ }
+ return event
+ }
+ }
+
private func moveTile(_ key: String, toward windowX: CGFloat) {
guard let source = tiles.firstIndex(where: { $0.key == key }) else { return }
var target = source
@@ -221,7 +236,14 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
@objc private func openViewer() { NSWorkspace.shared.open(viewerURL) }
private func openTicketPopup() {
- if let ticketPopup { ticketPopup.makeKeyAndOrderFront(nil); NSApp.activate(ignoringOtherApps: true); return }
+ NSLog("TicketBar opening control window")
+ if let ticketPopup {
+ centerOnPointerScreen(ticketPopup)
+ ticketPopup.makeKeyAndOrderFront(nil)
+ ticketPopup.orderFrontRegardless()
+ NSApp.activate(ignoringOtherApps: true)
+ return
+ }
let size = NSSize(width: 1480, height: 860)
let popup = NSWindow(contentRect: NSRect(origin: .zero, size: size), styleMask: [.titled, .closable, .resizable, .miniaturizable], backing: .buffered, defer: false)
popup.title = "Fleet Ticket Control · All / Open / Blocked / Doing"
@@ -230,6 +252,7 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
popup.minSize = NSSize(width: 900, height: 560)
popup.isReleasedWhenClosed = false
popup.hidesOnDeactivate = false
+ popup.delegate = self
let web = WKWebView(frame: popup.contentView?.bounds ?? .zero)
web.navigationDelegate = self
web.autoresizingMask = [.width, .height]
@@ -238,13 +261,34 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
// a one-off Authorization header would authenticate the HTML navigation but
// leave the board's same-origin fetch() calls unauthenticated and blank.
web.load(URLRequest(url: viewerURL))
- popup.center()
+ centerOnPointerScreen(popup)
ticketPopup = popup
popup.makeKeyAndOrderFront(nil)
popup.orderFrontRegardless()
NSApp.activate(ignoringOtherApps: true)
}
+ func windowWillClose(_ notification: Notification) {
+ guard let closing = notification.object as? NSWindow, closing === ticketPopup else { return }
+ ticketPopup = nil
+ }
+
+ private func centerOnPointerScreen(_ window: NSWindow) {
+ let pointer = NSEvent.mouseLocation
+ let screen = NSScreen.screens.first(where: { NSMouseInRect(pointer, $0.frame, false) })
+ ?? NSScreen.main
+ ?? NSScreen.screens.first
+ guard let visible = screen?.visibleFrame else { window.center(); return }
+ let width = min(window.frame.width, visible.width - 40)
+ let height = min(window.frame.height, visible.height - 40)
+ window.setFrame(NSRect(
+ x: visible.midX - width / 2,
+ y: visible.midY - height / 2,
+ width: width,
+ height: height
+ ), display: true)
+ }
+
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
completionHandler(.useCredential, URLCredential(user: "admin", password: "DW2024!", persistence: .forSession))
@@ -286,6 +330,7 @@ private final class BarController: NSObject, NSApplicationDelegate, WKNavigation
self.workingDetails = doing.map { "\(self.shortID($0.id)) · \($0.title)" }
self.latestDetail = latestText
self.panel.orderFrontRegardless()
+ if let popup = self.ticketPopup, !popup.isMiniaturized { popup.orderFrontRegardless() }
}
} catch {
DispatchQueue.main.async { self.latest.stringValue = "Latest · ledger unavailable" }
← d9472b3e Keep ticket controls in a real window
·
back to Ticket System
·
Record desktop ticket bar interaction proof 35f0880e →