From 8d8c0ee0c3b7b64cade721243b96cc388cf0c916 Mon Sep 17 00:00:00 2001 From: Waters Church Date: Sun, 25 Oct 2026 13:05:14 -0400 Subject: [PATCH] ProdDash Viewer 1.0.0: a Mac app that only shows ProdDash, with a tab per installation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new Apple-silicon app in viewer/macos (Swift, no dependencies, ~1 MB): one window showing a ProdDash dashboard in WebKit, and nothing else. It runs no server; it points at any ProdDash on the network. - Connections (name + address) in Settings; the add/edit sheet asks /api/modules whether ProdDash answers as you type. No port means 24500. - Two or more connections bring a slim tab bar of their names. In a window it floats over the dashboard and comes out while the pointer is on the title bar; it is not part of the title bar, because anything there makes WebKit push the page down. In full screen it is a titlebar accessory that comes down with the menu bar, and it flashes up after each switch. ⌃Tab / ⌃⇧Tab and ⌘1–9 switch from the keyboard. - Full screen, reopening in full screen if it quit there; window size and the selected tab are kept too. The dashboard's own full-screen button works as well. - Menus: View (Back, Forward, Reload Page, Enter Full Screen), Settings, Edit (copy/paste in the page's text fields), Window, Help (a guide that ships in the app). - Every installation keeps its own live web view. One that can't be reached says why and retries at 2, 4, 8, then every 15 s; a redirect off the installation is reported, not handed to the browser. JS alert, confirm and prompt show as sheets; links to other sites open in the browser. - General settings: open at login (SMAppService), keep the display awake while a dashboard is on screen (default on). build.sh signs with the Developer ID when one is installed and makes a notarised DMG with --notarize, as the launcher's does. It needs Xcode: with only the command line tools the macOS 27 SDK can't expand SwiftUI's @State. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 + CLAUDE.md | 7 +- README.md | 16 + viewer/macos/README.md | 121 ++++ viewer/macos/Resources/AppIcon.png | Bin 0 -> 172481 bytes viewer/macos/Resources/Help/index.html | 78 +++ viewer/macos/Resources/Info.plist | 44 ++ viewer/macos/Resources/make-icon.swift | 78 +++ viewer/macos/Sources/AppDelegate.swift | 110 ++++ viewer/macos/Sources/Connections.swift | 169 ++++++ viewer/macos/Sources/DashboardPage.swift | 425 ++++++++++++++ .../macos/Sources/HelpWindowController.swift | 42 ++ .../Sources/SettingsWindowController.swift | 299 ++++++++++ viewer/macos/Sources/TabBar.swift | 77 +++ viewer/macos/Sources/ViewerSettings.swift | 61 ++ .../Sources/ViewerWindowController.swift | 544 ++++++++++++++++++ viewer/macos/Sources/main.swift | 8 + viewer/macos/build.sh | 149 +++++ 18 files changed, 2229 insertions(+), 2 deletions(-) create mode 100644 viewer/macos/README.md create mode 100644 viewer/macos/Resources/AppIcon.png create mode 100644 viewer/macos/Resources/Help/index.html create mode 100644 viewer/macos/Resources/Info.plist create mode 100644 viewer/macos/Resources/make-icon.swift create mode 100644 viewer/macos/Sources/AppDelegate.swift create mode 100644 viewer/macos/Sources/Connections.swift create mode 100644 viewer/macos/Sources/DashboardPage.swift create mode 100644 viewer/macos/Sources/HelpWindowController.swift create mode 100644 viewer/macos/Sources/SettingsWindowController.swift create mode 100644 viewer/macos/Sources/TabBar.swift create mode 100644 viewer/macos/Sources/ViewerSettings.swift create mode 100644 viewer/macos/Sources/ViewerWindowController.swift create mode 100644 viewer/macos/Sources/main.swift create mode 100755 viewer/macos/build.sh diff --git a/.gitignore b/.gitignore index 3b6c710..cdf67fc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ launcher/macos/build/ launcher/macos/.cache/ launcher/windows/build/ launcher/windows/.cache/ + +# the Viewer's build output +viewer/macos/build/ diff --git a/CLAUDE.md b/CLAUDE.md index 117e8b0..2ce5c67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,10 +4,13 @@ Modular production dashboard web app for Waters Church, developed in this repo. The app lives at the repo root and is built: zero-dependency Node server (`server.js`, Node 18+), dashboard shell client in `public/`, admin page at `/admin`, modules in `modules/` (the repo's copies — a packaged install ships -none and installs them from the admin page's catalog), and a native macOS menu-bar launcher in +none and installs them from the admin page's catalog), a native macOS menu-bar launcher in `launcher/macos/` (Swift, no dependencies — the shipped macOS app: it carries the server and its own Node runtime, supervises them, and owns the macOS -permissions modules declare; see its README). Runtime state (module +permissions modules declare; see its README), and **ProdDash Viewer** in +`viewer/macos/` (Swift, no dependencies — a separate Apple-silicon app that +only displays one or more ProdDash installations in WebKit, with an +auto-hiding tab bar; it runs no server; see its README). Runtime state (module config, named layouts) is written to a per-machine data directory outside the checkout (`PRODDASH_DATA_DIR` overrides; see README "Where settings are kept") — never add code that writes runtime state into the repo folder. diff --git a/README.md b/README.md index 56bcd19..7b1d3e6 100644 --- a/README.md +++ b/README.md @@ -618,6 +618,19 @@ sheet's notes, as ProPresenter does for Vista — point Vista Cues' first sender at `127.0.0.1:5104` and paste the sheet it prints). See the testing section of the [module guide](docs/MODULE-GUIDE.md). +## ProdDash Viewer — a Mac that only shows the dashboard + +For a Mac that should show ProdDash and nothing else — a booth display, a +second screen watching the other campus — there is **ProdDash Viewer** +([viewer/macos](viewer/macos)): one window, the dashboard, and a slim +auto-hiding tab bar when it is set up with more than one installation. It +runs no server of its own; point it at any ProdDash on the network. Full +screen, reopening as it was left, and keeping the display awake are built in. + +```bash +cd viewer/macos && ./build.sh --dmg +``` + ## Install as a desktop web app Open the page in a browser first, then: @@ -648,6 +661,9 @@ ProdDash/ ├── package.json name + version (what the update check compares) ├── config/ proddash.json defaults (runtime state lives in the │ per-machine data directory — see "Configure") +├── launcher/macos/ the macOS app: menu-bar launcher carrying the server +│ and its own Node (launcher/windows: the Windows bundle) +├── viewer/macos/ ProdDash Viewer: a Mac app that only shows dashboards ├── docs/MODULE-GUIDE.md how to build and install a module ├── docs/ALERTS.md the alerts feature: levels, channels, admin, API ├── tools/prodcom-mock.js mock ProdCom API for development diff --git a/viewer/macos/README.md b/viewer/macos/README.md new file mode 100644 index 0000000..e00c02b --- /dev/null +++ b/viewer/macos/README.md @@ -0,0 +1,121 @@ +# ProdDash Viewer for macOS + +A Mac app whose only job is to show ProdDash. It opens one installation's +dashboard in its own window — or several, with a slim tab bar to switch +between them — and does nothing else: no address bar, no bookmarks, no +browser to wander off in. It is for the screens that just *show* a dashboard: +a booth display, a stage-manager's laptop, a second Mac watching the other +campus. + +It runs no server. ProdDash itself is installed with the menu-bar app in +[launcher/macos](../../launcher/macos); the Viewer points at that, or at any +other ProdDash on the network. + +Apple silicon, macOS 13 or newer. Native Swift (AppKit with a little SwiftUI, +WebKit for the page), no dependencies, about 1 MB. + +## Build + +```bash +./build.sh # build/ProdDash Viewer.app +./build.sh --dmg # …and build/ProdDash-Viewer--arm64.dmg +./build.sh --install # …and copy it to /Applications +./build.sh --run # …and open it +./build.sh --notarize # with --dmg: notarise and staple (Developer ID) +``` + +Needs Xcode, selected with `xcode-select` — with only the command line +tools, the macOS 27 SDK can't expand SwiftUI's `@State` macro and the +compile fails. Signing and notarising work exactly as +for ProdDash's own app — a Developer ID certificate when one is installed, +ad-hoc otherwise, and the same `proddash` notarytool profile; see +[launcher/macos/README.md](../../launcher/macos/README.md#signing). The +Viewer keeps its own version number (`VERSION` in `build.sh`), since it works +with any ProdDash rather than shipping with one. + +The icon is `Resources/AppIcon.png`, drawn by `Resources/make-icon.swift` — +ProdDash's tile mark on a display, so the two apps tell apart in Finder. +Run `swift Resources/make-icon.swift Resources/AppIcon.png` to redraw it. + +## Using it + +**Settings…** (⌘,) → **Connections** → **+**: a name (what the tab says) and +the address you open ProdDash at in a browser, such as `10.3.11.152:24500`. +Without a port, ProdDash's 24500 is assumed; a path is kept, so an address +can open a particular page. The sheet asks the address whether ProdDash is +answering as you type, but saves either way — the installation may simply be +switched off right now. Double-click a connection to change it, drag to +reorder the tabs. + +With two or more connections the tab bar appears. In a window it floats over +the top of the dashboard, comes out while the pointer is on the title bar and +tucks away after; in full screen it comes down with the menu bar, and shows +for a moment after each switch. ⌃Tab / ⌃⇧Tab and ⌘1…⌘9 switch from the +keyboard. A tab marked ⚠︎ is an installation that isn't answering. + +The menus are short on purpose: **View** has Back (⌘[), Forward (⌘]), Reload +Page (⌘R) and Enter Full Screen; the rest is what every Mac app has. **Edit** +is there because without it Copy and Paste do nothing in the dashboard's and +the admin page's text fields. **Help** opens the guide that ships in the app. + +**Settings → General**: **Open at login**, and **Keep the display awake** +(on by default) — while a dashboard is on screen, the display doesn't dim or +sleep. Minimised, hidden or covered, it lets go. + +## How it behaves + +- **Every dashboard stays live.** Each connection has its own web view, kept + running behind the one on screen, so switching is instant and nothing has + to reconnect. +- **The page never jumps.** In a window the tab bar is an overlay rather than + part of the title bar: anything in the title bar makes WebKit push the + whole page down to clear it, and a dashboard re-flowing every time the bar + came and went would be worse than no bar. In full screen it *is* a title + bar accessory — there the title bar lives in a window of its own, and the + page stays put. +- **It comes back by itself.** An installation that can't be reached shows + why (nothing answering, can't be found, no network — including macOS + withholding Local Network access) and is retried at 2, 4, 8 and then every + 15 seconds. Once a dashboard has loaded, ProdDash's own service worker and + reconnect logic carry it through a server restart. +- **It comes back as it was left.** The window's size and place, the tab that + was showing, and full screen: quit in full screen and it reopens in full + screen — so a booth Mac set to log in and open the Viewer at login returns + to the dashboard on its own after a restart. +- **It stays on ProdDash.** Links to the installation's own pages open in + the window; links anywhere else (a Planning Center item, the release notes) + open in the default browser, as do downloads. The page's `alert`, + `confirm` and `prompt` — the admin page confirms before deleting a layout, + Band Lineup prompts for names — show as sheets titled with the + installation's name. +- **The dashboard's own full-screen button works**, in addition to the + window's: it fills the screen with the dashboard alone; Esc leaves. +- Closing the window quits the app, as with System Settings — there is only + ever the one window. +- Safari's **Develop** menu can inspect a dashboard running in the Viewer. + +## Files + +``` +Sources/ + main.swift NSApplication, nothing else + AppDelegate.swift lifecycle and the menus + ViewerWindowController.swift the window: pages, tab bar overlay/accessory, + full screen, keyboard switching, display awake + DashboardPage.swift one connection's web view, status view, retry, + navigation policy, JavaScript dialogs + TabBar.swift the tabs (SwiftUI), shared by overlay and accessory + Connections.swift Connection, address rules, the store, the probe + ViewerSettings.swift preferences and the login item + SettingsWindowController.swift Settings: Connections and General + HelpWindowController.swift Help → ProdDash Viewer Help +Resources/ + Info.plist bundle id org.waterschurch.proddash.viewer + Help/index.html the guide shown by the Help menu + AppIcon.png, make-icon.swift the icon and what draws it +``` + +Preferences live in `~/Library/Preferences/org.waterschurch.proddash.viewer.plist` +(connections, the selected tab, full screen, keep awake); the web views' +storage — each dashboard's own per-browser settings — in WebKit's default +store for the app. diff --git a/viewer/macos/Resources/AppIcon.png b/viewer/macos/Resources/AppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd2f457e1bc4f4716e7ef8f3a906ce81c0a1b657 GIT binary patch literal 172481 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

