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