diff --git a/internal/setup/auto_create.go b/internal/setup/auto_create.go new file mode 100644 index 00000000..e8e7e43f --- /dev/null +++ b/internal/setup/auto_create.go @@ -0,0 +1,113 @@ +package setup + +// A simple example that shows how to retrieve a value from a Bubble Tea +// program after the Bubble Tea has exited. + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + choiceStyle = lipgloss.NewStyle().PaddingLeft(4) + selectedChoiceItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + + _ list.Item = choice{} +) + +type choice struct { + Name string `json:"name"` +} + +func (p choice) FilterValue() string { return "" } + +type autoCreateModel struct { + choice string + err error + list list.Model +} + +func NewAutoCreate() autoCreateModel { + choices := []choice{ + { + Name: "Yes", + }, + { + Name: "No", + }, + } + l := list.New(choicesToItems(choices), autoCreateDelegate{}, 85, 14) + l.Title = "Do you want to get started with our recommended project, environment, and flag?" + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) + + return autoCreateModel{ + list: l, + } +} + +func (m autoCreateModel) Init() tea.Cmd { + return nil +} + +func (m autoCreateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, keys.Enter): + i, ok := m.list.SelectedItem().(choice) + if ok { + m.choice = i.Name + } + case key.Matches(msg, keys.Quit): + return m, tea.Quit + default: + m.list, cmd = m.list.Update(msg) + } + } + + return m, cmd +} + +func (m autoCreateModel) View() string { + return "\n" + m.list.View() +} + +type autoCreateDelegate struct{} + +func (d autoCreateDelegate) Height() int { return 1 } +func (d autoCreateDelegate) Spacing() int { return 0 } +func (d autoCreateDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (d autoCreateDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(choice) + if !ok { + return + } + + str := i.Name + + fn := choiceStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return selectedChoiceItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} + +func choicesToItems(choices []choice) []list.Item { + items := make([]list.Item, len(choices)) + for i, c := range choices { + items[i] = list.Item(c) + } + + return items +} diff --git a/internal/setup/environments.go b/internal/setup/environments.go index 6f82ac09..6cb977af 100644 --- a/internal/setup/environments.go +++ b/internal/setup/environments.go @@ -107,8 +107,8 @@ func (d envDelegate) Render(w io.Writer, m list.Model, index int, listItem list. func environmentsToItems(environments []environment) []list.Item { items := make([]list.Item, len(environments)) - for i, proj := range environments { - items[i] = list.Item(proj) + for i, e := range environments { + items[i] = list.Item(e) } return items diff --git a/internal/setup/flags.go b/internal/setup/flags.go index 9101d331..76b79940 100644 --- a/internal/setup/flags.go +++ b/internal/setup/flags.go @@ -31,7 +31,7 @@ type flagModel struct { list list.Model } -func Newflag() tea.Model { +func NewFlag() tea.Model { flags := []flag{ { Key: "flag1", diff --git a/internal/setup/wizard.go b/internal/setup/wizard.go index 7ad23287..5b6a772a 100644 --- a/internal/setup/wizard.go +++ b/internal/setup/wizard.go @@ -12,7 +12,7 @@ type sessionState int // list of steps in the wizard const ( - initialStep sessionState = iota + autoCreateStep sessionState = iota projectsStep environmentsStep flagsStep @@ -21,13 +21,14 @@ const ( // WizardModel is a high level container model that controls the nested models which each // represent a step in the setup wizard. type WizardModel struct { - quitting bool - err error - currStep sessionState - steps []tea.Model - currProjectKey string - currEnvironmentKey string - currFlagKey string + quitting bool + err error + currStep sessionState + steps []tea.Model + useRecommendedResources bool + currProjectKey string + currEnvironmentKey string + currFlagKey string } func NewWizardModel() tea.Model { @@ -35,13 +36,14 @@ func NewWizardModel() tea.Model { // Since there isn't a model for the initial step, the currStep value will always be one ahead of the step in // this slice. It may be convenient to add a model for the initial step to contain its own view logic and to // prevent this off-by-one issue. + NewAutoCreate(), NewProject(), NewEnvironment(), - Newflag(), + NewFlag(), } return WizardModel{ - currStep: initialStep, + currStep: autoCreateStep, steps: steps, } } @@ -58,38 +60,51 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, keys.Enter): switch m.currStep { - case initialStep: - projModel, _ := m.steps[m.currStep].Update(fetchProjects{}) - // we need to cast this to get the data out of it, but maybe we can create our own interface with - // common values such as Choice() and Err() so we don't have to cast - p, ok := projModel.(projectModel) + case autoCreateStep: + model, _ := m.steps[autoCreateStep].Update(msg) + p, ok := model.(autoCreateModel) if ok { - if p.err != nil { - m.err = p.err - return m, nil + m.useRecommendedResources = p.choice == "Yes" + if m.useRecommendedResources { + // create project, environment, and flag + // go to step after flagsStep + m.currProjectKey = "setup-wizard-project" + m.currEnvironmentKey = "test" + m.currFlagKey = "setup-wizard-flag" + m.currStep = flagsStep + 1 + } else { + projModel, _ := m.steps[projectsStep].Update(fetchProjects{}) + // we need to cast this to get the data out of it, but maybe we can create our own interface with + // common values such as Choice() and Err() so we don't have to cast + p, ok := projModel.(projectModel) + if ok { + if p.err != nil { + m.err = p.err + return m, nil + } + } + // update projModel with the fetched projects + m.steps[projectsStep] = projModel + // go to the next step + m.currStep += 1 } } - - // update the nested model - m.steps[m.currStep] = projModel - // go to the next step - m.currStep += 1 case projectsStep: - projModel, _ := m.steps[m.currStep-1].Update(msg) + projModel, _ := m.steps[projectsStep].Update(msg) p, ok := projModel.(projectModel) if ok { m.currProjectKey = p.choice m.currStep += 1 } case environmentsStep: - envModel, _ := m.steps[m.currStep-1].Update(msg) + envModel, _ := m.steps[environmentsStep].Update(msg) p, ok := envModel.(environmentModel) if ok { m.currEnvironmentKey = p.choice m.currStep += 1 } case flagsStep: - model, _ := m.steps[m.currStep-1].Update(msg) + model, _ := m.steps[flagsStep].Update(msg) f, ok := model.(flagModel) if ok { m.currFlagKey = f.choice @@ -100,15 +115,15 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case key.Matches(msg, keys.Back): // only go back if not on the first step - if m.currStep > initialStep { + if m.currStep > autoCreateStep { m.currStep -= 1 } case key.Matches(msg, keys.Quit): m.quitting = true return m, tea.Quit default: - updatedModel, _ := m.steps[m.currStep-1].Update(msg) - m.steps[m.currStep-1] = updatedModel + updatedModel, _ := m.steps[m.currStep].Update(msg) + m.steps[m.currStep] = updatedModel } } @@ -124,15 +139,11 @@ func (m WizardModel) View() string { return fmt.Sprintf("ERROR: %s", m.err) } - if m.currStep == initialStep { - return "welcome" - } - if m.currStep > flagsStep { return fmt.Sprintf("envKey is %s, projKey is %s, flagKey is %s", m.currEnvironmentKey, m.currProjectKey, m.currFlagKey) } - return fmt.Sprintf("\nstep %d of %d\n"+m.steps[m.currStep-1].View(), m.currStep, len(m.steps)) + return fmt.Sprintf("\nstep %d of %d\n"+m.steps[m.currStep].View(), m.currStep+1, len(m.steps)) } type keyMap struct {