Skip to content

Commit

Permalink
Merge pull request #1527 from onflow/chasefleming/update-text-input
Browse files Browse the repository at this point in the history
Add generic text input prompt and refactor
  • Loading branch information
chasefleming authored Apr 17, 2024
2 parents 1b8d2be + 6fe0278 commit 56d0d3e
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 31 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/onflow/flow-cli
go 1.20

require (
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/dukex/mixpanel v1.0.1
github.com/getsentry/sentry-go v0.27.0
Expand Down Expand Up @@ -50,6 +51,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.10.0 // indirect
Expand All @@ -59,6 +61,7 @@ require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
Expand Down Expand Up @@ -179,8 +181,12 @@ github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down
53 changes: 35 additions & 18 deletions internal/prompt/select-options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,28 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

// OptionSelectModel represents the prompt state
type OptionSelectModel struct {
// optionSelectModel represents the prompt state but is now private
type optionSelectModel struct {
message string // message to display
cursor int // position of the cursor
Choices []string // items on the list
Selected map[int]struct{} // which items are selected
choices []string // items on the list
selected map[int]struct{} // which items are selected
}

// SelectOptions creates a prompt for selecting multiple options
func SelectOptions(options []string, message string) OptionSelectModel {
return OptionSelectModel{
// selectOptions creates a prompt for selecting multiple options but is now private
func selectOptions(options []string, message string) optionSelectModel {
return optionSelectModel{
message: message,
Choices: options,
Selected: make(map[int]struct{}),
choices: options,
selected: make(map[int]struct{}),
}
}

func (m OptionSelectModel) Init() tea.Cmd {
func (m optionSelectModel) Init() tea.Cmd {
return nil // No initial command
}

func (m OptionSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m optionSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
Expand All @@ -59,16 +59,16 @@ func (m OptionSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

case tea.KeyDown: // Navigate down
if m.cursor < len(m.Choices)-1 {
if m.cursor < len(m.choices)-1 {
m.cursor++
}

case tea.KeySpace: // Select an item
// Toggle selection
if _, ok := m.Selected[m.cursor]; ok {
delete(m.Selected, m.cursor) // Deselect
if _, ok := m.selected[m.cursor]; ok {
delete(m.selected, m.cursor) // Deselect
} else {
m.Selected[m.cursor] = struct{}{} // Select
m.selected[m.cursor] = struct{}{} // Select
}

case tea.KeyEnter: // Confirm selection
Expand All @@ -79,18 +79,18 @@ func (m OptionSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}

func (m OptionSelectModel) View() string {
func (m optionSelectModel) View() string {
var b strings.Builder
b.WriteString(fmt.Sprintf("%s.\n", m.message))
b.WriteString("Use arrow keys to navigate, space to select, enter to confirm, q to quit:\n\n")
for i, choice := range m.Choices {
for i, choice := range m.choices {
if m.cursor == i {
b.WriteString("> ")
} else {
b.WriteString(" ")
}
// Mark selected items
if _, ok := m.Selected[i]; ok {
if _, ok := m.selected[i]; ok {
b.WriteString("[x] ")
} else {
b.WriteString("[ ] ")
Expand All @@ -99,3 +99,20 @@ func (m OptionSelectModel) View() string {
}
return b.String()
}

// RunSelectOptions remains public and is the interface for external usage.
func RunSelectOptions(options []string, message string) ([]string, error) {
model := selectOptions(options, message)
p := tea.NewProgram(model)
finalModel, err := p.Run()
if err != nil {
return nil, err
}

final := finalModel.(optionSelectModel)
selectedChoices := make([]string, 0)
for i := range final.selected {
selectedChoices = append(selectedChoices, final.choices[i])
}
return selectedChoices, nil
}
83 changes: 83 additions & 0 deletions internal/prompt/text-input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Flow CLI
*
* Copyright 2019 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package prompt

import (
"fmt"

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

// textInputModel is now private, only accessible within the 'prompt' package.
type textInputModel struct {
textInput textinput.Model
err error
customMsg string
}

// newTextInput is a private function that initializes a new text input model.
func newTextInput(customMsg, placeholder string) textInputModel {
ti := textinput.New()
ti.Placeholder = placeholder
ti.Focus()
ti.CharLimit = 256
ti.Width = 30

return textInputModel{
textInput: ti,
customMsg: customMsg,
}
}

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

func (m textInputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
var cmd tea.Cmd
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}

return m, nil
}

func (m textInputModel) View() string {
return fmt.Sprintf("%s\n\n%s\n\n%s", m.customMsg, m.textInput.View(), "(Enter to submit, Esc to quit)")
}

// RunTextInput remains public. It's the entry point for external usage.
func RunTextInput(customMsg, placeholder string) (string, error) {
model := newTextInput(customMsg, placeholder)
p := tea.NewProgram(model)

if finalModel, err := p.Run(); err != nil {
return "", err
} else {
final := finalModel.(textInputModel)
return final.textInput.Value(), nil
}
}
28 changes: 15 additions & 13 deletions internal/super/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import (
"os"
"path/filepath"

flowsdk "github.com/onflow/flow-go-sdk"
"golang.org/x/exp/slices"

"github.com/onflow/flow-cli/internal/prompt"

tea "github.com/charmbracelet/bubbletea"
flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowGo "github.com/onflow/flow-go/model/flow"
flowkitConfig "github.com/onflow/flowkit/config"
Expand Down Expand Up @@ -98,8 +99,13 @@ func create(
} else {
// Ask for project name if not given
if len(args) < 1 {
name := prompt.NamePrompt()
targetDir, err = getTargetDirectory(name)
userInput, err := prompt.RunTextInput("Enter the name of your project", "Type your project name here...")
if err != nil {
fmt.Printf("Error running project name: %v\n", err)
os.Exit(1)
}

targetDir, err = getTargetDirectory(userInput)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -147,29 +153,25 @@ func create(

// Prompt to ask which core contracts should be installed
sc := systemcontracts.SystemContractsForChain(flowGo.Mainnet)
promptMessage := "Select the core contracts you'd like to install"
promptMessage := "Select the core contracts you'd like to install:"

contractNames := make([]string, 0)

for _, contract := range sc.All() {
contractNames = append(contractNames, contract.Name)
}

m := prompt.SelectOptions(contractNames, promptMessage)
finalModel, err := tea.NewProgram(m).Run()

selectedContractNames, err := prompt.RunSelectOptions(contractNames, promptMessage)
if err != nil {
fmt.Printf("Error running program: %v\n", err)
fmt.Printf("Error running dependency selection: %v\n", err)
os.Exit(1)
}

final := finalModel.(prompt.OptionSelectModel)

var dependencies []flowkitConfig.Dependency

// Loop standard contracts and add them to the dependencies if selected
for i, contract := range sc.All() {
if _, ok := final.Selected[i]; ok {
for _, contract := range sc.All() {
if slices.Contains(selectedContractNames, contract.Name) {
dependencies = append(dependencies, flowkitConfig.Dependency{
Name: contract.Name,
Source: flowkitConfig.Source{
Expand Down

0 comments on commit 56d0d3e

Please sign in to comment.