main pico / pkg / tui / menu.go
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}