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