Skip to content

Commit

Permalink
feat: Add auto create step in UI (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sunny Guduru authored Mar 13, 2024
1 parent 2ab3668 commit fb2fb5c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 37 deletions.
113 changes: 113 additions & 0 deletions internal/setup/auto_create.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions internal/setup/environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/setup/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type flagModel struct {
list list.Model
}

func Newflag() tea.Model {
func NewFlag() tea.Model {
flags := []flag{
{
Key: "flag1",
Expand Down
79 changes: 45 additions & 34 deletions internal/setup/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type sessionState int

// list of steps in the wizard
const (
initialStep sessionState = iota
autoCreateStep sessionState = iota
projectsStep
environmentsStep
flagsStep
Expand All @@ -21,27 +21,29 @@ 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 {
steps := []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,
}
}
Expand All @@ -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
Expand All @@ -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
}
}

Expand All @@ -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 {
Expand Down

0 comments on commit fb2fb5c

Please sign in to comment.