Files
ovpn/Sources/CampusNASMonitor/MeasuredScrollView.swift
romanvandClaude Opus 5 cd53b6a1a1 Add CampusNAS menu bar agent
Native macOS agent that keeps the office VPN and the NAS shares working
without the shell wrapper it replaces.

- VPN: full port of the `ovpn` script — host-route pinning, gateway
  discovery via networksetup, tunnel liveness by address plus pushed
  route, startup grace, restart of a wedged daemon. Root steps are
  batched into one system authorization prompt.
- SMB: mounts via NetFS, discovers shares from the server, detects
  mounts event-driven through NSWorkspace, matches the host by resolved
  alias so externally created mounts are recognised.
- Credentials never pass through the process: the VPN auth file is
  written by `security` straight to disk, and the password is stored via
  SecItemAdd rather than a command line visible in ps.
- UI: status window, settings editor, ⌘+/⌘− zoom that re-lays out rather
  than scaling a bitmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:19:31 +06:00

39 lines
1.3 KiB
Swift

import SwiftUI
/// A ScrollView that is as tall as its content, up to a cap.
///
/// Exists because a plain `ScrollView` inside a `VStack` has a tiny ideal
/// height: given only `.frame(maxHeight:)` it collapses to nearly nothing. That
/// bug shipped twice — once emptying the share list, once emptying the whole
/// settings window — so the fix lives in one place now.
/// File scope, not nested: a generic type cannot hold the static storage a
/// PreferenceKey needs.
private struct HeightKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = max(value, nextValue())
}
}
struct MeasuredScrollView<Content: View>: View {
var minHeight: CGFloat = 0
var maxHeight: CGFloat
@ViewBuilder var content: Content
@State private var measured: CGFloat = 0
var body: some View {
ScrollView {
content
.background(
GeometryReader { geometry in
Color.clear.preference(
key: HeightKey.self, value: geometry.size.height)
}
)
}
.frame(height: min(max(measured, minHeight), maxHeight))
.onPreferenceChange(HeightKey.self) { measured = $0 }
}
}