|
|
|
|
@ -10,32 +10,91 @@ import LeStorage |
|
|
|
|
|
|
|
|
|
struct APICallsListView: View { |
|
|
|
|
|
|
|
|
|
@State var collections: [String] = [] |
|
|
|
|
@State var descriptors: [CollectionDescriptor] = [] |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
|
|
List { |
|
|
|
|
ForEach(self.collections) { name in |
|
|
|
|
NavigationLink(name) { |
|
|
|
|
APICallsView(name: name) |
|
|
|
|
ForEach(self.descriptors) { descriptor in |
|
|
|
|
|
|
|
|
|
NavigationLink { |
|
|
|
|
if let syncedType = descriptor.type as? any SyncedStorable.Type { |
|
|
|
|
APICallsView(name: descriptor.name, type: syncedType) |
|
|
|
|
} |
|
|
|
|
} label: { |
|
|
|
|
LabeledContent(descriptor.name, value: descriptor.count.string) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
}.onAppear { |
|
|
|
|
self.collections = Store.main.collectionNames() |
|
|
|
|
self.load() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func load() { |
|
|
|
|
self.descriptors = Store.main.collectionNames().map { |
|
|
|
|
CollectionDescriptor(name: $0.0, type: $0.1) |
|
|
|
|
}.sorted(by: \.name, order: .ascending) |
|
|
|
|
Task { |
|
|
|
|
|
|
|
|
|
for descriptor in self.descriptors { |
|
|
|
|
if let type = descriptor.type as? any SyncedStorable.Type { |
|
|
|
|
await self.loadCount(type, descriptor) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func loadCount<T: SyncedStorable>(_ type: T.Type, _ descriptor: CollectionDescriptor) async { |
|
|
|
|
let calls = await StoreCenter.main.apiCalls(type: type) |
|
|
|
|
descriptor.count = calls.count |
|
|
|
|
// Logger.log("\(descriptor.name), count = \(calls.count)") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Observable |
|
|
|
|
class CollectionDescriptor: Identifiable, Hashable { |
|
|
|
|
|
|
|
|
|
var id: UUID = UUID() |
|
|
|
|
var name: String |
|
|
|
|
var type: any Storable.Type |
|
|
|
|
var count: Int = 0 |
|
|
|
|
|
|
|
|
|
init(name: String, type: any Storable.Type) { |
|
|
|
|
self.name = name |
|
|
|
|
self.type = type |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static func == (lhs: CollectionDescriptor, rhs: CollectionDescriptor) -> Bool { |
|
|
|
|
return lhs.id == rhs.id |
|
|
|
|
} |
|
|
|
|
func hash(into hasher: inout Hasher) { |
|
|
|
|
hasher.combine(self.id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct APICallsView: View { |
|
|
|
|
|
|
|
|
|
var name: String |
|
|
|
|
@State var text: String = "" |
|
|
|
|
var type: any SyncedStorable.Type |
|
|
|
|
|
|
|
|
|
// @State var text: String = "" |
|
|
|
|
@State var apiCalls: [any SomeCall] = [] |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
List { |
|
|
|
|
Text(self.text).lineLimit(nil) |
|
|
|
|
}.onAppear { |
|
|
|
|
ForEach(self.apiCalls, id: \.id) { apiCall in |
|
|
|
|
NavigationLink { |
|
|
|
|
APICallView(apiCall: apiCall, type: self.type) |
|
|
|
|
} label: { |
|
|
|
|
LabeledContent(apiCall.method.rawValue, value: apiCall.attemptsCount.string) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Text(self.text).lineLimit(nil) |
|
|
|
|
}.navigationTitle(self.name) |
|
|
|
|
.onAppear { |
|
|
|
|
self._load() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -43,10 +102,116 @@ struct APICallsView: View { |
|
|
|
|
@MainActor |
|
|
|
|
fileprivate func _load() { |
|
|
|
|
Task { |
|
|
|
|
self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) |
|
|
|
|
await self.load(self.type) |
|
|
|
|
// self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func load<T: SyncedStorable>(_ type: T.Type) async { |
|
|
|
|
let calls: [ApiCall<T>] = await StoreCenter.main.apiCalls(type: type) |
|
|
|
|
self.apiCalls = calls |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct APICallView: View { |
|
|
|
|
|
|
|
|
|
var apiCall: any SomeCall |
|
|
|
|
var type: any SyncedStorable.Type |
|
|
|
|
@State private var errorMessage: String? = nil |
|
|
|
|
@State private var isLoading: Bool = false |
|
|
|
|
@State private var showSuccessPopover: Bool = false |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
Form { |
|
|
|
|
Section { |
|
|
|
|
LabeledContent("Method", value: apiCall.method.rawValue) |
|
|
|
|
LabeledContent("Data id", value: apiCall.stringId) |
|
|
|
|
LabeledContent("Attempts", value: apiCall.attemptsCount.string) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Section { |
|
|
|
|
if isLoading { |
|
|
|
|
ProgressView() |
|
|
|
|
.padding() |
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
Button("Execute") { |
|
|
|
|
self.execute() |
|
|
|
|
// Clear previous error when attempting again |
|
|
|
|
errorMessage = nil |
|
|
|
|
} |
|
|
|
|
.disabled(isLoading) |
|
|
|
|
} |
|
|
|
|
if let errorMessage = errorMessage { |
|
|
|
|
LabeledContent("Error", value: errorMessage) |
|
|
|
|
// VStack(alignment: .leading) { |
|
|
|
|
// Text("Error:") |
|
|
|
|
// .fontWeight(.bold) |
|
|
|
|
// .foregroundColor(.red) |
|
|
|
|
// Text(errorMessage) |
|
|
|
|
// .foregroundColor(.red) |
|
|
|
|
// } |
|
|
|
|
} |
|
|
|
|
if showSuccessPopover { |
|
|
|
|
VStack { |
|
|
|
|
Image(systemName: "checkmark.circle.fill") |
|
|
|
|
.foregroundColor(.green) |
|
|
|
|
.font(.title) |
|
|
|
|
Text("Success") |
|
|
|
|
.foregroundColor(.green) |
|
|
|
|
} |
|
|
|
|
.padding() |
|
|
|
|
.background( |
|
|
|
|
RoundedRectangle(cornerRadius: 8) |
|
|
|
|
.fill(Color.white) |
|
|
|
|
.shadow(radius: 2) |
|
|
|
|
) |
|
|
|
|
.transition(.scale.combined(with: .opacity)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
Section { |
|
|
|
|
Text(apiCall.dataContent ?? "none").monospaced().font(.caption) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
.animation(.easeInOut, value: showSuccessPopover) |
|
|
|
|
.navigationTitle(String(describing: self.type)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func execute() { |
|
|
|
|
self.execute(type: self.type) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func execute<T: SyncedStorable>(type: T.Type) { |
|
|
|
|
if let call = self.apiCall as? ApiCall<T> { |
|
|
|
|
Task { |
|
|
|
|
await MainActor.run { |
|
|
|
|
isLoading = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
let result = try await StoreCenter.main.execute(apiCalls: [call]) |
|
|
|
|
print(result) |
|
|
|
|
// Update UI on the main thread |
|
|
|
|
await MainActor.run { |
|
|
|
|
errorMessage = nil |
|
|
|
|
isLoading = false |
|
|
|
|
|
|
|
|
|
// Show success popover |
|
|
|
|
showSuccessPopover = true |
|
|
|
|
} |
|
|
|
|
} catch { |
|
|
|
|
// Update UI on the main thread |
|
|
|
|
await MainActor.run { |
|
|
|
|
errorMessage = error.localizedDescription |
|
|
|
|
isLoading = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//#Preview { |
|
|
|
|
|