gitbay/MRs/MRListViewModel.swift
73 lines · 2267 bytes
1import Foundation
2import Observation
3
4/// `mr list <repo> --state <s>`, paginated.
5@Observable
6@MainActor
7final class MRListViewModel {
8
9 let list: PagedListModel<MergeRequest>
10 let repoPath: String
11 private let client: GitbayClient
12 var filter = MRFilter() {
13 didSet {
14 guard filter != oldValue else { return }
15 configureList()
16 // Only the free-text fields changed: debounce, so typing does
17 // not fire a request per keystroke. Anything else — a picker —
18 // reloads now. MRFilter has no `assignee` field: `mr list`
19 // rejects it.
20 var typed = oldValue
21 typed.search = filter.search
22 typed.author = filter.author
23 if typed == filter {
24 scheduleSearchReload()
25 } else {
26 reloadTask?.cancel()
27 reloadTask = Task { await list.reload() }
28 }
29 }
30 }
31
32 /// Open and closed milestones for the filter picker; fetched on first use.
33 private(set) var milestones: [Milestone] = []
34 private var reloadTask: Task<Void, Never>?
35
36 init(client: GitbayClient, repoPath: String) {
37 self.client = client
38 self.repoPath = repoPath
39 list = PagedListModel(
40 client: client,
41 argv: ["mr", "list", repoPath] + MRFilter().flags(),
42 emptyMessage: MRFilter().emptyMessage
43 )
44 }
45
46 var state: LoadState<[MergeRequest]> { list.state }
47
48 func load() async {
49 reloadTask?.cancel()
50 await list.reload()
51 }
52
53 func loadMilestones() async {
54 guard milestones.isEmpty else { return }
55 milestones = (try? await client.readList(
56 ["milestone", "list", repoPath, "--state", "all"], of: Milestone.self
57 )) ?? []
58 }
59
60 private func scheduleSearchReload() {
61 reloadTask?.cancel()
62 reloadTask = Task {
63 guard (try? await Task.sleep(for: .milliseconds(300))) != nil else { return }
64 guard !Task.isCancelled else { return }
65 await list.reload()
66 }
67 }
68
69 private func configureList() {
70 list.argv = ["mr", "list", repoPath] + filter.flags()
71 list.emptyMessage = filter.emptyMessage
72 }
73}