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 15 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
116 changes: 116 additions & 0 deletions internal/setup/autoCreate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package setup
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename the file auto_create.go to be more idiomatic?


// 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 {
Key string `json:"key"`
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{
{
Key: "yes",
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably only need the name here and the check later on could be for "Yes" instead of "yes".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works for me

Name: "Yes",
},
{
Key: "no",
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.Key
}
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 {
sunnyguduru marked this conversation as resolved.
Show resolved Hide resolved
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
sunnyguduru marked this conversation as resolved.
Show resolved Hide resolved
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 {
sunnyguduru marked this conversation as resolved.
Show resolved Hide resolved
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
} else {
// 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
}
}

// 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