Skip to content

Commit 713aeb1

Browse files
committed
Add type bootstrapWizardInput and related types.
Replace text fields with checkboxes for Boolean values in the bootstrap wizard. Add local variables for input lookup. Removed unnecessary copying of styles.
1 parent 3b1059d commit 713aeb1

File tree

2 files changed

+167
-74
lines changed

2 files changed

+167
-74
lines changed

pkg/run/bootstrap/bootstrap_wizard.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,7 @@ const (
300300
branchPrefix = "refs/heads/"
301301
)
302302

303-
type (
304-
GitProvider int32
305-
)
303+
type GitProvider int32
306304

307305
const (
308306
GitProviderUnknown GitProvider = 0

pkg/run/bootstrap/bootstrap_wizard_ui.go

Lines changed: 166 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package bootstrap
22

33
import (
44
"fmt"
5+
"strconv"
56
"strings"
67

78
"github.com/charmbracelet/bubbles/table"
@@ -19,7 +20,38 @@ type preWizardModel struct {
1920
msgChan chan GitProvider
2021
}
2122

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+
)
2355

2456
// UI styling
2557
var (
@@ -37,8 +69,8 @@ var (
3769
helpStyle = blurredStyle.Copy()
3870
cursorModeHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
3971

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))
4274
)
4375

4476
func makeViewport(width int, height int, content string) viewport.Model {
@@ -162,62 +194,93 @@ func (m preWizardModel) getContent() string {
162194
) + m.textInput.View()
163195
}
164196

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+
}
174241
}
175242

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+
}
180247

181-
ti.SetValue(task.flagValue)
182-
ti.Placeholder = task.flagDescription
248+
var checkmark string
183249

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("_")
186258
}
187259

260+
open := "["
261+
close := "]"
262+
188263
if isFocused {
189-
ti.Focus()
190-
ti.PromptStyle = focusedStyle
191-
ti.TextStyle = focusedStyle
264+
open = focusedStyle.Render(open)
265+
close = focusedStyle.Render(close)
192266
}
193267

194-
return ti
268+
return fmt.Sprintf("%s%s%s", open, checkmark, close)
195269
}
196270

197271
func initialWizardModel(tasks []*BootstrapWizardTask, msgChan chan BootstrapCmdOptions) wizardModel {
198272
numInputs := len(tasks)
199273

200-
inputs := make([]textinput.Model, numInputs)
274+
inputs := make([]*bootstrapWizardInput, numInputs)
201275

202276
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)
214278
}
215279

216280
return wizardModel{
217-
textInputs: inputs,
218-
errorMsg: "",
219-
prompts: prompts,
220-
msgChan: msgChan,
281+
inputs: inputs,
282+
errorMsg: "",
283+
msgChan: msgChan,
221284
}
222285
}
223286

@@ -241,34 +304,43 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
241304
m.cursorMode = textinput.CursorBlink
242305
}
243306

244-
cmdsTextInputs := make([]tea.Cmd, len(m.textInputs))
307+
cmdsTextInputs := make([]tea.Cmd, len(m.inputs))
245308

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+
}
248314
}
249315

250316
cmds = append(cmds, cmdsTextInputs...)
251317
case tea.KeyTab, tea.KeyShiftTab, tea.KeyEnter:
252318
t := msg.Type
253319

254-
if t == tea.KeyEnter && m.focusIndex == len(m.textInputs) {
320+
if t == tea.KeyEnter && m.focusIndex == len(m.inputs) {
255321
options := make(BootstrapCmdOptions)
256322

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
259327

260-
value := strings.TrimSpace(input.Value())
328+
if input.inputType == bootstrapWizardInputTypeTextInput {
329+
value = strings.TrimSpace(input.textInput.Value())
261330

262-
if value == "" {
263-
m.errorMsg = "Missing value in " + input.Placeholder
331+
if value == "" {
332+
m.errorMsg = "Missing value in " + input.textInput.Placeholder
264333

265-
m.viewport.SetContent(m.getContent())
334+
m.viewport.SetContent(m.getContent())
266335

267-
var cmdViewport tea.Cmd
336+
var cmdViewport tea.Cmd
268337

269-
m.viewport, cmdViewport = m.viewport.Update(msg)
338+
m.viewport, cmdViewport = m.viewport.Update(msg)
270339

271-
return m, cmdViewport
340+
return m, cmdViewport
341+
}
342+
} else {
343+
value = strconv.FormatBool(input.checkboxInput.checked)
272344
}
273345

274346
options[prompt[:strings.Index(prompt, flagSeparator)]] = value
@@ -285,29 +357,43 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
285357
m.focusIndex++
286358
}
287359

288-
if m.focusIndex > len(m.textInputs) {
360+
if m.focusIndex > len(m.inputs) {
289361
m.focusIndex = 0
290362
} else if m.focusIndex < 0 {
291-
m.focusIndex = len(m.textInputs)
363+
m.focusIndex = len(m.inputs)
292364
}
293365

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+
}
295374

296-
for i := 0; i <= len(m.textInputs)-1; i++ {
297375
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
301379

302380
continue
303381
}
304382

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
308386
}
309387

310388
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
311397
}
312398
case tea.WindowSizeMsg:
313399
if !m.windowIsReady {
@@ -338,12 +424,18 @@ func (m wizardModel) View() string {
338424
}
339425

340426
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))
342428

343429
// Only text inputs with Focus() set will respond, so it's safe to simply
344430
// 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)
347439
}
348440

349441
return tea.Batch(cmds...)
@@ -354,21 +446,24 @@ func (m wizardModel) getContent() string {
354446

355447
b.WriteString("Please enter the following values" + "\n" +
356448
"(Tab and Shift+Tab to move input selection," + "\n" +
449+
"(Space to toggle the currently focused checkbox," + "\n" +
357450
"Enter to move to the next input or submit the form, " + "\n" +
358451
"up and down arrows to scroll the view, Ctrl+C twice to quit):" + "\n\n\n")
359452

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)
362457
b.WriteRune('\n')
363-
b.WriteString(m.textInputs[i].View())
458+
b.WriteString(input.getView(i == m.focusIndex))
364459

365-
if i < len(m.textInputs)-1 {
460+
if i < len(m.inputs)-1 {
366461
b.WriteRune('\n')
367462
}
368463
}
369464

370465
button := &blurredButton
371-
if m.focusIndex == len(m.textInputs) {
466+
if m.focusIndex == len(m.inputs) {
372467
button = &focusedButton
373468
}
374469

0 commit comments

Comments
 (0)