#YV{AU_0s+HBXIV2>-+{ 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 literal 0 HcmV?d00001 diff --git a/viewer/macos/Resources/Help/index.html b/viewer/macos/Resources/Help/index.html new file mode 100644 index 0000000..7c2f1f9 --- /dev/null +++ b/viewer/macos/Resources/Help/index.html @@ -0,0 +1,78 @@ + + + + + +ProdDash Viewer Help + + + + +

ProdDash Viewer

+

A window onto ProdDash. It shows one installation’s dashboard — or several, with a tab for each — and nothing else.

+ +

Add an installation

+
    +
  • Choose ProdDash Viewer → Settings… (⌘,), then Connections, then +.
  • +
  • Name is what its tab says — “AB Booth”, “NA Ingest”.
  • +
  • Address is the one you open ProdDash at in a browser, such as 10.3.11.152:24500. Leave the port off and ProdDash’s own, 24500, is used. The sheet tells you as you type whether ProdDash is answering there.
  • +
  • Double-click a connection to change it; drag connections to change the order of the tabs.
  • +
+ +

Switch between installations

+

With two or more connections, a slim tab bar of their names sits under the title bar. It tucks itself away so the dashboard has the whole window: point at the title bar and it slides back out.

+
    +
  • In full screen, move the pointer to the top of the screen — the tab bar comes down with the menu bar. It also shows for a moment after each switch, so you can see where you are.
  • +
  • ⌃Tab and ⌃⇧Tab go to the next and previous installation; ⌘1–⌘8 go straight to one, ⌘9 to the last.
  • +
  • Every dashboard stays live in the background, so switching is instant.
  • +
  • A tab with ⚠︎ is an installation that isn’t answering.
  • +
+ +

Full screen

+

Click the green button, or choose View → Enter Full Screen (fn F or ⌃⌘F). If the Viewer was in full screen when it quit, it opens in full screen again — so a booth Mac that restarts comes back as it was left.

+

The dashboard’s own full-screen button works too: it fills the screen with the dashboard alone, tab bar and all put away. Press Esc to leave it.

+ +

Moving around

+
    +
  • View → Back (⌘[), Forward (⌘]) — after opening the admin page from the dashboard, for example. Swiping with two fingers works as in Safari.
  • +
  • View → Reload Page (⌘R).
  • +
  • Links to anywhere outside the installation open in your web browser.
  • +
+ +

When an installation can’t be reached

+

The Viewer says why — nothing answering, the address can’t be found, no network — and keeps trying by itself, so a dashboard comes back on its own after ProdDash restarts or its Mac reboots. Try Again asks right away.

+

If macOS asked whether ProdDash Viewer may find devices on your local network and the answer was no, it can’t reach anything: allow it in System Settings → Privacy & Security → Local Network.

+ +

General settings

+
    +
  • Open at login — start the Viewer when this Mac’s user logs in. macOS may ask you to approve it in System Settings first.
  • +
  • Keep the display awake — while a dashboard is on screen, the display doesn’t dim or sleep. On unless you turn it off.
  • +
+ +

Keyboard shortcuts

