diff --git a/internal/setup/projects.go b/internal/setup/projects.go index 41ba8ab7..87207d8b 100644 --- a/internal/setup/projects.go +++ b/internal/setup/projects.go @@ -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 { @@ -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 @@ -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 @@ -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() } @@ -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 diff --git a/internal/setup/text_input.go b/internal/setup/text_input.go new file mode 100644 index 00000000..a6c90073 --- /dev/null +++ b/internal/setup/text_input.go @@ -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" +} diff --git a/internal/setup/wizard.go b/internal/setup/wizard.go index ad289207..405b6459 100644 --- a/internal/setup/wizard.go +++ b/internal/setup/wizard.go @@ -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 @@ -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 + // 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 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) @@ -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):