Files
2026-05-26 16:38:50 +08:00

108 lines
4.3 KiB
Swift

#if canImport(GhosttyTerminal)
import GhosttyTerminal
import Library
import SwiftUI
@MainActor
struct TerminalSessionContentView: View {
@ObservedObject var viewModel: TerminalWrapperViewModel
let presentedSession: TailscaleSSHPresentedSession
var isActive: Bool = true
var onCloseSession: (() -> Void)?
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
#if os(iOS)
Color(uiColor: .systemBackground)
.ignoresSafeArea()
#elseif os(macOS)
Color(nsColor: .windowBackgroundColor)
.ignoresSafeArea()
#endif
TailsshTerminalSurfaceView(
state: viewModel.terminalState,
extras: viewModel.extras,
isActive: isActive
)
if case .connecting = viewModel.phase {
VStack(spacing: 16) {
ProgressView()
.controlSize(.large)
if let banner = viewModel.authBanner, !banner.isEmpty {
Text(Self.bannerAttributedString(banner))
.font(.callout)
.multilineTextAlignment(.leading)
.foregroundColor(.primary)
.padding()
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 10))
.padding(.horizontal)
.frame(maxWidth: 480)
.textSelection(.enabled)
}
}
} else if case let .finished(reason) = viewModel.phase {
VStack {
Spacer()
HStack(spacing: 12) {
Text(reason.displayText)
.font(.callout)
.multilineTextAlignment(.leading)
.textSelection(.enabled)
Spacer(minLength: 8)
Button("Close") {
if let onCloseSession {
onCloseSession()
} else {
dismiss()
}
}
.buttonStyle(.borderedProminent)
}
.padding()
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12))
.padding()
}
}
}
}
var displayedTitle: String {
Self.displayTitle(
phase: viewModel.phase,
extrasTitle: viewModel.extras.title,
peerHostName: presentedSession.peerHostName
)
}
static func displayTitle(
phase: TerminalWrapperViewModel.Phase,
extrasTitle: String,
peerHostName: String
) -> String {
if case .connecting = phase {
return peerHostName
}
let remote = extrasTitle.trimmingCharacters(in: .whitespaces)
return remote.isEmpty ? peerHostName : remote
}
static func bannerAttributedString(_ text: String) -> AttributedString {
var attributed = AttributedString(text)
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return attributed
}
let nsText = text as NSString
let matches = detector.matches(in: text, range: NSRange(location: 0, length: nsText.length))
for match in matches {
guard let url = match.url,
let range = Range(match.range, in: attributed) else { continue }
attributed[range].link = url
attributed[range].foregroundColor = .accentColor
attributed[range].underlineStyle = .single
}
return attributed
}
}
#endif