import Foundation import Observation /// `label list `, plus the two commands that change the set: /// `label set` creates one or recolours it, `label remove` takes it off /// the repository and off every issue that carried it. @Observable @MainActor final class LabelListViewModel { private(set) var state: LoadState<[IssueLabel]> = .loading private(set) var actionError: String? private(set) var working = false private let client: GitbayClient let repoPath: String init(client: GitbayClient, repoPath: String) { self.client = client self.repoPath = repoPath } func load() async { state = .loading do { let labels = try await client.readList( ["label", "list", repoPath], of: IssueLabel.self ) state = labels.isEmpty ? .empty("No labels yet.") : .loaded(labels) } catch { state = .from(error) } } /// Create a label or set its colour. A nil colour omits `--color` /// entirely, which the server reads as "make sure it exists" and /// leaves any colour it already had; an empty string clears it. func setLabel(_ name: String, color: String?) async { var argv = ["label", "set", repoPath, name] if let color { argv.append(contentsOf: ["--color", color]) } await perform(argv) } func remove(_ name: String) async { await perform(["label", "remove", repoPath, name]) } private func perform(_ argv: [String]) async { working = true actionError = nil defer { working = false } do { try await client.run(argv) await load() } catch let error as GitbayError { actionError = error.userFacingMessage } catch { actionError = GitbayError.transport(error).userFacingMessage } } }