Improve API list view

sync2
Laurent 8 months ago
parent a16b104757
commit eaabafb07e
  1. 185
      PadelClub/Views/Navigation/Toolbox/APICallsListView.swift
  2. 162
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift

@ -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 {

@ -52,82 +52,11 @@ struct ToolboxView: View {
}
#if DEBUG
DebugView()
#endif
Section {
NavigationLink("Settings") {
DebugSettingsView()
}
NavigationLink("API calls") {
APICallsListView()
}
}
Section {
RowButtonView("Reset ALL API Calls") {
StoreCenter.main.resetApiCalls()
Logger.log("Api calls reset")
}
}
Section {
RowButtonView("Fix Names") {
for tournament in dataStore.tournaments {
if let store = tournament.tournamentStore {
let playerRegistrations = store.playerRegistrations
playerRegistrations.forEach { player in
player.firstName = player.firstName.trimmed.capitalized
player.lastName = player.lastName.trimmed.uppercased()
}
do {
try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
Section {
RowButtonView("Delete teams") {
for tournament in DataStore.shared.tournaments {
if let store: TournamentStore = tournament.tournamentStore {
let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil })
do {
try store.teamRegistrations.delete(contentOfs: teamRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
Section {
// TODO
RowButtonView("Delete players") {
for tournament in DataStore.shared.tournaments {
if let store: TournamentStore = tournament.tournamentStore {
let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil })
do {
try store.playerRegistrations.delete(contentOfs: playersRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
#if TESTFLIGHT
DebugView()
#endif
Section {
@ -248,6 +177,89 @@ struct ToolboxView: View {
// ToolboxView()
//}
struct DebugView: View {
@EnvironmentObject var dataStore: DataStore
var body: some View {
Section {
NavigationLink("Settings") {
DebugSettingsView()
}
NavigationLink("API calls") {
APICallsListView()
}
}
Section {
RowButtonView("Reset ALL API Calls") {
StoreCenter.main.resetApiCalls()
Logger.log("Api calls reset")
}
}
Section {
RowButtonView("Fix Names") {
for tournament in dataStore.tournaments {
if let store = tournament.tournamentStore {
let playerRegistrations = store.playerRegistrations
playerRegistrations.forEach { player in
player.firstName = player.firstName.trimmed.capitalized
player.lastName = player.lastName.trimmed.uppercased()
}
do {
try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
Section {
RowButtonView("Delete teams") {
for tournament in DataStore.shared.tournaments {
if let store: TournamentStore = tournament.tournamentStore {
let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil })
do {
try store.teamRegistrations.delete(contentOfs: teamRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
Section {
// TODO
RowButtonView("Delete players") {
for tournament in DataStore.shared.tournaments {
if let store: TournamentStore = tournament.tournamentStore {
let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil })
do {
try store.playerRegistrations.delete(contentOfs: playersRegistrations)
} catch {
Logger.error(error)
}
}
}
}
}
}
}
struct ZipLog: Transferable {
private func _getZip() -> URL? {

Loading…
Cancel
Save