repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / tui / menu
Eric Bower · 05 Oct 24

menu.go

  1package menu
  2
  3import (
  4	tea "github.com/charmbracelet/bubbletea"
  5	"github.com/muesli/reflow/indent"
  6	"github.com/picosh/pico/tui/common"
  7)
  8
  9// menuChoice represents a chosen menu item.
 10type menuChoice int
 11
 12type MenuChoiceMsg struct {
 13	MenuChoice menuChoice
 14}
 15
 16const (
 17	KeysChoice menuChoice = iota
 18	TokensChoice
 19	NotificationsChoice
 20	PlusChoice
 21	SettingsChoice
 22	LogsChoice
 23	ChatChoice
 24	ExitChoice
 25	UnsetChoice // set when no choice has been made
 26)
 27
 28// menu text corresponding to menu choices. these are presented to the user.
 29var menuChoices = map[menuChoice]string{
 30	KeysChoice:          "Manage pubkeys",
 31	TokensChoice:        "Manage tokens",
 32	NotificationsChoice: "Notifications",
 33	PlusChoice:          "Pico+",
 34	SettingsChoice:      "Settings",
 35	LogsChoice:          "Logs",
 36	ChatChoice:          "Chat",
 37	ExitChoice:          "Exit",
 38}
 39
 40type Model struct {
 41	shared     common.SharedModel
 42	err        error
 43	menuIndex  int
 44	menuChoice menuChoice
 45}
 46
 47func NewModel(shared common.SharedModel) Model {
 48	return Model{
 49		shared:     shared,
 50		menuChoice: UnsetChoice,
 51	}
 52}
 53
 54func (m Model) Init() tea.Cmd {
 55	return nil
 56}
 57
 58func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 59	var (
 60		cmds []tea.Cmd
 61	)
 62
 63	switch msg := msg.(type) {
 64	case tea.KeyMsg:
 65		switch msg.Type {
 66		case tea.KeyCtrlC:
 67			m.shared.Dbpool.Close()
 68			return m, tea.Quit
 69		}
 70
 71		switch msg.String() {
 72		case "q", "esc":
 73			m.shared.Dbpool.Close()
 74			return m, tea.Quit
 75
 76		case "up", "k":
 77			m.menuIndex--
 78			if m.menuIndex < 0 {
 79				m.menuIndex = len(menuChoices) - 1
 80			}
 81
 82		case "enter":
 83			m.menuChoice = menuChoice(m.menuIndex)
 84			cmds = append(cmds, MenuMsg(m.menuChoice))
 85
 86		case "down", "j":
 87			m.menuIndex++
 88			if m.menuIndex >= len(menuChoices) {
 89				m.menuIndex = 0
 90			}
 91		}
 92	}
 93
 94	return m, tea.Batch(cmds...)
 95}
 96
 97func MenuMsg(choice menuChoice) tea.Cmd {
 98	return func() tea.Msg {
 99		return MenuChoiceMsg{
100			MenuChoice: choice,
101		}
102	}
103}
104
105func (m Model) bioView() string {
106	if m.shared.User == nil {
107		return "Loading user info..."
108	}
109
110	var username string
111	if m.shared.User.Name != "" {
112		username = m.shared.User.Name
113	} else {
114		username = m.shared.Styles.Subtle.Render("(none set)")
115	}
116
117	expires := ""
118	if m.shared.PlusFeatureFlag != nil {
119		expires = m.shared.PlusFeatureFlag.ExpiresAt.Format(common.DateFormat)
120	}
121
122	vals := []string{
123		"Username", username,
124		"Joined", m.shared.User.CreatedAt.Format(common.DateFormat),
125	}
126
127	if expires != "" {
128		vals = append(vals, "Pico+ Expires", expires)
129	}
130
131	return common.KeyValueView(m.shared.Styles, vals...)
132}
133
134func (m Model) menuView() string {
135	var s string
136	for i := 0; i < len(menuChoices); i++ {
137		e := "  "
138		menuItem := menuChoices[menuChoice(i)]
139		if i == m.menuIndex {
140			e = m.shared.Styles.SelectionMarker.String() +
141				m.shared.Styles.SelectedMenuItem.Render(menuItem)
142		} else {
143			e += menuItem
144		}
145		if i < len(menuChoices)-1 {
146			e += "\n"
147		}
148		s += e
149	}
150
151	return s
152}
153
154func (m Model) errorView(err error) string {
155	head := m.shared.Styles.Error.Render("Error: ")
156	body := m.shared.Styles.Subtle.Render(err.Error())
157	msg := m.shared.Styles.Wrap.Render(head + body)
158	return "\n\n" + indent.String(msg, 2)
159}
160
161func (m Model) footerView() string {
162	if m.err != nil {
163		return m.errorView(m.err)
164	}
165	return "\n\n" + common.HelpView(m.shared.Styles, "j/k, ↑/↓: choose", "enter: select")
166}
167
168func (m Model) View() string {
169	s := m.bioView()
170	s += "\n\n" + m.menuView()
171	s += m.footerView()
172	return s
173}