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