134 lines
5.2 KiB
Swift
134 lines
5.2 KiB
Swift
import SwiftUI
|
|
import ProxyCore
|
|
import GRDB
|
|
|
|
struct ComposeListView: View {
|
|
@State private var requests: [ComposeRequest] = []
|
|
@State private var showClearConfirmation = false
|
|
@State private var showTemplatePicker = false
|
|
@State private var observation: AnyDatabaseCancellable?
|
|
|
|
private let composeRepo = ComposeRepository()
|
|
|
|
var body: some View {
|
|
Group {
|
|
if requests.isEmpty {
|
|
EmptyStateView(
|
|
icon: "square.and.pencil",
|
|
title: "No Requests",
|
|
subtitle: "Create a new request to get started.",
|
|
actionTitle: "New Request"
|
|
) {
|
|
createEmptyRequest()
|
|
}
|
|
} else {
|
|
List {
|
|
ForEach(requests) { request in
|
|
NavigationLink(value: request.id) {
|
|
HStack {
|
|
MethodBadge(method: request.method)
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(request.name)
|
|
.font(.subheadline.weight(.medium))
|
|
if let url = request.url, !url.isEmpty {
|
|
Text(url)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDelete { indexSet in
|
|
for index in indexSet {
|
|
if let id = requests[index].id {
|
|
try? composeRepo.delete(id: id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationDestination(for: Int64?.self) { id in
|
|
if let id {
|
|
ComposeEditorView(requestId: id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Compose")
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button {
|
|
showClearConfirmation = true
|
|
} label: {
|
|
Image(systemName: "trash")
|
|
}
|
|
.disabled(requests.isEmpty)
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
Button("Empty Request") { createEmptyRequest() }
|
|
Button("GET with Query") { createTemplate(method: "GET", name: "GET with Query") }
|
|
Button("POST with JSON") { createTemplate(method: "POST", name: "POST with JSON", contentType: "application/json") }
|
|
Button("POST with Form") { createTemplate(method: "POST", name: "POST with Form", contentType: "application/x-www-form-urlencoded") }
|
|
Divider()
|
|
Button("Import from cURL") { showTemplatePicker = true }
|
|
} label: {
|
|
Image(systemName: "plus")
|
|
}
|
|
}
|
|
}
|
|
.confirmationDialog("Clear History", isPresented: $showClearConfirmation) {
|
|
Button("Clear All", role: .destructive) {
|
|
try? composeRepo.deleteAll()
|
|
}
|
|
} message: {
|
|
Text("This will permanently delete all compose requests.")
|
|
}
|
|
.sheet(isPresented: $showTemplatePicker) {
|
|
CURLImportView { parsed in
|
|
var request = ComposeRequest(
|
|
name: "Imported Request",
|
|
method: parsed.method,
|
|
url: parsed.url,
|
|
headers: encodeHeaders(parsed.headers),
|
|
body: parsed.body
|
|
)
|
|
try? composeRepo.insert(&request)
|
|
showTemplatePicker = false
|
|
}
|
|
}
|
|
.task {
|
|
observation = composeRepo.observeRequests()
|
|
.start(in: DatabaseManager.shared.dbPool) { error in
|
|
print("Compose observation error: \(error)")
|
|
} onChange: { newRequests in
|
|
withAnimation {
|
|
requests = newRequests
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func createEmptyRequest() {
|
|
var request = ComposeRequest()
|
|
try? composeRepo.insert(&request)
|
|
}
|
|
|
|
private func createTemplate(method: String, name: String, contentType: String? = nil) {
|
|
var headers: String?
|
|
if let contentType {
|
|
headers = encodeHeaders([(key: "Content-Type", value: contentType)])
|
|
}
|
|
var request = ComposeRequest(name: name, method: method, headers: headers)
|
|
try? composeRepo.insert(&request)
|
|
}
|
|
|
|
private func encodeHeaders(_ headers: [(key: String, value: String)]) -> String? {
|
|
var dict: [String: String] = [:]
|
|
for h in headers { dict[h.key] = h.value }
|
|
guard let data = try? JSONEncoder().encode(dict) else { return nil }
|
|
return String(data: data, encoding: .utf8)
|
|
}
|
|
}
|