import SwiftUI import WebKit /// View for previewing files with syntax highlighting struct FilePreviewView: View { let path: String @Environment(\.dismiss) var dismiss @State private var preview: FilePreview? @State private var isLoading = true @State private var presentedError: IdentifiableError? @State private var showingDiff = false @State private var gitDiff: FileDiff? var body: some View { NavigationStack { ZStack { Theme.Colors.terminalBackground .ignoresSafeArea() if isLoading { ProgressView("Loading...") .progressViewStyle(CircularProgressViewStyle(tint: Theme.Colors.primaryAccent)) } else if presentedError != nil { ContentUnavailableView { Label("Failed to Load File", systemImage: "exclamationmark.triangle") } description: { Text("The file could not be loaded. Please try again.") } actions: { Button("Retry") { Task { await loadPreview() } } .terminalButton() } } else if let preview { previewContent(for: preview) } } .navigationTitle(URL(fileURLWithPath: path).lastPathComponent) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Close") { dismiss() } .foregroundColor(Theme.Colors.primaryAccent) } if let preview, preview.type == .text { ToolbarItem(placement: .navigationBarTrailing) { Button("Diff") { showingDiff = true } .foregroundColor(Theme.Colors.primaryAccent) } } } } .preferredColorScheme(.dark) .task { await loadPreview() } .sheet(isPresented: $showingDiff) { if let diff = gitDiff { GitDiffView(diff: diff) } else { ProgressView("Loading diff...") .task { await loadDiff() } } } .errorAlert(item: $presentedError) } @ViewBuilder private func previewContent(for preview: FilePreview) -> some View { switch preview.type { case .text: if let content = preview.content { SyntaxHighlightedView( content: content, language: preview.language ?? "text" ) } case .image: if let content = preview.content, let data = Data(base64Encoded: content), let uiImage = UIImage(data: data) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) .padding() } case .binary: VStack(spacing: Theme.Spacing.large) { Image(systemName: "doc.zipper") .font(.system(size: 64)) .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) Text("Binary File") .font(.headline) .foregroundColor(Theme.Colors.terminalForeground) if let size = preview.size { Text(formatFileSize(size)) .font(.caption) .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) } } } } private func loadPreview() async { isLoading = true presentedError = nil do { preview = try await APIClient.shared.previewFile(path: path) isLoading = false } catch { presentedError = IdentifiableError(error: error) isLoading = false } } private func loadDiff() async { do { gitDiff = try await APIClient.shared.getGitDiff(path: path) } catch { // Silently fail - diff might not be available } } private func formatFileSize(_ size: Int64) -> String { let formatter = ByteCountFormatter() formatter.countStyle = .binary return formatter.string(fromByteCount: size) } } /// WebView-based syntax highlighted text view struct SyntaxHighlightedView: UIViewRepresentable { let content: String let language: String func makeUIView(context: Context) -> WKWebView { let configuration = WKWebViewConfiguration() let webView = WKWebView(frame: .zero, configuration: configuration) webView.isOpaque = false webView.backgroundColor = UIColor(Theme.Colors.cardBackground) webView.scrollView.backgroundColor = UIColor(Theme.Colors.cardBackground) loadContent(in: webView) return webView } func updateUIView(_ webView: WKWebView, context: Context) { // Content is static, no updates needed } private func loadContent(in webView: WKWebView) { let escapedContent = content .replacingOccurrences(of: "&", with: "&") .replacingOccurrences(of: "<", with: "<") .replacingOccurrences(of: ">", with: ">") .replacingOccurrences(of: "\"", with: """) .replacingOccurrences(of: "'", with: "'") let html = """
\(escapedContent)
"""
webView.loadHTMLString(html, baseURL: nil)
}
}
/// View for displaying git diffs
struct GitDiffView: View {
let diff: FileDiff
@Environment(\.dismiss)
var dismiss
var body: some View {
NavigationStack {
ZStack {
Theme.Colors.terminalBackground
.ignoresSafeArea()
DiffWebView(content: diff.diff)
}
.navigationTitle("Git Diff")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Close") {
dismiss()
}
.foregroundColor(Theme.Colors.primaryAccent)
}
}
}
.preferredColorScheme(.dark)
}
}
/// WebView for displaying diffs with syntax highlighting
struct DiffWebView: UIViewRepresentable {
let content: String
func makeUIView(context: Context) -> WKWebView {
let configuration = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.isOpaque = false
webView.backgroundColor = UIColor(Theme.Colors.cardBackground)
loadDiff(in: webView)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
// Content is static
}
private func loadDiff(in webView: WKWebView) {
let escapedContent = content
.replacingOccurrences(of: "&", with: "&")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: ">", with: ">")
let html = """
\(escapedContent)
"""
webView.loadHTMLString(html, baseURL: nil)
}
}