Antonio Mika
·
2026-10-10
1package tui
2
3import (
4 "github.com/picosh/pico/pkg/db"
5 "go.rockorager.dev/vaxis"
6 "go.rockorager.dev/vaxis/vxfw"
7 "go.rockorager.dev/vaxis/vxfw/list"
8 "go.rockorager.dev/vaxis/vxfw/text"
9)
10
11var menuChoices = []string{
12 "pubkeys",
13 "invite",
14 "tokens",
15 "logs",
16 "access-logs",
17 "analytics",
18 "pages",
19 "tuns",
20 "pico+",
21 "chat",
22}
23
24type LoadedUsageStats struct{}
25
26type MenuPage struct {
27 shared *SharedModel
28
29 list list.Dynamic
30 features *FeaturesList
31 services *ServicesList
32 stats *db.UserStats
33}
34
35func getMenuWidget(i uint, cursor uint) vxfw.Widget {
36 if int(i) >= len(menuChoices) {
37 return nil
38 }
39 var style vaxis.Style
40 if i == cursor {
41 style.Attribute = vaxis.AttrReverse
42 }
43 content := menuChoices[i]
44 return &text.Text{
45 Content: content,
46 Style: style,
47 }
48}
49
50func NewMenuPage(shrd *SharedModel) *MenuPage {
51 m := &MenuPage{shared: shrd}
52 m.list = list.Dynamic{Builder: getMenuWidget, DrawCursor: true}
53 m.features = NewFeaturesList(shrd)
54 m.services = NewServicesList(shrd)
55 return m
56}
57
58func loadChat(shrd *SharedModel) {
59 sp := &SenpaiCmd{
60 Shared: shrd,
61 }
62 _ = sp.Run()
63}
64
65func (m *MenuPage) fetchUsageStats() error {
66 stats, err := m.shared.Dbpool.FindUserStats(m.shared.User.ID)
67 if err != nil {
68 return err
69 }
70 m.stats = stats
71 return nil
72}
73
74func (m *MenuPage) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Command, error) {
75 switch msg := ev.(type) {
76 case PageIn:
77 _ = m.fetchUsageStats()
78 cmd, _ := m.features.HandleEvent(vxfw.Init{}, phase)
79 servicesCmd, _ := m.services.HandleEvent(vxfw.Init{}, phase)
80 return vxfw.BatchCmd([]vxfw.Command{
81 cmd,
82 servicesCmd,
83 vxfw.FocusWidgetCmd(&m.list),
84 }), nil
85 case vaxis.Key:
86 if msg.Matches(vaxis.KeyEnter) {
87 cursor := int(m.list.Cursor())
88 if cursor >= len(menuChoices) {
89 return nil, nil
90 }
91 choice := menuChoices[m.list.Cursor()]
92 m.shared.App.PostEvent(Navigate{To: choice})
93 }
94 }
95 return nil, nil
96}
97
98func (m *MenuPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
99 info, _ := NewUserInfo(m.shared).Draw(ctx)
100
101 brd := NewBorder(&m.list)
102 brd.Label = "menu"
103 brd.Style = vaxis.Style{Foreground: oj}
104 menuSurf, _ := brd.Draw(vxfw.DrawContext{
105 Characters: ctx.Characters,
106 Max: vxfw.Size{
107 Width: 30,
108 Height: uint16(len(menuChoices)) + 3,
109 },
110 })
111
112 services, _ := m.services.Draw(ctx)
113 features, _ := m.features.Draw(ctx)
114
115 leftPane := NewGroupStack([]vxfw.Surface{
116 menuSurf,
117 info,
118 services,
119 })
120 leftSurf, _ := leftPane.Draw(ctx)
121
122 right := []vxfw.Surface{}
123 if len(m.features.features) > 0 {
124 right = append(right, features)
125 }
126
127 if m.stats != nil {
128 pages, _ := NewUsageInfo("pages", &m.stats.Pages).Draw(ctx)
129 prose, _ := NewUsageInfo("prose", &m.stats.Prose).Draw(ctx)
130 pastes, _ := NewUsageInfo("pastes", &m.stats.Pastes).Draw(ctx)
131 feeds, _ := NewUsageInfo("feeds", &m.stats.Feeds).Draw(ctx)
132 right = append(right,
133 pages,
134 prose,
135 pastes,
136 feeds,
137 )
138 }
139 rightPane := NewGroupStack(right)
140 rightSurf, _ := rightPane.Draw(ctx)
141
142 root := vxfw.NewSurface(ctx.Max.Width, ctx.Max.Height, m)
143 root.AddChild(0, 0, leftSurf)
144 root.AddChild(30, 0, rightSurf)
145 return root, nil
146}