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