mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-07 11:35:53 +00:00
165 lines
4.2 KiB
Swift
165 lines
4.2 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Error Alert Modifier
|
|
|
|
/// A view modifier that presents errors using SwiftUI's built-in alert system
|
|
struct ErrorAlertModifier: ViewModifier {
|
|
@Binding var error: Error?
|
|
let title: String
|
|
let onDismiss: (() -> Void)?
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.alert(
|
|
title,
|
|
isPresented: .constant(error != nil),
|
|
presenting: error
|
|
) { _ in
|
|
Button("OK") {
|
|
error = nil
|
|
onDismiss?()
|
|
}
|
|
} message: { error in
|
|
Text(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Presents an error alert when an error is present
|
|
func errorAlert(
|
|
_ title: String = "Error",
|
|
error: Binding<Error?>,
|
|
onDismiss: (() -> Void)? = nil
|
|
)
|
|
-> some View
|
|
{
|
|
modifier(ErrorAlertModifier(error: error, title: title, onDismiss: onDismiss))
|
|
}
|
|
}
|
|
|
|
// MARK: - Task Error Handling
|
|
|
|
extension Task where Failure == Error {
|
|
/// Executes an async operation with error handling on the MainActor
|
|
@MainActor
|
|
@discardableResult
|
|
static func withErrorHandling<T>(
|
|
priority: TaskPriority? = nil,
|
|
errorBinding: Binding<Error?>,
|
|
operation: @escaping () async throws -> T
|
|
)
|
|
-> Task<T, Error>
|
|
{
|
|
Task<T, Error>(priority: priority) {
|
|
do {
|
|
return try await operation()
|
|
} catch {
|
|
errorBinding.wrappedValue = error
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Error Recovery Protocol
|
|
|
|
/// Protocol for errors that can provide recovery actions
|
|
protocol RecoverableError: Error {
|
|
var recoverySuggestion: String? { get }
|
|
var recoveryActions: [ErrorRecoveryAction]? { get }
|
|
}
|
|
|
|
struct ErrorRecoveryAction {
|
|
let title: String
|
|
let action: () async throws -> Void
|
|
}
|
|
|
|
// MARK: - Error Toast View
|
|
|
|
/// A toast-style error notification
|
|
struct ErrorToast: View {
|
|
let error: Error
|
|
let onDismiss: () -> Void
|
|
|
|
@State private var opacity: Double = 0
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundColor(.red)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("Error")
|
|
.font(.headline)
|
|
|
|
Text(error.localizedDescription)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(2)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Button(action: onDismiss) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.buttonStyle(PlainButtonStyle())
|
|
}
|
|
.padding()
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.fill(Color(NSColor.controlBackgroundColor))
|
|
.shadow(radius: 10)
|
|
)
|
|
.padding()
|
|
.opacity(opacity)
|
|
.onAppear {
|
|
withAnimation(.easeIn(duration: 0.3)) {
|
|
opacity = 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Error State Management
|
|
|
|
// AsyncState property wrapper removed as it's not used in the codebase
|
|
|
|
// MARK: - Async Error Boundary
|
|
|
|
/// A view that catches and displays errors from async operations
|
|
struct AsyncErrorBoundary<Content: View>: View {
|
|
@State private var error: Error?
|
|
let content: () -> Content
|
|
|
|
var body: some View {
|
|
content()
|
|
.environment(\.asyncErrorHandler, AsyncErrorHandler { error in
|
|
self.error = error
|
|
})
|
|
.errorAlert(error: $error)
|
|
}
|
|
}
|
|
|
|
// MARK: - Environment Values
|
|
|
|
private struct AsyncErrorHandlerKey: EnvironmentKey {
|
|
nonisolated(unsafe) static let defaultValue = AsyncErrorHandler { _ in }
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
var asyncErrorHandler: AsyncErrorHandler {
|
|
get { self[AsyncErrorHandlerKey.self] }
|
|
set { self[AsyncErrorHandlerKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
struct AsyncErrorHandler {
|
|
let handler: (Error) -> Void
|
|
|
|
func handle(_ error: Error) {
|
|
handler(error)
|
|
}
|
|
}
|