+ + + + + + + + +
⌘,Settings
⌘[   ⌘]Back, Forward
⌘RReload Page
fn F   ⌃⌘FEnter or leave full screen
⌃Tab   ⌃⇧TabNext, previous installation
⌘1 … ⌘9An installation by its place in the tab bar
⌘WClose the window, which quits the Viewer
+ + + diff --git a/viewer/macos/Resources/Info.plist b/viewer/macos/Resources/Info.plist new file mode 100644 index 0000000..eefb3c6 --- /dev/null +++ b/viewer/macos/Resources/Info.plist @@ -0,0 +1,44 @@ + + + + + CFBundleName + ProdDash Viewer + CFBundleDisplayName + ProdDash Viewer + CFBundleExecutable + ProdDashViewer + CFBundleIdentifier + org.waterschurch.proddash.viewer + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1.0.0 + CFBundleIconFile + AppIcon + NSHumanReadableCopyright + © 2026 Waters Church + LSApplicationCategoryType + public.app-category.utilities + LSMinimumSystemVersion + 13.0 + LSArchitecturePriority + + arm64 + + NSHighResolutionCapable + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + NSLocalNetworkUsageDescription + ProdDash Viewer shows the dashboards of the ProdDash installations you add in Settings, which run on this network. + + diff --git a/viewer/macos/Resources/make-icon.swift b/viewer/macos/Resources/make-icon.swift new file mode 100644 index 0000000..2d49fd7 --- /dev/null +++ b/viewer/macos/Resources/make-icon.swift @@ -0,0 +1,78 @@ +// Draws AppIcon.png, the Viewer's icon: ProdDash's 2×2 tile mark on a +// display, so the Viewer and ProdDash itself tell apart in Finder and the +// Dock. Run it again only to change the icon; build.sh uses the PNG. +// +// swift Resources/make-icon.swift Resources/AppIcon.png +import AppKit + +let side = 1024 +let output = CommandLine.arguments.dropFirst().first ?? "AppIcon.png" + +guard let bitmap = NSBitmapImageRep( + bitmapDataPlanes: nil, pixelsWide: side, pixelsHigh: side, bitsPerSample: 8, + samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, + bytesPerRow: 0, bitsPerPixel: 0), + let context = NSGraphicsContext(bitmapImageRep: bitmap) else { fatalError("no bitmap") } +NSGraphicsContext.current = context +let g = context.cgContext +// Top-left origin, as the measurements below are written. +g.translateBy(x: 0, y: CGFloat(side)) +g.scaleBy(x: 1, y: -1) + +func color(_ hex: UInt32, _ alpha: CGFloat = 1) -> CGColor { + CGColor(srgbRed: CGFloat((hex >> 16) & 0xff) / 255, green: CGFloat((hex >> 8) & 0xff) / 255, + blue: CGFloat(hex & 0xff) / 255, alpha: alpha) +} +func rounded(_ rect: CGRect, _ radius: CGFloat) -> CGPath { + CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil) +} +func fill(_ path: CGPath, _ fillColor: CGColor) { + g.addPath(path); g.setFillColor(fillColor); g.fillPath() +} + +// The body: Apple's icon grid — an 824-point rounded square, centred, on the +// soft shadow macOS icons sit on. ProdDash's own near-black. +let body = CGRect(x: 100, y: 100, width: 824, height: 824) +let bodyPath = rounded(body, 185) +g.saveGState() +g.setShadow(offset: CGSize(width: 0, height: 12), blur: 28, color: color(0x000000, 0.35)) +fill(bodyPath, color(0x0b0e12)) +g.restoreGState() +g.saveGState() +g.addPath(bodyPath); g.clip() +let shade = CGGradient(colorsSpace: CGColorSpace(name: CGColorSpace.sRGB), + colors: [color(0x1b222b), color(0x0a0d11)] as CFArray, locations: [0, 1])! +g.drawLinearGradient(shade, start: CGPoint(x: 0, y: body.minY), end: CGPoint(x: 0, y: body.maxY), options: []) +g.restoreGState() +g.addPath(rounded(body.insetBy(dx: 1.5, dy: 1.5), 183.5)) +g.setStrokeColor(color(0xffffff, 0.08)); g.setLineWidth(3); g.strokePath() + +// The display: bezel, screen, neck and foot. +let bezel = CGRect(x: 212, y: 250, width: 600, height: 410) +let metal = color(0x3a434f) +fill(rounded(CGRect(x: 474, y: 650, width: 76, height: 84), 8), metal) +fill(rounded(CGRect(x: 372, y: 724, width: 280, height: 36), 18), color(0x4a5462)) +fill(rounded(bezel, 44), metal) +let screen = bezel.insetBy(dx: 22, dy: 22) +fill(rounded(screen, 26), color(0x0c1015)) + +// ProdDash's mark on the screen: three outlined tiles and the lit one. +let padding: CGFloat = 46, gap: CGFloat = 30 +let tileW = (screen.width - padding * 2 - gap) / 2 +let tileH = (screen.height - padding * 2 - gap) / 2 +for (column, row) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + let tile = CGRect(x: screen.minX + padding + CGFloat(column) * (tileW + gap), + y: screen.minY + padding + CGFloat(row) * (tileH + gap), + width: tileW, height: tileH) + if column == 1 && row == 1 { + fill(rounded(tile, 22), color(0x2fe39a)) + } else { + let inset = tile.insetBy(dx: 5, dy: 5) + fill(rounded(inset, 18), color(0x121820)) + g.addPath(rounded(inset, 18)); g.setStrokeColor(color(0x3d4652)); g.setLineWidth(10); g.strokePath() + } +} + +guard let png = bitmap.representation(using: .png, properties: [:]) else { fatalError("no png") } +try png.write(to: URL(fileURLWithPath: output)) +print("wrote \(output)") diff --git a/viewer/macos/Sources/AppDelegate.swift b/viewer/macos/Sources/AppDelegate.swift new file mode 100644 index 0000000..a659e51 --- /dev/null +++ b/viewer/macos/Sources/AppDelegate.swift @@ -0,0 +1,110 @@ +import AppKit + +/** + ProdDash Viewer: one window showing a ProdDash dashboard, a tab per + installation when there's more than one, and Settings to say which. + + The menus are deliberately short — Back, Forward, Reload Page and Settings, + plus what every Mac app has: the app menu, Edit (without which Copy and + Paste do nothing in the dashboard's text fields), Window and Help. + */ +final class AppDelegate: NSObject, NSApplicationDelegate { + + static var version: String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev" + } + + private var viewer: ViewerWindowController! + private lazy var settingsWindow = SettingsWindowController() + private lazy var helpWindow = HelpWindowController() + + func applicationWillFinishLaunching(_ notification: Notification) { + NSWindow.allowsAutomaticWindowTabbing = false + NSApp.mainMenu = makeMainMenu() + } + + func applicationDidFinishLaunching(_ notification: Notification) { + viewer = ViewerWindowController() + viewer.onAddConnection = { [weak self] in self?.settingsWindow.show(addingConnection: true) } + viewer.showWindow(nil) + if ViewerSettings.shared.reopenInFullScreen { + DispatchQueue.main.async { self.viewer.window?.toggleFullScreen(nil) } + } + } + + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if !flag { viewer.showWindow(nil) } + return true + } + + func applicationWillTerminate(_ notification: Notification) { + viewer?.rememberFullScreen() + } + + func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { true } + + @objc func showSettings(_ sender: Any?) { settingsWindow.show() } + @objc func showViewerHelp(_ sender: Any?) { helpWindow.show() } + + // MARK: - Menus + + private func makeMainMenu() -> NSMenu { + let name = "ProdDash Viewer" + let main = NSMenu() + + let app = submenu(of: main, title: name) + app.addItem(withTitle: "About \(name)", + action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: "") + app.addItem(.separator()) + app.addItem(withTitle: "Settings…", action: #selector(showSettings(_:)), keyEquivalent: ",") + app.addItem(.separator()) + let services = submenu(of: app, title: "Services") + NSApp.servicesMenu = services + app.addItem(.separator()) + app.addItem(withTitle: "Hide \(name)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h") + app.addItem(withTitle: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), + keyEquivalent: "h").keyEquivalentModifierMask = [.command, .option] + app.addItem(withTitle: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "") + app.addItem(.separator()) + app.addItem(withTitle: "Quit \(name)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") + + let edit = submenu(of: main, title: "Edit") + edit.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z") + edit.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "Z") + edit.addItem(.separator()) + edit.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x") + edit.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c") + edit.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") + edit.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") + + let view = submenu(of: main, title: "View") + view.addItem(withTitle: "Back", action: #selector(ViewerWindowController.goBackInPage(_:)), keyEquivalent: "[") + view.addItem(withTitle: "Forward", action: #selector(ViewerWindowController.goForwardInPage(_:)), keyEquivalent: "]") + view.addItem(withTitle: "Reload Page", action: #selector(ViewerWindowController.reloadPage(_:)), keyEquivalent: "r") + view.addItem(.separator()) + // AppKit retitles this "Exit Full Screen" while the window is in full screen. + view.addItem(withTitle: "Enter Full Screen", action: #selector(NSWindow.toggleFullScreen(_:)), + keyEquivalent: "f").keyEquivalentModifierMask = [.command, .control] + + let window = submenu(of: main, title: "Window") + window.addItem(withTitle: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w") + window.addItem(withTitle: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m") + window.addItem(withTitle: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: "") + window.addItem(.separator()) + window.addItem(withTitle: "Bring All to Front", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "") + NSApp.windowsMenu = window + + let help = submenu(of: main, title: "Help") + help.addItem(withTitle: "\(name) Help", action: #selector(showViewerHelp(_:)), keyEquivalent: "?") + NSApp.helpMenu = help + + return main + } + + @discardableResult + private func submenu(of parent: NSMenu, title: String) -> NSMenu { + let menu = NSMenu(title: title) + parent.addItem(withTitle: title, action: nil, keyEquivalent: "").submenu = menu + return menu + } +} diff --git a/viewer/macos/Sources/Connections.swift b/viewer/macos/Sources/Connections.swift new file mode 100644 index 0000000..6153350 --- /dev/null +++ b/viewer/macos/Sources/Connections.swift @@ -0,0 +1,169 @@ +import Foundation +import SwiftUI + +/// One ProdDash installation the viewer shows: the name its tab carries and +/// the address it answers at. +struct Connection: Codable, Identifiable, Equatable { + var id = UUID() + var name: String + /// Always a full http(s) URL — see `Address.normalize`. + var address: String + + var url: URL? { URL(string: address) } + + /// The name, or the host when nobody gave it one. + var displayName: String { + let trimmed = name.trimmingCharacters(in: .whitespaces) + return trimmed.isEmpty ? (url?.host ?? address) : trimmed + } + + var displayAddress: String { url.map(Address.display) ?? address } +} + +enum Address { + /// ProdDash's port unless someone changed it (config/proddash.json). + static let defaultPort = 24500 + + /// What someone typed, as the URL ProdDash answers at: `10.3.11.152` and + /// `10.3.11.152:24500` both become `http://10.3.11.152:24500/`. A typed + /// scheme is taken at its word — `http://host` means port 80. A path is + /// kept, so an address can open a particular page. + static func normalize(_ text: String) -> URL? { + var typed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !typed.isEmpty else { return nil } + let hasScheme = typed.contains("://") + if !hasScheme { typed = "http://" + typed } + guard var parts = URLComponents(string: typed), + let scheme = parts.scheme?.lowercased(), scheme == "http" || scheme == "https", + let host = parts.host, !host.isEmpty else { return nil } + parts.scheme = scheme + if !hasScheme && parts.port == nil { parts.port = defaultPort } + if parts.path.isEmpty { parts.path = "/" } + return parts.url + } + + /// Scheme, host and port — what decides whether a link stays in the viewer. + static func origin(_ url: URL) -> String? { + guard let scheme = url.scheme?.lowercased(), let host = url.host?.lowercased() else { return nil } + let port = url.port ?? (scheme == "https" ? 443 : 80) + return "\(scheme)://\(host):\(port)" + } + + /// For people to read: no trailing slash on a bare address. + static func display(_ url: URL) -> String { + var text = url.absoluteString + if url.path == "/" && url.query == nil && url.fragment == nil && text.hasSuffix("/") { text.removeLast() } + return text + } + + /// Just the scheme, host and port, for people: `https://host`. + static func originDisplay(_ url: URL) -> String { + var parts = URLComponents() + parts.scheme = url.scheme + parts.host = url.host + parts.port = url.port + return parts.string ?? url.absoluteString + } + + /// `host:port`, the way the error messages name a place. + static func place(_ url: URL?) -> String { + guard let url, let host = url.host else { return "that address" } + return url.port.map { "\(host):\($0)" } ?? host + } +} + +/// The connections, in tab order, kept in the viewer's preferences. +final class ConnectionStore: ObservableObject { + + static let shared = ConnectionStore() + private static let key = "connections" + + @Published private(set) var connections: [Connection] { + didSet { save() } + } + + private init() { + let saved = UserDefaults.standard.data(forKey: Self.key) + connections = saved.flatMap { try? JSONDecoder().decode([Connection].self, from: $0) } ?? [] + } + + func add(_ connection: Connection) { connections.append(connection) } + + func update(_ connection: Connection) { + guard let index = connections.firstIndex(where: { $0.id == connection.id }) else { return } + connections[index] = connection + } + + func remove(_ id: UUID) { connections.removeAll { $0.id == id } } + + func move(from source: IndexSet, to destination: Int) { + connections.move(fromOffsets: source, toOffset: destination) + } + + private func save() { + guard let data = try? JSONEncoder().encode(connections) else { return } + UserDefaults.standard.set(data, forKey: Self.key) + } +} + +/// Network failures in words an operator can act on. +enum NetworkProblem { + static func describe(_ error: Error, url: URL?) -> String { + let ns = error as NSError + let place = Address.place(url) + guard ns.domain == NSURLErrorDomain else { return ns.localizedDescription } + switch ns.code { + case NSURLErrorCannotConnectToHost: + return "Nothing is answering at \(place). ProdDash may not be running there, or it may use another port." + case NSURLErrorTimedOut: + return "\(place) didn't answer in time." + case NSURLErrorCannotFindHost, NSURLErrorDNSLookupFailed: + return "\(url?.host ?? place) can't be found on the network." + case NSURLErrorNotConnectedToInternet: + return "This Mac isn't on a network — or macOS is keeping ProdDash Viewer off it. Check System Settings → Privacy & Security → Local Network." + case NSURLErrorNetworkConnectionLost: + return "The connection to \(place) dropped." + case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateUntrusted, + NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorServerCertificateHasBadDate, + NSURLErrorServerCertificateNotYetValid: + return "\(place) offered a secure connection this Mac doesn't trust." + default: + return ns.localizedDescription + } + } +} + +/// Asks an address whether ProdDash is there, for the connection sheet. +enum Probe { + + enum Answer: Equatable { + case prodDash + case somethingElse + case noAnswer(String) + } + + private static let session: URLSession = { + let config = URLSessionConfiguration.ephemeral + config.timeoutIntervalForRequest = 4 + config.timeoutIntervalForResource = 6 + return URLSession(configuration: config) + }() + + /// `/api/modules` is the first thing every dashboard asks for, and it + /// answers without a PIN — so `{"modules": …}` there means ProdDash. + static func ask(_ url: URL) async -> Answer { + guard var parts = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return .somethingElse } + parts.path = "/api/modules" + parts.query = nil + parts.fragment = nil + guard let target = parts.url else { return .somethingElse } + do { + let (data, response) = try await session.data(from: target) + let ok = (response as? HTTPURLResponse)?.statusCode == 200 + let body = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + return ok && body?["modules"] != nil ? .prodDash : .somethingElse + } catch { + return .noAnswer(NetworkProblem.describe(error, url: url)) + } + } +} diff --git a/viewer/macos/Sources/DashboardPage.swift b/viewer/macos/Sources/DashboardPage.swift new file mode 100644 index 0000000..9719995 --- /dev/null +++ b/viewer/macos/Sources/DashboardPage.swift @@ -0,0 +1,425 @@ +import AppKit +import SwiftUI +import WebKit + +/** + One installation's dashboard: its web view, and what shows instead while the + installation can't be reached. Every page stays alive while the viewer runs, + so switching tabs is instant and the dashboards behind the one on screen stay + live. + */ +final class DashboardPage: NSObject, WKNavigationDelegate, WKUIDelegate { + + final class Status: ObservableObject { + enum Phase: Equatable { + case connecting + case showing + case unreachable(String) + } + @Published var phase: Phase = .connecting + @Published var name = "" + @Published var address = "" + } + + private(set) var connection: Connection + /// What the window shows: the web view, with the status view over it. + let view = NSView() + private(set) var webView: WKWebView + let status = Status() + /// Something the tab bar or the menus show has changed. + var onChange: () -> Void = {} + + private let window: () -> NSWindow? + private var statusView: NSView! + private var retryTimer: Timer? + private var retryDelay: TimeInterval = 2 + private var revealTimer: Timer? + private var lastAttempt: URL? + + var isUnreachable: Bool { + if case .unreachable = status.phase { return true } + return false + } + + init(connection: Connection, window: @escaping () -> NSWindow?) { + self.connection = connection + self.window = window + self.webView = Self.makeWebView() + super.init() + status.name = connection.displayName + status.address = connection.displayAddress + install(webView) + let overlay = NSHostingView(rootView: PageStatusView(status: status) { [weak self] in self?.retry() }) + overlay.sizingOptions = [] // sized by the window, never the other way round + overlay.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(overlay) + pin(overlay) + statusView = overlay + } + + private static func makeWebView() -> WKWebView { + let config = WKWebViewConfiguration() + config.websiteDataStore = .default() + // The dashboard's own full-screen button uses the Fullscreen API. + config.preferences.isElementFullscreenEnabled = true + // An alert may need to sound on a dashboard nobody has touched. + config.mediaTypesRequiringUserActionForPlayback = [] + config.applicationNameForUserAgent = "ProdDashViewer/\(AppDelegate.version)" + let web = WKWebView(frame: .zero, configuration: config) + web.allowsBackForwardNavigationGestures = true + // Safari's Develop menu can inspect a dashboard running in here. + if #available(macOS 13.3, *) { web.isInspectable = true } + return web + } + + private func install(_ web: WKWebView) { + web.navigationDelegate = self + web.uiDelegate = self + // Hidden until it has drawn something: no white flash on a dark booth + // display while the first load is in flight. + web.isHidden = true + web.translatesAutoresizingMaskIntoConstraints = false + if let statusView { + view.addSubview(web, positioned: .below, relativeTo: statusView) + } else { + view.addSubview(web) + } + pin(web) + } + + private func pin(_ subview: NSView) { + NSLayoutConstraint.activate([ + subview.leadingAnchor.constraint(equalTo: view.leadingAnchor), + subview.trailingAnchor.constraint(equalTo: view.trailingAnchor), + subview.topAnchor.constraint(equalTo: view.topAnchor), + subview.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + // MARK: - Loading + + func load() { load(connection.url) } + + private func load(_ url: URL?) { + guard let url else { + return show(.unreachable("“\(connection.address)” isn't an address ProdDash Viewer can open. Change it in Settings.")) + } + lastAttempt = url + webView.load(URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 15)) + } + + /// Reload, or — while the installation can't be reached — try again now. + func reload() { + if isUnreachable || webView.url == nil { return retry() } + webView.reload() + } + + func goBack() { webView.goBack() } + func goForward() { webView.goForward() } + + func retry() { + retryTimer?.invalidate() + load(lastAttempt ?? connection.url) + } + + /// An installation that isn't answering is usually one restarting — an + /// update, a reboot — so keep asking, less often the longer it takes. + private func scheduleRetry() { + retryTimer?.invalidate() + retryTimer = Timer.scheduledTimer(withTimeInterval: retryDelay, repeats: false) { [weak self] _ in + self?.retry() + } + retryDelay = min(retryDelay * 2, 15) + } + + /// Settings changed this connection. A new address is a different + /// installation, so it gets a fresh web view — Back can't wander into the + /// old one. + func update(_ new: Connection) { + let moved = new.address != connection.address + connection = new + status.name = new.displayName + status.address = new.displayAddress + guard moved else { return onChange() } + retire(webView) + webView = Self.makeWebView() + install(webView) + retryTimer?.invalidate() + revealTimer?.invalidate() + retryDelay = 2 + show(.connecting) + load() + } + + /// The connection was removed. + func close() { + retryTimer?.invalidate() + revealTimer?.invalidate() + retire(webView) + view.removeFromSuperview() + } + + private func retire(_ web: WKWebView) { + web.stopLoading() + web.navigationDelegate = nil + web.uiDelegate = nil + web.removeFromSuperview() + } + + private func show(_ phase: Status.Phase) { + status.phase = phase + statusView.isHidden = phase == .showing + onChange() + } + + private func isHome(_ url: URL) -> Bool { + guard let home = connection.url.flatMap(Address.origin) else { return false } + return Address.origin(url) == home + } + + // MARK: - Navigation + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + guard webView === self.webView else { return } + reveal() + } + + /// Normally the load finishes within moments of this; but a tile's frame + /// that never answers holds the load event back, and the dashboard + /// shouldn't sit behind "Connecting…" waiting for it. + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + guard webView === self.webView, status.phase != .showing else { return } + revealTimer?.invalidate() + revealTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { [weak self, weak webView] _ in + guard let self, let webView, webView === self.webView else { return } + self.reveal() + } + } + + private func reveal() { + revealTimer?.invalidate() + retryTimer?.invalidate() + retryDelay = 2 + webView.isHidden = false + if status.phase != .showing { show(.showing) } + } + + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + failed(webView, error) + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + failed(webView, error) + } + + private func failed(_ web: WKWebView, _ error: Error) { + guard web === webView else { return } + let ns = error as NSError + // Not failures: a load replaced by another, or one handed to the browser. + if ns.domain == NSURLErrorDomain && ns.code == NSURLErrorCancelled { return } + if ns.domain == "WebKitErrorDomain" && (ns.code == 102 || ns.code == 204) { return } + if let failing = ns.userInfo[NSURLErrorFailingURLErrorKey] as? URL, isHome(failing) { + lastAttempt = failing + } + show(.unreachable(NetworkProblem.describe(error, url: lastAttempt))) + scheduleRetry() + } + + /// The page's process died (memory pressure, a WebKit crash). A booth + /// display is never left blank: bring it back — after the usual retry + /// delay, so a page that takes its process down as it loads can't spin. + func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { + guard webView === self.webView else { return } + if let url = webView.url, isHome(url) { lastAttempt = url } + scheduleRetry() + } + + /// The viewer shows ProdDash and nothing else: the installation's own + /// pages open here, any other site opens in the browser. + func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + guard let url = action.request.url else { return decisionHandler(.allow) } + // A tile may embed another site in a frame; only the page itself is policed. + guard action.targetFrame?.isMainFrame ?? true else { return decisionHandler(.allow) } + switch url.scheme?.lowercased() { + case "http", "https": + if !isHome(url) { + let followed = action.targetFrame == nil || status.phase == .showing + || action.navigationType == .linkActivated || action.navigationType == .formSubmitted + if followed { + NSWorkspace.shared.open(url) // someone followed a link off ProdDash + } else { + redirected(to: url) + } + return decisionHandler(.cancel) + } + if action.targetFrame == nil { + // target="_blank" or window.open on ProdDash's own page: one + // window, so it opens here. + let request = action.request + DispatchQueue.main.async { webView.load(request) } + return decisionHandler(.cancel) + } + decisionHandler(.allow) + case "about", "blob", "data": + decisionHandler(.allow) + case "file": + // A file dropped somewhere the page didn't take it. + decisionHandler(.cancel) + default: + NSWorkspace.shared.open(url) // mailto: and the like + decisionHandler(.cancel) + } + } + + /// Something the page can't show is a download: the browser does those. + func webView(_ webView: WKWebView, decidePolicyFor response: WKNavigationResponse, + decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + if response.isForMainFrame, !response.canShowMIMEType, let url = response.response.url { + NSWorkspace.shared.open(url) + return decisionHandler(.cancel) + } + decisionHandler(.allow) + } + + /// The address itself sends the viewer somewhere else — a proxy, http + /// moving to https. Handing that to the browser would leave the page + /// stuck connecting, and do it again on every retry. + private func redirected(to url: URL) { + show(.unreachable("\(Address.place(connection.url)) sends ProdDash Viewer on to \(Address.originDisplay(url)). If that's where ProdDash answers, change the address in Settings to it.")) + scheduleRetry() + } + + /// window.open(), which doesn't pass through decidePolicyFor: ProdDash's + /// own pages open here, other sites in the browser. The viewer never + /// opens a second window. + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, + for action: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + guard let url = action.request.url else { return nil } + if isHome(url) { + webView.load(action.request) + } else if url.scheme == "http" || url.scheme == "https" { + NSWorkspace.shared.open(url) + } + return nil + } + + // MARK: - JavaScript dialogs and file pickers + + // A WKWebView answers alert, confirm and prompt with "no" unless the app + // shows them — and the admin page confirms before deleting a layout, Band + // Lineup prompts for names. + + func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, + initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + let alert = dialog(message) + alert.addButton(withTitle: "OK") + present(alert) { _ in completionHandler() } + } + + func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, + initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { + let alert = dialog(message) + alert.addButton(withTitle: "OK") + alert.addButton(withTitle: "Cancel") + present(alert) { completionHandler($0 == .alertFirstButtonReturn) } + } + + func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, + defaultText: String?, initiatedByFrame frame: WKFrameInfo, + completionHandler: @escaping (String?) -> Void) { + let alert = dialog(prompt) + let field = NSTextField(string: defaultText ?? "") + field.frame = NSRect(x: 0, y: 0, width: 280, height: 22) + alert.accessoryView = field + alert.addButton(withTitle: "OK") + alert.addButton(withTitle: "Cancel") + alert.window.initialFirstResponder = field + present(alert) { completionHandler($0 == .alertFirstButtonReturn ? field.stringValue : nil) } + } + + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, + initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + let panel = NSOpenPanel() + panel.canChooseFiles = true + panel.canChooseDirectories = parameters.allowsDirectories + panel.allowsMultipleSelection = parameters.allowsMultipleSelection + if let window = window(), window.attachedSheet == nil { + panel.beginSheetModal(for: window) { completionHandler($0 == .OK ? panel.urls : nil) } + } else { + completionHandler(panel.runModal() == .OK ? panel.urls : nil) + } + } + + /// Titled with the installation's name, so it's clear which one is asking. + private func dialog(_ message: String) -> NSAlert { + let alert = NSAlert() + alert.messageText = connection.displayName + alert.informativeText = message + return alert + } + + private func present(_ alert: NSAlert, then done: @escaping (NSApplication.ModalResponse) -> Void) { + if let window = window(), window.attachedSheet == nil { + alert.beginSheetModal(for: window, completionHandler: done) + } else { + done(alert.runModal()) + } + } +} + +/// Over the web view while there is nothing to show yet, or while the +/// installation can't be reached. +struct PageStatusView: View { + + @ObservedObject var status: DashboardPage.Status + let retry: () -> Void + /// A fast load shouldn't flash a spinner. + @State private var patient = false + + var body: some View { + ZStack { + Color(nsColor: .windowBackgroundColor) + switch status.phase { + case .showing: + EmptyView() + case .connecting: + if patient { + VStack(spacing: 12) { + ProgressView() + Text("Connecting to \(status.name)…").foregroundStyle(.secondary) + } + } + case .unreachable(let why): + VStack(spacing: 10) { + Image(systemName: "network.slash") + .font(.system(size: 44, weight: .light)) + .foregroundStyle(.secondary) + .padding(.bottom, 4) + Text("Can’t Reach \(status.name)") + .font(.title2.weight(.semibold)) + Text(why) + .multilineTextAlignment(.center) + .foregroundStyle(.secondary) + .frame(maxWidth: 420) + .fixedSize(horizontal: false, vertical: true) + Text(status.address) + .font(.callout.monospaced()) + .foregroundStyle(.tertiary) + .textSelection(.enabled) + Button("Try Again", action: retry) + .controlSize(.large) + .padding(.top, 8) + Text("ProdDash Viewer keeps trying by itself.") + .font(.caption) + .foregroundStyle(.tertiary) + } + .padding(40) + } + } + .task { + try? await Task.sleep(for: .milliseconds(800)) + patient = true + } + } +} diff --git a/viewer/macos/Sources/HelpWindowController.swift b/viewer/macos/Sources/HelpWindowController.swift new file mode 100644 index 0000000..f20ca8f --- /dev/null +++ b/viewer/macos/Sources/HelpWindowController.swift @@ -0,0 +1,42 @@ +import AppKit +import WebKit + +/// Help → ProdDash Viewer Help: the page that ships inside the app, so it +/// opens with no network and always matches this version. +final class HelpWindowController: NSWindowController, WKNavigationDelegate { + + convenience init() { + let web = WKWebView() + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 640, height: 720), + styleMask: [.titled, .closable, .miniaturizable, .resizable], + backing: .buffered, defer: false) + window.title = "ProdDash Viewer Help" + window.minSize = NSSize(width: 420, height: 360) + window.contentView = web + window.tabbingMode = .disallowed + window.isRestorable = false + window.center() + window.setFrameAutosaveName("ProdDashViewerHelp") + self.init(window: window) + web.navigationDelegate = self + if let page = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "Help") { + web.loadFileURL(page, allowingReadAccessTo: page.deletingLastPathComponent()) + } + } + + func show() { + showWindow(nil) + window?.makeKeyAndOrderFront(nil) + } + + /// The help page itself stays here; its links to the web open in the browser. + func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + guard let url = action.request.url, !url.isFileURL, url.scheme != "about" else { + return decisionHandler(.allow) + } + NSWorkspace.shared.open(url) + decisionHandler(.cancel) + } +} diff --git a/viewer/macos/Sources/SettingsWindowController.swift b/viewer/macos/Sources/SettingsWindowController.swift new file mode 100644 index 0000000..7af69a9 --- /dev/null +++ b/viewer/macos/Sources/SettingsWindowController.swift @@ -0,0 +1,299 @@ +import AppKit +import SwiftUI + +/// Settings (⌘,): a standard toolbar-tabbed settings window with two panes, +/// Connections and General. Every change applies as it's made. +final class SettingsWindowController: NSWindowController { + + private var model: SettingsModel! + private var tabs: NSTabViewController! + + convenience init() { + let model = SettingsModel() + let tabs = NSTabViewController() + tabs.tabStyle = .toolbar + tabs.addTabViewItem(Self.pane("Connections", symbol: "network", + ConnectionsPane(store: .shared, model: model))) + tabs.addTabViewItem(Self.pane("General", symbol: "gearshape", + GeneralPane(settings: .shared))) + let window = NSWindow(contentViewController: tabs) + window.styleMask = [.titled, .closable] + window.toolbarStyle = .preference + window.isRestorable = false + self.init(window: window) + self.model = model + self.tabs = tabs + window.center() + } + + private static func pane(_ label: String, symbol: String, _ content: Content) -> NSTabViewItem { + let controller = NSHostingController(rootView: content) + controller.sizingOptions = .preferredContentSize + controller.title = label // the window takes its title from the pane + let item = NSTabViewItem(viewController: controller) + item.label = label + item.image = NSImage(systemSymbolName: symbol, accessibilityDescription: label) + return item + } + + /// Show Settings; `addingConnection` opens it on the add-a-connection sheet. + func show(addingConnection: Bool = false) { + if addingConnection { + tabs.selectedTabViewItemIndex = 0 + model.pendingAdd = true + } + showWindow(nil) + window?.makeKeyAndOrderFront(nil) + } +} + +final class SettingsModel: ObservableObject { + /// Set to open the add sheet as soon as the Connections pane can. + @Published var pendingAdd = false +} + +// MARK: - Connections + +private struct ConnectionDraft: Identifiable { + var id: UUID + var name: String + var address: String + var isNew: Bool +} + +private struct ConnectionsPane: View { + + @ObservedObject var store: ConnectionStore + @ObservedObject var model: SettingsModel + @State private var selection: UUID? + @State private var editing: ConnectionDraft? + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + Text("Each connection is a ProdDash installation on your network. With more than one, a tab bar at the top of the window switches between them — drag them into the order you want.") + .font(.callout) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + + List(selection: $selection) { + ForEach(store.connections) { connection in + VStack(alignment: .leading, spacing: 2) { + Text(connection.displayName) + Text(connection.displayAddress) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, 3) + .tag(connection.id) + } + .onMove { store.move(from: $0, to: $1) } + } + .listStyle(.bordered(alternatesRowBackgrounds: true)) + .contextMenu(forSelectionType: UUID.self) { ids in + Button("Edit…") { edit(ids.first) } + .disabled(ids.count != 1) + Button("Remove") { + ids.forEach(store.remove) + if let selected = selection, ids.contains(selected) { selection = nil } + } + } primaryAction: { ids in + edit(ids.first) + } + .overlay { + if store.connections.isEmpty { + Text("No Connections").foregroundStyle(.tertiary) + } + } + .frame(height: 210) + + HStack(spacing: 6) { + Button(action: add) { + Image(systemName: "plus").frame(width: 14) + } + .help("Add a connection") + .accessibilityLabel("Add") + Button(action: removeSelected) { + Image(systemName: "minus").frame(width: 14) + } + .help("Remove the selected connection") + .accessibilityLabel("Remove") + .disabled(selection == nil) + Spacer() + Button("Edit…") { edit(selection) } + .disabled(selection == nil) + } + } + .padding(20) + .frame(width: 520) + .sheet(item: $editing) { draft in + ConnectionEditor(draft: draft, onSave: save) + } + .onReceive(model.$pendingAdd) { pending in + guard pending else { return } + DispatchQueue.main.async { + model.pendingAdd = false + add() + } + } + } + + private func add() { + editing = ConnectionDraft(id: UUID(), name: "", address: "", isNew: true) + } + + private func edit(_ id: UUID?) { + guard let id, let connection = store.connections.first(where: { $0.id == id }) else { return } + editing = ConnectionDraft(id: id, name: connection.name, address: connection.displayAddress, isNew: false) + } + + private func removeSelected() { + guard let id = selection else { return } + store.remove(id) + selection = nil + } + + private func save(_ draft: ConnectionDraft) { + guard let url = Address.normalize(draft.address) else { return } + let connection = Connection(id: draft.id, + name: draft.name.trimmingCharacters(in: .whitespaces), + address: url.absoluteString) + if draft.isNew { store.add(connection) } else { store.update(connection) } + selection = connection.id + } +} + +/// The add / edit sheet. It asks the address whether ProdDash is there as +/// you type, so a mistyped address shows up here rather than on the booth +/// display — but saving never depends on it: the installation may simply be +/// switched off right now. +private struct ConnectionEditor: View { + + @State var draft: ConnectionDraft + let onSave: (ConnectionDraft) -> Void + @Environment(\.dismiss) private var dismiss + + private enum Check: Equatable { case idle, checking, answer(Probe.Answer) } + @State private var check: Check = .idle + + private var url: URL? { Address.normalize(draft.address) } + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + Text(draft.isNew ? "Add Connection" : "Edit Connection") + .font(.headline) + + Form { + TextField("Name:", text: $draft.name, prompt: Text(url?.host ?? "AB Booth")) + TextField("Address:", text: $draft.address, prompt: Text("10.3.11.152:24500")) + LabeledContent { + status + .font(.caption) + .frame(maxWidth: .infinity, minHeight: 30, alignment: .topLeading) + } label: { + EmptyView() + } + } + + HStack { + Spacer() + Button("Cancel", role: .cancel) { dismiss() } + .keyboardShortcut(.cancelAction) + Button(draft.isNew ? "Add" : "Save") { + onSave(draft) + dismiss() + } + .keyboardShortcut(.defaultAction) + .disabled(url == nil) + } + } + .padding(20) + .frame(width: 460) + .task(id: draft.address) { + guard let url else { return check = .idle } + check = .checking + try? await Task.sleep(for: .milliseconds(500)) // let the typing finish + guard !Task.isCancelled else { return } + let answer = await Probe.ask(url) + guard !Task.isCancelled else { return } + check = .answer(answer) + } + } + + @ViewBuilder private var status: some View { + switch check { + case .idle where url == nil: + Text(draft.address.isEmpty + ? "The address you open ProdDash at in a browser. Without a port, ProdDash’s \(String(Address.defaultPort)) is used." + : "That isn’t an address.") + .foregroundStyle(.secondary) + case .idle, .checking: // idle with an address: the check is about to start + HStack(spacing: 6) { + ProgressView().controlSize(.mini) + Text("Checking \(url.map(Address.display) ?? "")…").foregroundStyle(.secondary) + } + case .answer(.prodDash): + Label("ProdDash is answering at \(url.map(Address.display) ?? "").", systemImage: "checkmark.circle.fill") + .foregroundStyle(.green) + case .answer(.somethingElse): + Label("Something answers at \(url.map(Address.display) ?? "") — but it isn’t ProdDash.", systemImage: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + case .answer(.noAnswer(let why)): + Label(why, systemImage: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + } + } +} + +// MARK: - General + +private struct GeneralPane: View { + + @ObservedObject var settings: ViewerSettings + @State private var openAtLogin = LoginItem.isEnabled || LoginItem.needsApproval + @State private var needsApproval = LoginItem.needsApproval + @State private var loginProblem: String? + + var body: some View { + Form { + Section { + Toggle("Open at login", isOn: Binding( + get: { openAtLogin }, + set: { wanted in + loginProblem = LoginItem.set(wanted) + needsApproval = LoginItem.needsApproval + openAtLogin = LoginItem.isEnabled || needsApproval + })) + if needsApproval { + HStack { + Text("Waiting for your approval in System Settings.") + .foregroundStyle(.secondary) + Spacer() + Button("Open Login Items…") { LoginItem.openSystemSettings() } + } + } + if let loginProblem { + Text(loginProblem).foregroundStyle(.red) + } + } + Section { + Toggle(isOn: $settings.keepDisplayAwake) { + Text("Keep the display awake") + Text("While a dashboard is on screen, the display doesn’t dim or sleep.") + } + } + } + .formStyle(.grouped) + .scrollDisabled(true) + .frame(width: 520, height: needsApproval || loginProblem != nil ? 200 : 152) + .onAppear(perform: refreshLoginItem) + // Approving it in System Settings happens elsewhere; catch up on return. + .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { _ in + refreshLoginItem() + } + } + + private func refreshLoginItem() { + needsApproval = LoginItem.needsApproval + openAtLogin = LoginItem.isEnabled || needsApproval + } +} diff --git a/viewer/macos/Sources/TabBar.swift b/viewer/macos/Sources/TabBar.swift new file mode 100644 index 0000000..5114c4e --- /dev/null +++ b/viewer/macos/Sources/TabBar.swift @@ -0,0 +1,77 @@ +import AppKit +import SwiftUI + +/// What the tab bar shows: one tab per connection, in Settings' order. +final class TabBarModel: ObservableObject { + + struct Tab: Identifiable, Equatable { + let id: UUID + var title: String + var address: String + var unreachable: Bool + } + + @Published var tabs: [Tab] = [] + @Published var selection: UUID? + var onSelect: (UUID) -> Void = { _ in } +} + +/// Hosts the tabs. A click on a tab works even while another app is in +/// front, rather than only bringing the viewer forward. +final class TabBarHostingView: NSHostingView { + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } +} + +/// The slim bar of installation names. It sits in the window's title bar +/// area, so it takes the title bar's material in light and dark alike. +struct TabBar: View { + + @ObservedObject var model: TabBarModel + + var body: some View { + HStack(spacing: 4) { + ForEach(model.tabs) { tab in + TabButton(tab: tab, selected: tab.id == model.selection) { model.onSelect(tab.id) } + } + } + .padding(.horizontal, 8) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +private struct TabButton: View { + + let tab: TabBarModel.Tab + let selected: Bool + let action: () -> Void + @State private var hovering = false + + var body: some View { + Button(action: action) { + HStack(spacing: 5) { + if tab.unreachable { + Image(systemName: "exclamationmark.triangle.fill") + .imageScale(.small) + .foregroundStyle(.orange) + } + Text(tab.title) + .lineLimit(1) + .truncationMode(.tail) + } + .font(.system(size: 12, weight: selected ? .semibold : .regular)) + .foregroundStyle(selected ? .primary : .secondary) + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, minHeight: 24) + .background( + RoundedRectangle(cornerRadius: 6, style: .continuous) + .fill(Color.primary.opacity(selected ? 0.12 : hovering ? 0.06 : 0)) + ) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) // the keyboard belongs to the dashboard; ⌃Tab switches + .onHover { hovering = $0 } + .help(tab.unreachable ? "\(tab.address) — not answering" : tab.address) + .accessibilityAddTraits(selected ? .isSelected : []) + } +} diff --git a/viewer/macos/Sources/ViewerSettings.swift b/viewer/macos/Sources/ViewerSettings.swift new file mode 100644 index 0000000..1cdf8c9 --- /dev/null +++ b/viewer/macos/Sources/ViewerSettings.swift @@ -0,0 +1,61 @@ +import Foundation +import ServiceManagement + +/// The viewer's own preferences. The connections themselves are in +/// ConnectionStore. +final class ViewerSettings: ObservableObject { + + static let shared = ViewerSettings() + private let defaults = UserDefaults.standard + + /// Keep the display on while a dashboard is on screen — a booth display + /// that dims halfway through a service is the failure this is here for. + @Published var keepDisplayAwake: Bool { + didSet { defaults.set(keepDisplayAwake, forKey: "keepDisplayAwake") } + } + + private init() { + defaults.register(defaults: ["keepDisplayAwake": true]) + keepDisplayAwake = defaults.bool(forKey: "keepDisplayAwake") + } + + /// The window was in full screen when the viewer last quit, so it opens in + /// full screen again — a booth Mac that restarts comes back as it was left. + var reopenInFullScreen: Bool { + get { defaults.bool(forKey: "reopenInFullScreen") } + set { defaults.set(newValue, forKey: "reopenInFullScreen") } + } + + /// The tab that was showing, so the viewer reopens on it. + var selectedConnection: UUID? { + get { defaults.string(forKey: "selectedConnection").flatMap(UUID.init(uuidString:)) } + set { defaults.set(newValue?.uuidString, forKey: "selectedConnection") } + } +} + +/// "Open at login", through the macOS 13 login-item API. The operator may +/// still have to approve it in System Settings — hence `needsApproval`, which +/// Settings shows rather than failing silently. +enum LoginItem { + + static var isEnabled: Bool { SMAppService.mainApp.status == .enabled } + static var needsApproval: Bool { SMAppService.mainApp.status == .requiresApproval } + + /// Nil when it worked; otherwise what went wrong, for Settings to show. + static func set(_ enabled: Bool) -> String? { + do { + if enabled { + if SMAppService.mainApp.status != .enabled { try SMAppService.mainApp.register() } + } else { + if SMAppService.mainApp.status != .notRegistered { try SMAppService.mainApp.unregister() } + } + return nil + } catch { + return error.localizedDescription + } + } + + static func openSystemSettings() { + SMAppService.openSystemSettingsLoginItems() + } +} diff --git a/viewer/macos/Sources/ViewerWindowController.swift b/viewer/macos/Sources/ViewerWindowController.swift new file mode 100644 index 0000000..403b96f --- /dev/null +++ b/viewer/macos/Sources/ViewerWindowController.swift @@ -0,0 +1,544 @@ +import AppKit +import Combine +import SwiftUI + +/** + The viewer's one window: the selected installation's dashboard and — when + there is more than one — a slim tab bar of their names. + + In a window the bar floats over the top of the dashboard, just under the + title bar: it comes out while the pointer is on the title bar and tucks away + after. It is deliberately not part of the title bar — anything there makes + WebKit push the whole page down to clear it, and a dashboard jumping every + time the bar came and went would be worse than no bar at all. + + In full screen the bar *is* part of the title bar (a titlebar accessory), so + it comes down with the menu bar like any Mac app's toolbar; there the title + bar lives in a window of its own and the page stays put. + */ +final class ViewerWindowController: NSWindowController, NSWindowDelegate, NSMenuItemValidation { + + static let tabBarHeight: CGFloat = 32 + + /// The standard title bar's height: where the dashboard starts. + private static let titlebarHeight: CGFloat = { + let probe = NSRect(x: 0, y: 0, width: 100, height: 100) + return NSWindow.frameRect(forContentRect: probe, styleMask: [.titled]).height - probe.height + }() + + /// Empty state's "Add Connection…". + var onAddConnection: () -> Void = {} + + private let store = ConnectionStore.shared + private let settings = ViewerSettings.shared + private var pages: [UUID: DashboardPage] = [:] + private var order: [UUID] = [] + private var selection: UUID? + + private let content = ViewerContentView() + private let pageArea = NSView() + private var pageAreaTop: NSLayoutConstraint! + private var emptyState: NSView! + + private let tabModel = TabBarModel() + private var tabOverlay: TabBarOverlay! + private var tabOverlayTop: NSLayoutConstraint! + private let tabAccessory = NSTitlebarAccessoryViewController() + private var accessoryAttached = false + private var tabBarShown = false + private var hideTimer: Timer? + private var inFullScreen = false + private var leaving = false + + private var awakeActivity: NSObjectProtocol? + private var keyMonitor: Any? + private var cancellables = Set() + + private var currentPage: DashboardPage? { selection.flatMap { pages[$0] } } + private var hasTabs: Bool { order.count > 1 } + + convenience init() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 1280, height: 800), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + window.title = "ProdDash Viewer" + window.minSize = NSSize(width: 640, height: 420) + window.tabbingMode = .disallowed // the tab bar here is ProdDash's, not AppKit's + window.collectionBehavior.insert(.fullScreenPrimary) + window.isRestorable = false // frame and full screen are kept below instead + window.center() + window.setFrameAutosaveName("ProdDashViewerWindow") + self.init(window: window) + window.delegate = self + build(in: window) + } + + private func build(in window: NSWindow) { + // The content runs under the title bar so the pointer can be watched + // there; the dashboard itself starts below it. + window.contentView = content + + pageArea.translatesAutoresizingMaskIntoConstraints = false + content.addSubview(pageArea) + pageAreaTop = pageArea.topAnchor.constraint(equalTo: content.topAnchor, constant: Self.titlebarHeight) + + let empty = NSHostingView(rootView: EmptyStateView { [weak self] in self?.onAddConnection() }) + empty.sizingOptions = [] // sized by the window, never the other way round + empty.translatesAutoresizingMaskIntoConstraints = false + content.addSubview(empty) + emptyState = empty + + tabOverlay = TabBarOverlay(model: tabModel) + tabOverlay.translatesAutoresizingMaskIntoConstraints = false + tabOverlay.isHidden = true + tabOverlay.alphaValue = 0 + content.addSubview(tabOverlay) + tabOverlayTop = tabOverlay.topAnchor.constraint(equalTo: content.topAnchor, constant: hiddenOverlayTop) + + NSLayoutConstraint.activate([ + pageAreaTop, + pageArea.leadingAnchor.constraint(equalTo: content.leadingAnchor), + pageArea.trailingAnchor.constraint(equalTo: content.trailingAnchor), + pageArea.bottomAnchor.constraint(equalTo: content.bottomAnchor), + empty.topAnchor.constraint(equalTo: pageArea.topAnchor), + empty.leadingAnchor.constraint(equalTo: pageArea.leadingAnchor), + empty.trailingAnchor.constraint(equalTo: pageArea.trailingAnchor), + empty.bottomAnchor.constraint(equalTo: pageArea.bottomAnchor), + tabOverlayTop, + tabOverlay.leadingAnchor.constraint(equalTo: content.leadingAnchor), + tabOverlay.trailingAnchor.constraint(equalTo: content.trailingAnchor), + tabOverlay.heightAnchor.constraint(equalToConstant: Self.tabBarHeight), + ]) + + let bar = TabBarHostingView(rootView: TabBar(model: tabModel)) + bar.sizingOptions = [] + bar.frame = NSRect(x: 0, y: 0, width: 800, height: Self.tabBarHeight) + tabAccessory.view = bar + tabAccessory.layoutAttribute = .bottom + tabAccessory.fullScreenMinHeight = 0 // out of sight until the menu bar comes down + tabModel.onSelect = { [weak self] id in self?.select(id) } + + content.topZoneHeight = { [weak self] in self?.revealZoneHeight ?? 0 } + content.onPointerInTopZone = { [weak self] inside in self?.pointerInTopZone(inside) } + + selection = settings.selectedConnection + // @Published sends before it stores, so use the value it sends. + store.$connections + .sink { [weak self] connections in self?.sync(connections) } + .store(in: &cancellables) + settings.$keepDisplayAwake + .sink { [weak self] keep in self?.updateDisplayAwake(keep) } + .store(in: &cancellables) + + installKeyboardSwitching() + } + + // MARK: - Connections and tabs + + private func sync(_ connections: [Connection]) { + let ids = Set(connections.map(\.id)) + let hadTabs = hasTabs + for (id, page) in pages where !ids.contains(id) { + page.close() + pages[id] = nil + } + for connection in connections { + if let page = pages[connection.id] { + page.update(connection) + } else { + let page = DashboardPage(connection: connection) { [weak self] in self?.window } + page.onChange = { [weak self, weak page] in + if let page { self?.pageChanged(page) } + } + pages[connection.id] = page + // A dashboard behind the one on screen still lays itself out + // at the window's size, not at nothing. + page.view.frame = NSRect(x: 0, y: 0, width: content.bounds.width, + height: max(content.bounds.height - pageAreaTop.constant, 0)) + page.view.layoutSubtreeIfNeeded() + page.load() + } + } + order = connections.map(\.id) + if selection.map(ids.contains) != true { selection = order.first } + showSelection() + refreshTabs() + placeTabBar() + if hasTabs && !hadTabs { flashTabBar(for: 3) } // out long enough to be noticed + updateDisplayAwake(settings.keepDisplayAwake) + } + + private func pageChanged(_ page: DashboardPage) { + refreshTabs() + if page === currentPage { + updateTitle() + // A new address means a new web view: put it on screen. + if page.view.superview !== pageArea { showSelection() } + // The web view can't take the keyboard until it's shown something. + if page.status.phase == .showing, (window?.firstResponder as? NSView)?.isDescendant(of: page.webView) != true { + window?.makeFirstResponder(page.webView) + } + } + } + + private func showSelection() { + let page = currentPage + for view in pageArea.subviews where view !== page?.view { view.removeFromSuperview() } + if let page, page.view.superview !== pageArea { + page.view.translatesAutoresizingMaskIntoConstraints = false + pageArea.addSubview(page.view) + NSLayoutConstraint.activate([ + page.view.leadingAnchor.constraint(equalTo: pageArea.leadingAnchor), + page.view.trailingAnchor.constraint(equalTo: pageArea.trailingAnchor), + page.view.topAnchor.constraint(equalTo: pageArea.topAnchor), + page.view.bottomAnchor.constraint(equalTo: pageArea.bottomAnchor), + ]) + } + emptyState.isHidden = page != nil + if let page { window?.makeFirstResponder(page.webView) } + settings.selectedConnection = selection + updateTitle() + } + + private func updateTitle() { + window?.title = currentPage?.connection.displayName ?? "ProdDash Viewer" + } + + private func refreshTabs() { + tabModel.tabs = order.compactMap { id in + pages[id].map { + TabBarModel.Tab(id: id, title: $0.connection.displayName, + address: $0.connection.displayAddress, unreachable: $0.isUnreachable) + } + } + tabModel.selection = selection + } + + func select(_ id: UUID) { + guard pages[id] != nil else { return } + if id != selection { + selection = id + showSelection() + refreshTabs() + } + flashTabBar(for: 2) + } + + private func select(index: Int) { + guard order.indices.contains(index) else { return } + select(order[index]) + } + + private func step(_ delta: Int) { + guard !order.isEmpty else { return } + let index = selection.flatMap { order.firstIndex(of: $0) } ?? 0 + select(index: (index + delta + order.count) % order.count) + } + + /// ⌃Tab / ⌃⇧Tab and ⌘1…⌘9 (⌘9 the last), as in Safari. Not menu items: + /// the menus stay as short as the viewer is. + private func installKeyboardSwitching() { + keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in + guard let self, event.window === self.window, self.hasTabs else { return event } + // Only the keys that are pressed together — Caps Lock doesn't count. + let mods = event.modifierFlags.intersection([.command, .control, .option, .shift]) + if event.keyCode == 48, mods.subtracting(.shift) == .control { // Tab + self.step(mods.contains(.shift) ? -1 : 1) + return nil + } + if mods == .command, let digit = event.charactersIgnoringModifiers.flatMap(Int.init), (1...9).contains(digit) { + self.select(index: digit == 9 ? self.order.count - 1 : digit - 1) + return nil + } + return event + } + } + + // MARK: - The tab bar coming and going + + /// Overlay in a window, titlebar accessory in full screen — and neither + /// with only one connection. The accessory stays attached whenever there + /// are tabs, so it is already in place when AppKit builds the full-screen + /// title bar; in a window it is hidden, and hidden it takes no room, so + /// the page stays put. + private func placeTabBar() { + tabAccessory.isHidden = !inFullScreen + if hasTabs != accessoryAttached { + accessoryAttached = hasTabs + if hasTabs { + window?.addTitlebarAccessoryViewController(tabAccessory) + } else { + tabAccessory.removeFromParent() + } + } + if !hasTabs { + hideTimer?.invalidate() + setTabBar(shown: false, animated: false) + } + content.updateTrackingAreas() + } + + /// Under the title bar in a window; the top of the screen in full screen, + /// where it only flashes up after a switch. + private var shownOverlayTop: CGFloat { inFullScreen ? 0 : Self.titlebarHeight } + /// Slid up by a few points while hidden, so it slides down as it fades in. + private var hiddenOverlayTop: CGFloat { shownOverlayTop - 8 } + + /// The strip along the top that brings the bar out: the title bar, a + /// sliver of the dashboard below it, and the bar itself while it's out. + private var revealZoneHeight: CGFloat { + guard hasTabs, !inFullScreen else { return 0 } + return Self.titlebarHeight + (tabBarShown ? Self.tabBarHeight : 4) + } + + private func pointerInTopZone(_ inside: Bool) { + if inside { + hideTimer?.invalidate() + setTabBar(shown: true) + } else { + scheduleHide(after: 0.8) + } + } + + /// Bring the bar out for a moment — at launch, and after switching, so + /// it's clear what changed. In full screen too, where nothing else says + /// which installation is on screen. + private func flashTabBar(for seconds: TimeInterval) { + guard hasTabs else { return } + setTabBar(shown: true) + if !content.pointerInTopZone { scheduleHide(after: seconds) } + } + + private func scheduleHide(after delay: TimeInterval) { + hideTimer?.invalidate() + hideTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in + guard let self, !self.content.pointerInTopZone else { return } + self.setTabBar(shown: false) + } + } + + private func setTabBar(shown: Bool, animated: Bool = true) { + let shown = shown && hasTabs + guard shown != tabBarShown else { return } + tabBarShown = shown + if shown { tabOverlay.isHidden = false } + content.layoutSubtreeIfNeeded() + NSAnimationContext.runAnimationGroup({ context in + context.duration = animated ? 0.18 : 0 + context.allowsImplicitAnimation = true + tabOverlay.animator().alphaValue = shown ? 1 : 0 + tabOverlayTop.animator().constant = shown ? shownOverlayTop : hiddenOverlayTop + content.layoutSubtreeIfNeeded() + }, completionHandler: { [weak self] in + guard let self, !self.tabBarShown else { return } + self.tabOverlay.isHidden = true + }) + content.updateTrackingAreas() + } + + // MARK: - Full screen + + func windowWillEnterFullScreen(_ notification: Notification) { + settleOverlay() + inFullScreen = true + pageAreaTop.constant = 0 + placeTabBar() + tabOverlayTop.constant = hiddenOverlayTop + } + + func windowDidEnterFullScreen(_ notification: Notification) { + if !leaving { settings.reopenInFullScreen = true } + } + + /// The accessory hides before the window is a window again — showing in + /// a window's title bar, it would push the dashboard down. + func windowWillExitFullScreen(_ notification: Notification) { + settleOverlay() + inFullScreen = false + pageAreaTop.constant = Self.titlebarHeight + placeTabBar() + tabOverlayTop.constant = hiddenOverlayTop + } + + /// No overlay riding through the full-screen animation. + private func settleOverlay() { + hideTimer?.invalidate() + setTabBar(shown: false, animated: false) + } + + func windowDidExitFullScreen(_ notification: Notification) { + if !leaving { settings.reopenInFullScreen = false } + content.updateTrackingAreas() + } + + /// Called on the way out, before AppKit takes the window out of full + /// screen on its own account. + func rememberFullScreen() { + guard !leaving, let window else { return } + leaving = true + settings.reopenInFullScreen = window.styleMask.contains(.fullScreen) + } + + // MARK: - Window + + /// Before AppKit takes a full-screen window out of full screen to close it. + func windowShouldClose(_ sender: NSWindow) -> Bool { + rememberFullScreen() + return true + } + + /// One window is the whole app: closing it quits, as in System Settings. + func windowWillClose(_ notification: Notification) { + rememberFullScreen() + DispatchQueue.main.async { NSApp.terminate(nil) } + } + + func windowDidChangeOcclusionState(_ notification: Notification) { + updateDisplayAwake(settings.keepDisplayAwake) + } + + /// Hold the display awake while a dashboard is actually on screen — not + /// while the window is minimised, hidden or covered. + private func updateDisplayAwake(_ keep: Bool) { + let visible = window?.occlusionState.contains(.visible) == true + let wanted = keep && visible && !order.isEmpty + if wanted, awakeActivity == nil { + awakeActivity = ProcessInfo.processInfo.beginActivity( + options: [.idleDisplaySleepDisabled], reason: "Showing a ProdDash dashboard") + } else if !wanted, let activity = awakeActivity { + ProcessInfo.processInfo.endActivity(activity) + awakeActivity = nil + } + } + + // MARK: - View menu + + @objc func goBackInPage(_ sender: Any?) { currentPage?.goBack() } + @objc func goForwardInPage(_ sender: Any?) { currentPage?.goForward() } + @objc func reloadPage(_ sender: Any?) { currentPage?.reload() } + + func validateMenuItem(_ item: NSMenuItem) -> Bool { + switch item.action { + case #selector(goBackInPage(_:)): return currentPage?.webView.canGoBack == true + case #selector(goForwardInPage(_:)): return currentPage?.webView.canGoForward == true + case #selector(reloadPage(_:)): return currentPage != nil + default: return true + } + } +} + +/// The tab bar as it shows in a window: the title bar's material, the tabs, +/// and a hairline under them, floating over the top of the dashboard. +final class TabBarOverlay: NSView { + + init(model: TabBarModel) { + super.init(frame: .zero) + let material = NSVisualEffectView() + material.material = .titlebar + material.blendingMode = .withinWindow + material.state = .followsWindowActiveState + let tabs = TabBarHostingView(rootView: TabBar(model: model)) + tabs.sizingOptions = [] + let rule = Hairline() + for view in [material, tabs, rule] { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + } + NSLayoutConstraint.activate([ + material.leadingAnchor.constraint(equalTo: leadingAnchor), + material.trailingAnchor.constraint(equalTo: trailingAnchor), + material.topAnchor.constraint(equalTo: topAnchor), + material.bottomAnchor.constraint(equalTo: bottomAnchor), + tabs.leadingAnchor.constraint(equalTo: leadingAnchor), + tabs.trailingAnchor.constraint(equalTo: trailingAnchor), + tabs.topAnchor.constraint(equalTo: topAnchor), + tabs.bottomAnchor.constraint(equalTo: rule.topAnchor), + rule.leadingAnchor.constraint(equalTo: leadingAnchor), + rule.trailingAnchor.constraint(equalTo: trailingAnchor), + rule.bottomAnchor.constraint(equalTo: bottomAnchor), + rule.heightAnchor.constraint(equalToConstant: 1), + ]) + } + + required init?(coder: NSCoder) { fatalError("not used") } + + /// The title bar's separator line. (An NSBox separator would do, but one + /// laid out from a zero frame decides it's vertical and pins the window + /// to a point wide.) + private final class Hairline: NSView { + override func draw(_ dirtyRect: NSRect) { + NSColor.separatorColor.setFill() + bounds.fill() + } + } +} + +/// The window's content view. It watches the strip along the top of the +/// window — which, with the content drawn under the title bar, includes the +/// title bar — and says when the pointer comes and goes. Tracking stays on +/// while another app is in front, so the bar answers the pointer even then. +final class ViewerContentView: NSView { + + var topZoneHeight: () -> CGFloat = { 0 } + var onPointerInTopZone: (Bool) -> Void = { _ in } + private var zone: NSTrackingArea? + + private var zoneRect: NSRect { + let height = min(topZoneHeight(), bounds.height) + return NSRect(x: 0, y: bounds.maxY - height, width: bounds.width, height: height) + } + + var pointerInTopZone: Bool { + guard let window else { return false } + let rect = zoneRect + return rect.height > 0 && rect.contains(convert(window.mouseLocationOutsideOfEventStream, from: nil)) + } + + override func updateTrackingAreas() { + super.updateTrackingAreas() + if let zone { removeTrackingArea(zone) } + zone = nil + let rect = zoneRect + guard rect.height > 0 else { return } + let area = NSTrackingArea(rect: rect, options: [.mouseEnteredAndExited, .activeAlways], owner: self) + addTrackingArea(area) + zone = area + } + + override func mouseEntered(with event: NSEvent) { + if event.trackingArea === zone { onPointerInTopZone(true) } + } + + override func mouseExited(with event: NSEvent) { + if event.trackingArea === zone { onPointerInTopZone(false) } + } +} + +/// The window before any connection exists. +struct EmptyStateView: View { + + let add: () -> Void + + var body: some View { + VStack(spacing: 14) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .frame(width: 96, height: 96) + Text("No ProdDash Connections") + .font(.title2.weight(.semibold)) + Text("Add the address of a ProdDash installation and its dashboard opens here. Add more than one to switch between them.") + .multilineTextAlignment(.center) + .foregroundStyle(.secondary) + .frame(maxWidth: 380) + .fixedSize(horizontal: false, vertical: true) + Button("Add Connection…", action: add) + .controlSize(.large) + .keyboardShortcut(.defaultAction) + .padding(.top, 6) + } + .padding(40) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(nsColor: .windowBackgroundColor)) + } +} diff --git a/viewer/macos/Sources/main.swift b/viewer/macos/Sources/main.swift new file mode 100644 index 0000000..5e8f441 --- /dev/null +++ b/viewer/macos/Sources/main.swift @@ -0,0 +1,8 @@ +import AppKit + +// ProdDash Viewer — a window onto ProdDash, and nothing else. +let delegate = AppDelegate() +let application = NSApplication.shared +application.delegate = delegate +application.setActivationPolicy(.regular) +application.run() diff --git a/viewer/macos/build.sh b/viewer/macos/build.sh new file mode 100755 index 0000000..ceb038a --- /dev/null +++ b/viewer/macos/build.sh @@ -0,0 +1,149 @@ +#!/bin/zsh +# Build ProdDash Viewer.app — a window onto ProdDash installations, for Apple +# silicon Macs. Native Swift, no dependencies. +# +# ./build.sh build/ProdDash Viewer.app +# ./build.sh --dmg …and build/ProdDash-Viewer--arm64.dmg +# ./build.sh --install …and copy it to /Applications +# ./build.sh --run …and launch it +# ./build.sh --notarize with --dmg: notarise and staple (needs a +# Developer ID and a notarytool keychain profile) +# +# Signing works as ProdDash's own app does (launcher/macos/README.md): a +# "Developer ID Application" certificate when one is installed (override with +# CODESIGN_IDENTITY), ad-hoc when none is — fine on your own Macs, but another +# Mac asks before it opens an ad-hoc app. +# +# Needs Xcode (xcode-select pointing at it): with the command line tools +# alone, the macOS 27 SDK can't expand SwiftUI's @State macro. +set -e -u +SCRIPT="${0:A}" +cd "${SCRIPT:h}" + +APP_NAME="ProdDash Viewer" +EXECUTABLE="ProdDashViewer" +BUNDLE_ID="org.waterschurch.proddash.viewer" +VERSION="1.0.0" +ARCH="arm64" # Apple silicon +DEPLOYMENT_TARGET="13.0" + +OUT_DIR="build" +APP="$OUT_DIR/$APP_NAME.app" +CONTENTS="$APP/Contents" + +INSTALL=0 +RUN=0 +MAKE_DMG=0 +NOTARIZE=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --install) INSTALL=1 ;; + --run) RUN=1 ;; + --dmg) MAKE_DMG=1 ;; + --notarize) NOTARIZE=1; MAKE_DMG=1 ;; + -h|--help) sed -n '2,18p' "$SCRIPT"; exit 0 ;; + *) echo "build.sh: unknown option $1" >&2; exit 2 ;; + esac + shift +done + +command -v swiftc >/dev/null || { + echo "build.sh: swiftc not found — install Xcode, then: sudo xcode-select -s /Applications/Xcode.app" >&2 + exit 1 +} + +echo "$APP_NAME $VERSION — building for $ARCH" + +rm -rf "$OUT_DIR" +mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources" + +echo " compiling" +swiftc -O -whole-module-optimization \ + -target "$ARCH-apple-macos$DEPLOYMENT_TARGET" \ + -module-name ProdDashViewer \ + -o "$CONTENTS/MacOS/$EXECUTABLE" \ + Sources/*.swift + +cp Resources/Info.plist "$CONTENTS/Info.plist" +/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$CONTENTS/Info.plist" >/dev/null +/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$CONTENTS/Info.plist" >/dev/null +printf 'APPL????' > "$CONTENTS/PkgInfo" + +cp -R Resources/Help "$CONTENTS/Resources/Help" + +# Resources/AppIcon.png is drawn by Resources/make-icon.swift — ProdDash's +# tile mark on a display, so the Viewer and ProdDash itself tell apart. +echo " building the icon" +ICONSET="$OUT_DIR/AppIcon.iconset" +mkdir -p "$ICONSET" +for size in 16 32 128 256 512; do + sips -z $size $size Resources/AppIcon.png --out "$ICONSET/icon_${size}x${size}.png" >/dev/null + double=$((size * 2)) + sips -z $double $double Resources/AppIcon.png --out "$ICONSET/icon_${size}x${size}@2x.png" >/dev/null +done +iconutil -c icns "$ICONSET" -o "$CONTENTS/Resources/AppIcon.icns" +rm -rf "$ICONSET" +find "$APP" -name '.DS_Store' -delete + +# ── signing ────────────────────────────────────────────────────────────── +IDENTITY="${CODESIGN_IDENTITY:-}" +if [[ -z "$IDENTITY" ]]; then + IDENTITY=$(security find-identity -v -p codesigning 2>/dev/null \ + | awk -F'"' '/Developer ID Application/ {print $2; exit}') +fi +if [[ -n "$IDENTITY" ]]; then + echo " signing as $IDENTITY" + SIGN=(--force --sign "$IDENTITY" --options runtime --timestamp) +else + echo " signing ad-hoc (no Developer ID installed — other Macs will ask before opening it)" + IDENTITY="-" + SIGN=(--force --sign -) +fi +codesign "${SIGN[@]}" --identifier "$BUNDLE_ID" "$APP" +codesign --verify --deep --strict "$APP" # stops the build if it doesn't +echo " signature verifies" + +echo "built $APP ($(du -sh "$APP" | awk '{print $1}'))" + +# ── disk image ─────────────────────────────────────────────────────────── +if [[ $MAKE_DMG -eq 1 ]]; then + DMG="$OUT_DIR/ProdDash-Viewer-$VERSION-$ARCH.dmg" + STAGE="$OUT_DIR/dmg" + rm -rf "$STAGE" + mkdir -p "$STAGE" + cp -R "$APP" "$STAGE/" + ln -s /Applications "$STAGE/Applications" # drag-to-install, the usual way + hdiutil create -volname "$APP_NAME $VERSION" -srcfolder "$STAGE" \ + -ov -format UDZO -quiet "$DMG" + rm -rf "$STAGE" + [[ "$IDENTITY" == "-" ]] || codesign --force --sign "$IDENTITY" --timestamp "$DMG" + echo "built $DMG ($(du -sh "$DMG" | awk '{print $1}'))" + + if [[ $NOTARIZE -eq 1 ]]; then + if [[ "$IDENTITY" == "-" ]]; then + echo "build.sh: can't notarise an ad-hoc signed app — a Developer ID is required" >&2 + exit 1 + fi + PROFILE="${NOTARY_PROFILE:-proddash}" + echo " notarising as keychain profile \"$PROFILE\" (a few minutes)" + xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait + xcrun stapler staple "$DMG" + echo " notarised and stapled — this opens anywhere with no warning" + fi +fi + +if [[ $INSTALL -eq 1 ]]; then + DEST="/Applications/$APP_NAME.app" + if pgrep -x "$EXECUTABLE" >/dev/null; then + osascript -e "tell application id \"$BUNDLE_ID\" to quit" >/dev/null 2>&1 || true + sleep 1 + fi + rm -rf "$DEST" + cp -R "$APP" "$DEST" + echo "installed $DEST" + APP="$DEST" +fi + +[[ $RUN -eq 1 ]] && open "$APP" +exit 0