import AppKit import SwiftUI import ShotdeckCore struct MenuBarView: View { @Environment(AppModel.self) private var model var body: some View { VStack(alignment: .leading, spacing: 8) { statusRow SessionStrip() Divider() actionsList if !model.allReturns.isEmpty { Divider() returnsBlock } } .padding(10) .frame(width: 320, alignment: .leading) .controlSize(.small) } @ViewBuilder private var statusRow: some View { if !model.screenRecordingGranted { HStack(alignment: .top, spacing: 6) { Image(systemName: "exclamationmark.triangle") .foregroundStyle(.yellow) VStack(alignment: .leading, spacing: 4) { Text( ShotdeckError.screenRecordingNotGranted.errorDescription ?? "Screen Recording is turned off." ) .font(.caption) .fixedSize(horizontal: false, vertical: true) Button("Open Screen Recording settings") { model.openScreenRecordingSettings() } } } } else { Text(model.statusLine ?? defaultStatusText) .font(.caption) .foregroundStyle(.primary) .fixedSize(horizontal: false, vertical: true) } } private var defaultStatusText: String { guard let region = model.region else { return "No region yet — press \(model.hotkeyDisplayString) to pick one." } let w = Int(region.rect.width) let h = Int(region.rect.height) let display = displayName(for: region) if model.session.isEmpty { return "Region \(w) × \(h) on \(display) · Nothing captured yet." } let count = model.session.captures.count return "\(count) captures · region \(w) × \(h) on \(display)" } private func displayName(for region: CaptureRegion) -> String { for (index, screen) in NSScreen.screens.enumerated() { let id = (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)? .uint32Value if id == region.displayID { return "Display \(index + 1)" } } return "Display 1" } private var actionsList: some View { VStack(alignment: .leading, spacing: 2) { Button { let anchor = NSApp.keyWindow?.contentView if let sender = model as? SendCapable { Task { await sender.send(anchor: anchor) } } else { model.setStatus("Send is not available in this build.") } } label: { actionLabel("Send…") } .disabled(model.session.isEmpty || model.isSending) Button { Task { await model.captureNow() } } label: { actionLabel("Capture now", trailing: model.hotkeyDisplayString) } .disabled(model.isCapturing) Button { Task { await model.rePickRegion() } } label: { actionLabel("Re-select area") } Button { Task { await model.copyCommentedLinks() } } label: { actionLabel( "Copy commented links", trailing: model.commentedReturns.isEmpty ? nil : "\(model.commentedReturns.count)" ) } .disabled(model.commentedReturns.isEmpty) Button { model.openSpoolFolder() } label: { actionLabel("Open spool folder") } Button { if let presenter = model as? SettingsWindowPresenting { presenter.presentSettingsWindow() } else { model.setStatus("Settings is not available in this build.") } } label: { actionLabel("Settings…") } Button { NSApp.terminate(nil) } label: { actionLabel("Quit Redline") } } .buttonStyle(.plain) } private func actionLabel(_ title: String, trailing: String? = nil) -> some View { HStack(spacing: 8) { Text(title) Spacer(minLength: 8) if let trailing { Text(trailing) .foregroundStyle(.secondary) .monospacedDigit() } } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .padding(.vertical, 3) } @ViewBuilder private var returnsBlock: some View { if let provider = model as? ReturnsSectionProviding { provider.returnsSection() } else { VStack(alignment: .leading, spacing: 4) { Text("Came back from your device") .font(.caption) .foregroundStyle(.secondary) ForEach(newestReturns.prefix(8)) { doc in HStack(spacing: 8) { Text(doc.fileURL.lastPathComponent) .lineLimit(1) Spacer(minLength: 8) Text(doc.isCommented ? "\(doc.annotatedPages.count) page\(doc.annotatedPages.count == 1 ? "" : "s") marked" : "not marked") .font(.caption) .foregroundStyle(doc.isCommented ? .primary : .secondary) } } } } } private var newestReturns: [ReturnedDocument] { model.allReturns.sorted { $0.detectedAt > $1.detectedAt } } }