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 new input model, use in projects step #30

Merged
merged 8 commits into from
Mar 18, 2024
81 changes: 60 additions & 21 deletions internal/setup/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ type project struct {
func (p project) FilterValue() string { return "" }

type projectModel struct {
choice string
err error
list list.Model
choice string
err error
list list.Model
showInput bool
textInput tea.Model
}

func NewProject() tea.Model {
Expand All @@ -48,8 +50,31 @@ func (p projectModel) Init() tea.Cmd {

func (m projectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
// if we've selected the option to create a new project, delegate to the textInput model
if m.showInput {
m.textInput, cmd = m.textInput.Update(msg)

// catch the enter key here to update the projectModel when a final value is provided
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):
iModel, ok := m.textInput.(inputModel)
if ok {
m.choice = iModel.textInput.Value()
m.showInput = false
}

// TODO: send request to create project, hardcoding for now
projects = append(projects, project{Key: m.choice, Name: m.choice})
}
default:

}
return m, cmd
}
switch msg := msg.(type) {
case fetchProjects:
case fetchResources:
projects, err := getProjects()
if err != nil {
m.err = err
Expand All @@ -59,9 +84,16 @@ func (m projectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Enter):

i, ok := m.list.SelectedItem().(project)
if ok {
m.choice = i.Key
if i.Key == "create-new-project" {
iModel := newTextInputModel("desired-proj-key", "Enter project name")
m.textInput = iModel
m.showInput = true
} else {
m.choice = i.Key
}
}
case key.Matches(msg, keys.Quit):
return m, tea.Quit
Expand All @@ -74,6 +106,10 @@ func (m projectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

func (m projectModel) View() string {
if m.showInput {
return m.textInput.View()
}

return "\n" + m.list.View()
}

Expand Down Expand Up @@ -110,27 +146,30 @@ func projectsToItems(projects []project) []list.Item {
return items
}

type fetchProjects struct{}

// type projectsResponse struct {
// Items []project `json:"items"`
// }

var projects = []project{
{
Key: "proj1",
Name: "project 1",
},
{
Key: "proj2",
Name: "project 2",
},
{
Key: "proj3",
Name: "project 3",
},
}

func getProjects() ([]project, error) {
return []project{
{
Key: "proj1",
Name: "project 1",
},
{
Key: "proj2",
Name: "project 2",
},
{
Key: "proj3",
Name: "project 3",
},
}, nil
projectList := projects
createNewOption := project{Key: "create-new-project", Name: "Create a new project"}
projectList = append(projectList, createNewOption)
return projectList, nil

// uncomment out below to fetch projects locally after adding an access token to the
// Authorization header
Expand Down
62 changes: 62 additions & 0 deletions internal/setup/text_input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package setup

import (
"fmt"

"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)

type inputModel struct {
textInput textinput.Model
done bool
title string
err error
}

func newTextInputModel(placeholder, title string) inputModel {
ti := textinput.New()
ti.Placeholder = placeholder
ti.Focus()
ti.CharLimit = 156
ti.Width = 20

return inputModel{
title: title,
textInput: ti,
err: nil,
}
}

func (m inputModel) Init() tea.Cmd {
return textinput.Blink
}

func (m inputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter:
m.done = true
return m, nil
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}

// TODO: Handle errors
}

m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}

func (m inputModel) View() string {
return fmt.Sprintf(
"%s\n\n%s\n\n%s",
m.title,
m.textInput.View(),
"(esc to quit)",
) + "\n"
}
29 changes: 15 additions & 14 deletions internal/setup/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
// TODO: we may want to rename this for clarity
type sessionState int

// generic message type to pass into each models' Update method when we want to perform a new GET request
type fetchResources struct{}

// list of steps in the wizard
const (
autoCreateStep sessionState = iota
Expand Down Expand Up @@ -82,28 +85,24 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
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
Comment on lines -86 to -97
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 don't think we actually needed to do this casting here

// pre-load projects
m.steps[projectsStep], _ = m.steps[projectsStep].Update(fetchResources{})
m.currStep += 1
}
}
case projectsStep:
projModel, _ := m.steps[projectsStep].Update(msg)
// 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
Comment on lines +95 to +96
Copy link
Contributor Author

Choose a reason for hiding this comment

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

but we do need to do it here so i moved that comment down

p, ok := projModel.(projectModel)
if ok {
m.currProjectKey = p.choice
m.currStep += 1
// update projModel with new input model
m.steps[projectsStep] = p
// only progress if we don't want to show input
if !p.showInput {
m.currStep += 1
}
}
case environmentsStep:
envModel, _ := m.steps[environmentsStep].Update(msg)
Expand Down Expand Up @@ -137,6 +136,8 @@ func (m WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// only go back if not on the first step
if m.currStep > autoCreateStep {
// fetch resources for the previous step again in case we created new ones
m.steps[m.currStep-1], _ = m.steps[m.currStep-1].Update(fetchResources{})
m.currStep -= 1
}
case key.Matches(msg, keys.Quit):
Expand Down
Loading