* Use placeholder as default if field left empty and is required Co-authored-by: mcastorina <[email protected]> * Drop unused func * uncomment azure source * update wording --------- Co-authored-by: mcastorina <[email protected]>
241 lines
5.3 KiB
Go
241 lines
5.3 KiB
Go
package textinputs
|
|
|
|
// from https://github.com/charmbracelet/bubbletea/blob/master/examples/textinputs/main.go
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
var (
|
|
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
|
|
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
|
noStyle = lipgloss.NewStyle()
|
|
helpStyle = blurredStyle.Copy()
|
|
// cursorStyle = focusedStyle.Copy()
|
|
// cursorModeHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
|
|
|
|
focusedButton = focusedStyle.Copy().Render("[ Next ]")
|
|
blurredButton = fmt.Sprintf("[ %s ]", blurredStyle.Render("Next"))
|
|
focusedSkipButton = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("[ Run with defaults ]")
|
|
blurredSkipButton = fmt.Sprintf("[ %s ]", lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render("Run with defaults"))
|
|
)
|
|
|
|
// SelectNextMsg used for emitting events when the 'Next' button is selected.
|
|
type SelectNextMsg int
|
|
|
|
// SelectSkipMsg used for emitting events when the 'Skip' button is selected.
|
|
type SelectSkipMsg int
|
|
|
|
type Model struct {
|
|
focusIndex int
|
|
inputs []textinput.Model
|
|
configs []InputConfig
|
|
// cursorMode cursor.Mode
|
|
skipButton bool
|
|
}
|
|
|
|
type InputConfig struct {
|
|
Label string
|
|
Key string
|
|
Help string
|
|
Required bool
|
|
Placeholder string
|
|
}
|
|
|
|
type Input struct {
|
|
Value string
|
|
IsDefault bool
|
|
}
|
|
|
|
func (m Model) GetInputs() map[string]Input {
|
|
inputs := make(map[string]Input)
|
|
|
|
for i, input := range m.inputs {
|
|
isDefault := false
|
|
value := input.Value()
|
|
if value == "" && m.configs[i].Required {
|
|
isDefault = true
|
|
value = input.Placeholder
|
|
}
|
|
inputs[m.configs[i].Key] = Input{Value: value, IsDefault: isDefault}
|
|
}
|
|
|
|
return inputs
|
|
}
|
|
|
|
func (m Model) GetLabels() map[string]string {
|
|
labels := make(map[string]string)
|
|
|
|
for _, config := range m.configs {
|
|
labels[config.Key] = config.Label
|
|
}
|
|
|
|
return labels
|
|
}
|
|
|
|
func New(config []InputConfig) Model {
|
|
m := Model{
|
|
inputs: make([]textinput.Model, len(config)),
|
|
}
|
|
|
|
for i, conf := range config {
|
|
input := textinput.New()
|
|
input.Placeholder = conf.Placeholder
|
|
|
|
if i == 0 {
|
|
input.Focus()
|
|
input.TextStyle = focusedStyle
|
|
input.PromptStyle = focusedStyle
|
|
}
|
|
|
|
m.inputs[i] = input
|
|
}
|
|
|
|
m.configs = config
|
|
return m
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return textinput.Blink
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
// Set focus to next input
|
|
case "enter", "up", "down":
|
|
s := msg.String()
|
|
|
|
// Did the user press enter while the submit or skip button was focused?
|
|
// If so, emit the appropriate command.
|
|
if s == "enter" && m.focusIndex == len(m.inputs) {
|
|
return m, func() tea.Msg { return SelectNextMsg(0) }
|
|
} else if s == "enter" && m.focusIndex == -1 {
|
|
return m, func() tea.Msg { return SelectSkipMsg(0) }
|
|
}
|
|
|
|
// Cycle indexes
|
|
if s == "up" {
|
|
m.focusIndex--
|
|
} else {
|
|
m.focusIndex++
|
|
}
|
|
|
|
if m.focusIndex > len(m.inputs) {
|
|
m.focusIndex = 0
|
|
} else if !m.skipButton && m.focusIndex < 0 {
|
|
m.focusIndex = len(m.inputs)
|
|
} else if m.skipButton && m.focusIndex < -1 {
|
|
m.focusIndex = len(m.inputs)
|
|
}
|
|
|
|
cmds := make([]tea.Cmd, len(m.inputs))
|
|
for i := 0; i < len(m.inputs); i++ {
|
|
if i == m.focusIndex {
|
|
// Set focused state
|
|
cmds[i] = m.focusInput(i)
|
|
continue
|
|
}
|
|
// Remove focused state
|
|
m.unfocusInput(i)
|
|
}
|
|
|
|
return m, tea.Batch(cmds...)
|
|
}
|
|
}
|
|
|
|
// Handle character input and blinking
|
|
cmd := m.updateInputs(msg)
|
|
|
|
return m, cmd
|
|
}
|
|
|
|
func (m *Model) updateInputs(msg tea.Msg) tea.Cmd {
|
|
cmds := make([]tea.Cmd, len(m.inputs))
|
|
|
|
// Only text inputs with Focus() set will respond, so it's safe to simply
|
|
// update all of them here without any further logic.
|
|
for i := range m.inputs {
|
|
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
|
}
|
|
|
|
return tea.Batch(cmds...)
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
var b strings.Builder
|
|
|
|
if m.skipButton {
|
|
button := &blurredSkipButton
|
|
if m.focusIndex == -1 {
|
|
button = &focusedSkipButton
|
|
}
|
|
fmt.Fprintf(&b, "%s\n\n\n", *button)
|
|
}
|
|
|
|
for i := range m.inputs {
|
|
if m.configs[i].Label != "" {
|
|
b.WriteString(m.GetLabel(m.configs[i]))
|
|
}
|
|
|
|
b.WriteString(m.inputs[i].View())
|
|
b.WriteRune('\n')
|
|
if i < len(m.inputs)-1 {
|
|
b.WriteRune('\n')
|
|
}
|
|
}
|
|
|
|
button := &blurredButton
|
|
if m.focusIndex == len(m.inputs) {
|
|
button = &focusedButton
|
|
}
|
|
fmt.Fprintf(&b, "\n\n%s\n\n", *button)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (m Model) GetLabel(c InputConfig) string {
|
|
var label strings.Builder
|
|
|
|
label.WriteString(c.Label)
|
|
if c.Required {
|
|
label.WriteString("*")
|
|
}
|
|
|
|
if len(c.Help) > 0 {
|
|
label.WriteString("\n" + helpStyle.Render(c.Help))
|
|
}
|
|
|
|
label.WriteString("\n")
|
|
return label.String()
|
|
}
|
|
|
|
func (m Model) SetSkip(skip bool) Model {
|
|
m.skipButton = skip
|
|
if m.skipButton {
|
|
if len(m.inputs) > 0 {
|
|
m.unfocusInput(0)
|
|
}
|
|
m.focusIndex = -1
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (m *Model) unfocusInput(index int) {
|
|
m.inputs[index].Blur()
|
|
m.inputs[index].PromptStyle = noStyle
|
|
m.inputs[index].TextStyle = noStyle
|
|
}
|
|
|
|
func (m *Model) focusInput(index int) tea.Cmd {
|
|
m.inputs[index].PromptStyle = focusedStyle
|
|
m.inputs[index].TextStyle = focusedStyle
|
|
return m.inputs[index].Focus()
|
|
}
|