From 1b9f805d9ba3a757efef0ca851540df77eba89e0 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Thu, 9 May 2024 19:26:28 -0400 Subject: [PATCH] feat: better loading --- examples/dynamic/dynamic-country/main.go | 18 ++++++++++++++++++ field_select.go | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/examples/dynamic/dynamic-country/main.go b/examples/dynamic/dynamic-country/main.go index be5d7fc3..cad19cfd 100644 --- a/examples/dynamic/dynamic-country/main.go +++ b/examples/dynamic/dynamic-country/main.go @@ -44,6 +44,24 @@ func main() { time.Sleep(1000 * time.Millisecond) return huh.NewOptions(s...) }, &country /* only this function when `country` changes */), + huh.NewSelect[string](). + Height(8). + TitleFunc(func() string { + switch country { + case "United States": + return "State" + case "Canada": + return "Province" + default: + return "Territory" + } + }, &country). + OptionsFunc(func() []huh.Option[string] { + s := states[country] + // simulate API call + time.Sleep(1000 * time.Millisecond) + return huh.NewOptions(s...) + }, &country /* only this function when `country` changes */), huh.NewNote(). TitleFunc(func() string { return fmt.Sprintf("You selected: %s", country) diff --git a/field_select.go b/field_select.go index a156a598..96f8990e 100644 --- a/field_select.go +++ b/field_select.go @@ -4,8 +4,10 @@ import ( "fmt" "strings" "sync" + "time" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -40,6 +42,7 @@ type Select[T comparable] struct { focused bool filtering bool filter textinput.Model + spinner spinner.Model // options inline bool @@ -54,6 +57,8 @@ func NewSelect[T comparable]() *Select[T] { filter := textinput.New() filter.Prompt = "/" + s := spinner.New(spinner.WithSpinner(spinner.Line)) + return &Select[T]{ value: new(T), validate: func(T) error { return nil }, @@ -63,6 +68,7 @@ func NewSelect[T comparable]() *Select[T] { options: Eval[[]Option[T]]{cache: make(map[uint64][]Option[T])}, title: Eval[string]{cache: make(map[uint64]string)}, description: Eval[string]{cache: make(map[uint64]string)}, + spinner: s, } } @@ -292,12 +298,21 @@ func (s *Select[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { s.selected = clamp(s.selected, 0, len(s.options.val)-1) } else { s.options.loading = true + s.options.loadingStart = time.Now() cmds = append(cmds, func() tea.Msg { return updateOptionsMsg[T]{id: s.id, hash: hash, options: s.options.fn()} - }) + }, s.spinner.Tick) } } return s, tea.Batch(cmds...) + + case spinner.TickMsg: + if !s.options.loading { + break + } + s.spinner, cmd = s.spinner.Update(msg) + return s, cmd + case updateTitleMsg: if msg.id == s.id && msg.hash == s.title.bindingsHash { s.title.update(msg.title) @@ -481,6 +496,12 @@ func (s *Select[T]) optionsView() string { sb strings.Builder ) + if s.options.loading && time.Since(s.options.loadingStart) > spinnerShowThreshold { + s.spinner.Style = s.activeStyles().MultiSelectSelector.UnsetString() + sb.WriteString(s.spinner.View() + " Loading...") + return sb.String() + } + if s.inline { sb.WriteString(styles.PrevIndicator.Faint(s.selected <= 0).String()) if len(s.filteredOptions) > 0 {