Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auto create step in UI #10

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to just directly use projectsStep here. Since the case is already ensuring we're on projectsStep. Same for the other cases

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))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got a little confused here. But basically we're rendering the current step's view. While showing what step we're on in the header out of the total number of steps.
Was able to use currStep instead of currStep-1 since I got rid of the initial step.

}

type keyMap struct {
Expand Down
Loading