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}