Eric Bower
·
17 May 24
create.go
1package createtoken
2
3import (
4 "fmt"
5 "strings"
6
7 input "github.com/charmbracelet/bubbles/textinput"
8 tea "github.com/charmbracelet/bubbletea"
9 "github.com/picosh/pico/tui/common"
10 "github.com/picosh/pico/tui/pages"
11)
12
13type state int
14
15const (
16 ready state = iota
17 submitting
18 submitted
19)
20
21type index int
22
23const (
24 textInput index = iota
25 okButton
26 cancelButton
27)
28
29type TokenSetMsg struct {
30 token string
31}
32
33type errMsg struct {
34 err error
35}
36
37func (e errMsg) Error() string { return e.err.Error() }
38
39type Model struct {
40 shared common.SharedModel
41
42 state state
43 tokenName string
44 token string
45 index index
46 errMsg string
47 input input.Model
48}
49
50// NewModel returns a new username model in its initial state.
51func NewModel(shared common.SharedModel) Model {
52 im := input.New()
53 im.Cursor.Style = shared.Styles.Cursor
54 im.Placeholder = "A name used for your reference"
55 im.PlaceholderStyle = shared.Styles.InputPlaceholder
56 im.Prompt = shared.Styles.FocusedPrompt.String()
57 im.CharLimit = 256
58 im.Focus()
59
60 return Model{
61 shared: shared,
62
63 state: ready,
64 tokenName: "",
65 token: "",
66 index: textInput,
67 errMsg: "",
68 input: im,
69 }
70}
71
72// updateFocus updates the focused states in the model based on the current
73// focus index.
74func (m *Model) updateFocus() {
75 if m.index == textInput && !m.input.Focused() {
76 m.input.Focus()
77 m.input.Prompt = m.shared.Styles.FocusedPrompt.String()
78 } else if m.index != textInput && m.input.Focused() {
79 m.input.Blur()
80 m.input.Prompt = m.shared.Styles.Prompt.String()
81 }
82}
83
84// Move the focus index one unit forward.
85func (m *Model) indexForward() {
86 m.index += 1
87 if m.index > cancelButton {
88 m.index = textInput
89 }
90
91 m.updateFocus()
92}
93
94// Move the focus index one unit backwards.
95func (m *Model) indexBackward() {
96 m.index -= 1
97 if m.index < textInput {
98 m.index = cancelButton
99 }
100
101 m.updateFocus()
102}
103
104// Init is the Bubble Tea initialization function.
105func (m Model) Init() tea.Cmd {
106 return input.Blink
107}
108
109// Update is the Bubble Tea update loop.
110func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
111 switch msg := msg.(type) {
112 case tea.KeyMsg:
113 // Ignore keys if we're submitting
114 if m.state == submitting {
115 return m, nil
116 }
117
118 switch msg.String() {
119 case "q", "esc":
120 return m, pages.Navigate(pages.TokensPage)
121 case "tab":
122 m.indexForward()
123 case "shift+tab":
124 m.indexBackward()
125 case "l", "k", "right":
126 if m.index != textInput {
127 m.indexForward()
128 }
129 case "h", "j", "left":
130 if m.index != textInput {
131 m.indexBackward()
132 }
133 case "up", "down":
134 if m.index == textInput {
135 m.indexForward()
136 } else {
137 m.index = textInput
138 m.updateFocus()
139 }
140 case "enter":
141 switch m.index {
142 case textInput:
143 fallthrough
144 case okButton: // Submit the form
145 // form already submitted so ok button exits
146 if m.state == submitted {
147 return m, pages.Navigate(pages.TokensPage)
148 }
149
150 m.state = submitting
151 m.errMsg = ""
152 m.tokenName = strings.TrimSpace(m.input.Value())
153
154 return m, m.addToken()
155 case cancelButton:
156 return m, pages.Navigate(pages.TokensPage)
157 }
158 }
159
160 // Pass messages through to the input element if that's the element
161 // in focus
162 if m.index == textInput {
163 var cmd tea.Cmd
164 m.input, cmd = m.input.Update(msg)
165
166 return m, cmd
167 }
168
169 return m, nil
170
171 case TokenSetMsg:
172 m.state = submitted
173 m.token = msg.token
174 return m, nil
175
176 case errMsg:
177 m.state = ready
178 head := m.shared.Styles.Error.Render("Oh, what? There was a curious error we were not expecting. ")
179 body := m.shared.Styles.Subtle.Render(msg.Error())
180 m.errMsg = m.shared.Styles.Wrap.Render(head + body)
181
182 return m, nil
183
184 // leaving page so reset model
185 case pages.NavigateMsg:
186 next := NewModel(m.shared)
187 return next, next.Init()
188
189 default:
190 var cmd tea.Cmd
191 m.input, cmd = m.input.Update(msg) // Do we still need this?
192
193 return m, cmd
194 }
195}
196
197// View renders current view from the model.
198func (m Model) View() string {
199 s := "Enter a name for your token\n\n"
200 s += m.input.View() + "\n\n"
201
202 if m.state == submitting {
203 s += spinnerView()
204 } else if m.state == submitted {
205 s = fmt.Sprintf("Save this token:\n%s\n\n", m.token)
206 s += "After you exit this screen you will *not* be able to see it again.\n\n"
207 s += common.OKButtonView(m.shared.Styles, m.index == 1, true)
208 } else {
209 s += common.OKButtonView(m.shared.Styles, m.index == 1, true)
210 s += " " + common.CancelButtonView(m.shared.Styles, m.index == 2, false)
211 if m.errMsg != "" {
212 s += "\n\n" + m.errMsg
213 }
214 }
215
216 return s
217}
218
219func (m *Model) addToken() tea.Cmd {
220 return func() tea.Msg {
221 token, err := m.shared.Dbpool.InsertToken(m.shared.User.ID, m.tokenName)
222 if err != nil {
223 return errMsg{err}
224 }
225
226 return TokenSetMsg{token}
227 }
228}
229
230func spinnerView() string {
231 return "Submitting..."
232}