okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

dawntui: page_view docs on listFrame and listView, always-live filter, shared confirm, ctrl+r retry, gallery of every page state2ecf203

main
3.7 KiB102 linesraw
1package tui
2
3import (
4	"albedo/cli/internal/daemon"
5	"errors"
6
7	tea "charm.land/bubbletea/v2"
8)
9
10// pagesDoc is a page with rows in two badges, one with detail, and actions
11// that cover a text, a secret, a choice and a confirmed chord.
12func pagesDoc() *PageDocument {
13	text := []daemon.FormField{{Name: "text", Label: "text", Type: "text", Required: true}}
14	return &PageDocument{
15		Title:   "work",
16		Summary: "2 open · 2 resolved",
17		Empty:   "no work items yet",
18		Rows: []PageRow{
19			{ID: "12", Text: "fix the webhook retry", Badge: "open", Tone: ToneWarning},
20			{ID: "13", Text: "ship the picker", Badge: "open", Tone: ToneActive},
21			{ID: "7", Text: "stale binary", Badge: "resolved", Tone: ToneMuted},
22			{ID: "3", Text: "ruff not on PATH", Badge: "resolved", Tone: ToneMuted,
23				Detail: "ruff is missing from PATH; the binary lives in pre-commit's env.\n\nsuggestion: document the canonical invocation"},
24		},
25		Actions: []PageAction{
26			{ID: "add", Key: "ctrl+o", Label: "add", Fields: text},
27			{ID: "edit", Key: "ctrl+e", Label: "edit", Row: true, Fields: text},
28			{ID: "delete", Key: "ctrl+d", Label: "delete", Row: true, Confirm: true, Confirmation: "Delete this item?"},
29			{ID: "toggle", Key: "ctrl+t", Label: "done", Row: true, Fields: []daemon.FormField{{Name: "done", Label: "done", Type: "boolean"}}},
30			{ID: "secret", Key: "ctrl+g", Label: "new secret", Row: true, Fields: []daemon.FormField{{Name: "secret", Label: "replacement", Type: "secret", Required: true}}},
31		},
32	}
33}
34
35func pagesModel(width, height int, doc *PageDocument) PageViewModel {
36	m := NewPageViewModel(nil, "s", "/work")
37	m.SetSize(width, height)
38	m.setDoc(doc)
39	m.Busy = false
40	return m
41}
42
43// pagesChord is a ctrl chord press, the way actions are reached.
44func pagesChord(letter rune) tea.KeyPressMsg {
45	return tea.KeyPressMsg{Code: letter, Mod: tea.ModCtrl}
46}
47
48func pagesShot(keys ...tea.Msg) func(width, height int) string {
49	return func(width, height int) string {
50		m := pagesModel(width, height, pagesDoc())
51		for _, key := range keys {
52			m, _ = m.Update(key)
53		}
54		return m.View()
55	}
56}
57
58func init() {
59	down := tea.KeyPressMsg{Code: tea.KeyDown}
60	registerShots("pages",
61		shotState{"list", pagesShot()},
62		shotState{"filtering", pagesShot(shotTyped("ruff")...)},
63		shotState{"no-match", pagesShot(shotTyped("zzz")...)},
64		shotState{"row-detail", pagesShot(down, down, down)},
65		shotState{"confirm", pagesShot(pagesChord('d'))},
66		shotState{"text-prompt", pagesShot(pagesChord('o'))},
67		shotState{"secret-prompt", pagesShot(down, down, down, pagesChord('g'), tea.KeyPressMsg{Code: 's', Text: "s"})},
68		shotState{"choice-prompt", pagesShot(down, pagesChord('t'))},
69		shotState{"actions-menu", pagesShot(pagesChord('a'))},
70		shotState{"busy", func(width, height int) string {
71			m := pagesModel(width, height, pagesDoc())
72			m.Busy = true
73			return m.View()
74		}},
75		shotState{"notice", func(width, height int) string {
76			m := pagesModel(width, height, pagesDoc())
77			m.Notice = "added: write the release notes"
78			return m.View()
79		}},
80		shotState{"error", func(width, height int) string {
81			m := pagesModel(width, height, pagesDoc())
82			m.Error = "daemon refused the change"
83			return m.View()
84		}},
85		shotState{"empty", func(width, height int) string {
86			doc := pagesDoc()
87			doc.Rows = nil
88			return pagesModel(width, height, doc).View()
89		}},
90		shotState{"loading", func(width, height int) string {
91			m := NewPageViewModel(nil, "s", "/work")
92			m.SetSize(width, height)
93			return m.View()
94		}},
95		shotState{"load-failed", func(width, height int) string {
96			m := NewPageViewModel(nil, "s", "/work")
97			m.SetSize(width, height)
98			m, _ = m.Update(pageLoadedMsg{Gen: m.Generation, Err: errors.New("daemon connection unavailable")})
99			return m.View()
100		}},
101	)
102}