mirror of
https://github.com/SagerNet/sing-box-for-apple.git
synced 2026-06-03 11:37:44 +00:00
108 lines
4.3 KiB
Swift
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
|