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}