@@ -2,6 +2,7 @@ package bootstrap
2
2
3
3
import (
4
4
"fmt"
5
+ "strconv"
5
6
"strings"
6
7
7
8
"github.com/charmbracelet/bubbles/table"
@@ -19,7 +20,38 @@ type preWizardModel struct {
19
20
msgChan chan GitProvider
20
21
}
21
22
22
- const flagSeparator = " - "
23
+ type wizardModel struct {
24
+ windowIsReady bool
25
+ viewport viewport.Model
26
+ inputs []* bootstrapWizardInput
27
+ msgChan chan BootstrapCmdOptions
28
+ cursorMode textinput.CursorMode
29
+ focusIndex int
30
+ errorMsg string
31
+ }
32
+
33
+ type checkbox struct {
34
+ checked bool
35
+ }
36
+
37
+ type inputType int32
38
+
39
+ const (
40
+ inputTypeTextInput inputType = 0
41
+ inputTypeCheckbox inputType = 1
42
+ )
43
+
44
+ type bootstrapWizardInput struct {
45
+ inputType inputType
46
+ prompt string
47
+ textInput textinput.Model
48
+ checkboxInput * checkbox
49
+ }
50
+
51
+ const (
52
+ flagSeparator = " - "
53
+ buttonText = "Submit"
54
+ )
23
55
24
56
// UI styling
25
57
var (
37
69
helpStyle = blurredStyle .Copy ()
38
70
cursorModeHelpStyle = lipgloss .NewStyle ().Foreground (lipgloss .Color ("244" ))
39
71
40
- focusedButton = focusedStyle .Copy (). Render ("[ Submit ]" )
41
- blurredButton = fmt .Sprintf ("[ %s ]" , blurredStyle .Render ("Submit" ))
72
+ focusedButton = focusedStyle .Render ( fmt . Sprintf ("[ %s ]" , buttonText ) )
73
+ blurredButton = fmt .Sprintf ("[ %s ]" , blurredStyle .Render (buttonText ))
42
74
)
43
75
44
76
func makeViewport (width int , height int , content string ) viewport.Model {
@@ -162,61 +194,94 @@ func (m preWizardModel) getContent() string {
162
194
) + m .textInput .View ()
163
195
}
164
196
165
- type wizardModel struct {
166
- windowIsReady bool
167
- viewport viewport.Model
168
- textInputs []textinput.Model
169
- prompts []string
170
- msgChan chan BootstrapCmdOptions
171
- cursorMode textinput.CursorMode
172
- focusIndex int
173
- errorMsg string
174
- }
175
197
176
- func makeTextInput (task * BootstrapWizardTask , isFocused bool ) textinput.Model {
177
- ti := textinput .New ()
178
- ti .CursorStyle = cursorStyle
179
- ti .CharLimit = 100
180
198
181
- ti . SetValue (task . flagValue )
182
- ti . Placeholder = task . flagDescription
199
+ func makeInput (task * BootstrapWizardTask , isFocused bool ) * bootstrapWizardInput {
200
+ var inputType inputType
183
201
184
- if task .isPassword {
185
- ti .EchoMode = textinput .EchoPassword
202
+ if task .isBoolean {
203
+ inputType = inputTypeCheckbox
204
+ } else {
205
+ inputType = inputTypeTextInput
186
206
}
187
207
188
- if isFocused {
189
- ti .Focus ()
190
- ti .PromptStyle = focusedStyle
191
- ti .TextStyle = focusedStyle
208
+ prompt := task .flagName + flagSeparator + task .flagDescription
209
+
210
+ ti := textinput.Model {}
211
+
212
+ var cb * checkbox
213
+
214
+ if inputType == inputTypeCheckbox {
215
+ cb = & checkbox {
216
+ checked : task .flagValue == "true" ,
217
+ }
218
+ } else {
219
+ ti = textinput .New ()
220
+ ti .CursorStyle = cursorStyle
221
+ ti .CharLimit = 100
222
+
223
+ ti .SetValue (task .flagValue )
224
+ ti .Placeholder = task .flagDescription
225
+
226
+ if task .isPassword {
227
+ ti .EchoMode = textinput .EchoPassword
228
+ }
229
+
230
+ if isFocused {
231
+ ti .Focus ()
232
+ ti .PromptStyle = focusedStyle
233
+ ti .TextStyle = focusedStyle
234
+ }
192
235
}
193
236
194
- return ti
237
+ return & bootstrapWizardInput {
238
+ inputType : inputType ,
239
+ prompt : prompt ,
240
+ textInput : ti ,
241
+ checkboxInput : cb ,
242
+ }
195
243
}
196
244
197
- func initialWizardModel (tasks []* BootstrapWizardTask , msgChan chan BootstrapCmdOptions ) wizardModel {
198
- numInputs := len (tasks )
245
+ func (input * bootstrapWizardInput ) getView (isFocused bool ) string {
246
+ if input .inputType == inputTypeTextInput {
247
+ return input .textInput .View ()
248
+ }
199
249
200
- inputs := make ([]textinput. Model , numInputs )
250
+ var checkmark string
201
251
202
- for i := range inputs {
203
- task := tasks [i ]
252
+ if input .checkboxInput .checked {
253
+ checkmark = "x"
254
+
255
+ if isFocused {
256
+ checkmark = focusedStyle .Render (checkmark )
257
+ }
258
+ } else {
259
+ checkmark = blurredStyle .Render ("_" )
260
+ }
204
261
205
- ti := makeTextInput (task , i == 0 )
262
+ open := "["
263
+ close := "]"
206
264
207
- inputs [i ] = ti
265
+ if isFocused {
266
+ open = focusedStyle .Render (open )
267
+ close = focusedStyle .Render (close )
208
268
}
209
269
210
- prompts := []string {}
270
+ return fmt .Sprintf ("%s%s%s" , open , checkmark , close )
271
+ }
272
+
273
+ func initialWizardModel (tasks []* BootstrapWizardTask , msgChan chan BootstrapCmdOptions ) wizardModel {
274
+ numInputs := len (tasks )
275
+
276
+ inputs := make ([]* bootstrapWizardInput , numInputs )
211
277
212
- for _ , task := range tasks {
213
- prompts = append ( prompts , task . flagName + flagSeparator + task . flagDescription )
278
+ for i := range inputs {
279
+ inputs [ i ] = makeInput ( tasks [ i ], i == 0 )
214
280
}
215
281
216
282
return wizardModel {
217
- textInputs : inputs ,
283
+ inputs : inputs ,
218
284
errorMsg : "" ,
219
- prompts : prompts ,
220
285
msgChan : msgChan ,
221
286
}
222
287
}
@@ -241,34 +306,43 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
241
306
m .cursorMode = textinput .CursorBlink
242
307
}
243
308
244
- cmdsTextInputs := make ([]tea.Cmd , len (m .textInputs ))
309
+ cmdsTextInputs := make ([]tea.Cmd , len (m .inputs ))
245
310
246
- for i := range m .textInputs {
247
- cmdsTextInputs [i ] = m .textInputs [i ].SetCursorMode (m .cursorMode )
311
+ for i := range m .inputs {
312
+ input := m .inputs [i ]
313
+ if input .inputType == inputTypeTextInput {
314
+ cmdsTextInputs [i ] = input .textInput .SetCursorMode (m .cursorMode )
315
+ }
248
316
}
249
317
250
318
cmds = append (cmds , cmdsTextInputs ... )
251
319
case tea .KeyTab , tea .KeyShiftTab , tea .KeyEnter :
252
320
t := msg .Type
253
321
254
- if t == tea .KeyEnter && m .focusIndex == len (m .textInputs ) {
322
+ if t == tea .KeyEnter && m .focusIndex == len (m .inputs ) {
255
323
options := make (BootstrapCmdOptions )
256
324
257
- for i , input := range m .textInputs {
258
- prompt := m . prompts [ i ]
325
+ for _ , input := range m .inputs {
326
+ prompt := input . prompt
259
327
260
- value := strings . TrimSpace ( input . Value ())
328
+ var value string
261
329
262
- if value == "" {
263
- m . errorMsg = "Missing value in " + input .Placeholder
330
+ if input . inputType == inputTypeTextInput {
331
+ value = strings . TrimSpace ( input .textInput . Value ())
264
332
265
- m .viewport .SetContent (m .getContent ())
333
+ if value == "" {
334
+ m .errorMsg = "Missing value in " + input .textInput .Placeholder
266
335
267
- var cmdViewport tea. Cmd
336
+ m . viewport . SetContent ( m . getContent ())
268
337
269
- m . viewport , cmdViewport = m . viewport . Update ( msg )
338
+ var cmdViewport tea. Cmd
270
339
271
- return m , cmdViewport
340
+ m .viewport , cmdViewport = m .viewport .Update (msg )
341
+
342
+ return m , cmdViewport
343
+ }
344
+ } else {
345
+ value = strconv .FormatBool (input .checkboxInput .checked )
272
346
}
273
347
274
348
options [prompt [:strings .Index (prompt , flagSeparator )]] = value
@@ -285,29 +359,43 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
285
359
m .focusIndex ++
286
360
}
287
361
288
- if m .focusIndex > len (m .textInputs ) {
362
+ if m .focusIndex > len (m .inputs ) {
289
363
m .focusIndex = 0
290
364
} else if m .focusIndex < 0 {
291
- m .focusIndex = len (m .textInputs )
365
+ m .focusIndex = len (m .inputs )
292
366
}
293
367
294
- cmdsTextInputs := make ([]tea.Cmd , len (m .textInputs ))
368
+ cmdsTextInputs := []tea.Cmd {}
369
+
370
+ for i := 0 ; i <= len (m .inputs )- 1 ; i ++ {
371
+ input := m .inputs [i ]
372
+
373
+ if input .inputType == inputTypeCheckbox {
374
+ continue
375
+ }
295
376
296
- for i := 0 ; i <= len (m .textInputs )- 1 ; i ++ {
297
377
if i == m .focusIndex {
298
- cmdsTextInputs [ i ] = m . textInputs [ i ] .Focus ()
299
- m . textInputs [ i ] .PromptStyle = focusedStyle
300
- m . textInputs [ i ] .TextStyle = focusedStyle
378
+ cmdsTextInputs = append ( cmdsTextInputs , input . textInput .Focus () )
379
+ input . textInput .PromptStyle = focusedStyle
380
+ input . textInput .TextStyle = focusedStyle
301
381
302
382
continue
303
383
}
304
384
305
- m . textInputs [ i ] .Blur ()
306
- m . textInputs [ i ] .PromptStyle = noStyle
307
- m . textInputs [ i ] .TextStyle = noStyle
385
+ input . textInput .Blur ()
386
+ input . textInput .PromptStyle = noStyle
387
+ input . textInput .TextStyle = noStyle
308
388
}
309
389
310
390
cmds = append (cmds , cmdsTextInputs ... )
391
+ case tea .KeySpace :
392
+ input := m .inputs [m .focusIndex ]
393
+
394
+ if m .focusIndex == len (m .inputs ) || input .inputType != inputTypeCheckbox {
395
+ break
396
+ }
397
+
398
+ input .checkboxInput .checked = ! input .checkboxInput .checked
311
399
}
312
400
case tea.WindowSizeMsg :
313
401
if ! m .windowIsReady {
@@ -338,12 +426,18 @@ func (m wizardModel) View() string {
338
426
}
339
427
340
428
func (m * wizardModel ) updateInputs (msg tea.Msg ) tea.Cmd {
341
- cmds := make ([]tea.Cmd , len (m .textInputs ))
429
+ cmds := make ([]tea.Cmd , len (m .inputs ))
342
430
343
431
// Only text inputs with Focus() set will respond, so it's safe to simply
344
432
// update all of them here without any further logic.
345
- for i := range m .textInputs {
346
- m .textInputs [i ], cmds [i ] = m .textInputs [i ].Update (msg )
433
+ for i := range m .inputs {
434
+ input := m .inputs [i ]
435
+
436
+ if input .inputType == inputTypeCheckbox {
437
+ continue
438
+ }
439
+
440
+ input .textInput , cmds [i ] = input .textInput .Update (msg )
347
441
}
348
442
349
443
return tea .Batch (cmds ... )
@@ -354,21 +448,24 @@ func (m wizardModel) getContent() string {
354
448
355
449
b .WriteString ("Please enter the following values" + "\n " +
356
450
"(Tab and Shift+Tab to move input selection," + "\n " +
451
+ "(Space to toggle the currently focused checkbox," + "\n " +
357
452
"Enter to move to the next input or submit the form, " + "\n " +
358
453
"up and down arrows to scroll the view, Ctrl+C twice to quit):" + "\n \n \n " )
359
454
360
- for i := range m .textInputs {
361
- b .WriteString (m .prompts [i ])
455
+ for i := range m .inputs {
456
+ input := m .inputs [i ]
457
+
458
+ b .WriteString (input .prompt )
362
459
b .WriteRune ('\n' )
363
- b .WriteString (m . textInputs [ i ]. View ( ))
460
+ b .WriteString (input . getView ( i == m . focusIndex ))
364
461
365
- if i < len (m .textInputs )- 1 {
462
+ if i < len (m .inputs )- 1 {
366
463
b .WriteRune ('\n' )
367
464
}
368
465
}
369
466
370
467
button := & blurredButton
371
- if m .focusIndex == len (m .textInputs ) {
468
+ if m .focusIndex == len (m .inputs ) {
372
469
button = & focusedButton
373
470
}
374
471
0 commit comments