gitbay/Issues/LabelListViewModel.swift
86 lines · 2693 bytes
1import Foundation
2import Observation
3
4/// Whose label or milestone set a screen manages. A repository's is
5/// `label <verb> <repo>`; an org's is `org label <verb> <org>`, and its
6/// rows are seen by every repository under the org (v1.19.0).
7nonisolated enum LabelOwner: Hashable, Sendable {
8 case repo(String)
9 case org(String)
10
11 /// The command prefix for `noun` (`label` or `milestone`) and `verb`.
12 func argv(_ noun: String, _ verb: String) -> [String] {
13 switch self {
14 case .repo(let path): [noun, verb, path]
15 case .org(let name): ["org", noun, verb, name]
16 }
17 }
18
19 var isOrg: Bool {
20 if case .org = self { return true }
21 return false
22 }
23}
24
25/// `label list`, plus the two commands that change the set: `label set`
26/// creates one or recolours it, `label remove` takes it off the owner and
27/// off every issue that carried it.
28@Observable
29@MainActor
30final class LabelListViewModel {
31
32 private(set) var state: LoadState<[IssueLabel]> = .loading
33 private(set) var actionError: String?
34 private(set) var working = false
35
36 private let client: GitbayClient
37 let owner: LabelOwner
38
39 init(client: GitbayClient, owner: LabelOwner) {
40 self.client = client
41 self.owner = owner
42 }
43
44 convenience init(client: GitbayClient, repoPath: String) {
45 self.init(client: client, owner: .repo(repoPath))
46 }
47
48 func load() async {
49 state = .loading
50 do {
51 let labels = try await client.readList(
52 owner.argv("label", "list"), of: IssueLabel.self
53 )
54 state = labels.isEmpty ? .empty("No labels yet.") : .loaded(labels)
55 } catch {
56 state = .from(error)
57 }
58 }
59
60 /// Create a label or set its colour. A nil colour omits `--color`
61 /// entirely, which the server reads as "make sure it exists" and
62 /// leaves any colour it already had; an empty string clears it.
63 func setLabel(_ name: String, color: String?) async {
64 var argv = owner.argv("label", "set") + [name]
65 if let color { argv.append(contentsOf: ["--color", color]) }
66 await perform(argv)
67 }
68
69 func remove(_ name: String) async {
70 await perform(owner.argv("label", "remove") + [name])
71 }
72
73 private func perform(_ argv: [String]) async {
74 working = true
75 actionError = nil
76 defer { working = false }
77 do {
78 try await client.run(argv)
79 await load()
80 } catch let error as GitbayError {
81 actionError = error.userFacingMessage
82 } catch {
83 actionError = GitbayError.transport(error).userFacingMessage
84 }
85 }
86}