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}