From 1059ddc33f98021ab0db0f5311bdad262a7082a8 Mon Sep 17 00:00:00 2001 From: Leon Yang Date: Fri, 17 Feb 2023 17:09:43 +0800 Subject: [PATCH] Enable pagination and filter (#92) - Enable pagination and filter on the cluster, project, and import selections. - Use fmt.Sprintf() to fix panic. --- internal/service/cloud/logic.go | 18 +++++-- internal/ui/select_model.go | 90 +++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/internal/service/cloud/logic.go b/internal/service/cloud/logic.go index 5014e5c5..65533f31 100644 --- a/internal/service/cloud/logic.go +++ b/internal/service/cloud/logic.go @@ -85,6 +85,10 @@ func GetSelectedProject(pageSize int64, client TiDBCloudClient) (*Project, error if err != nil { return nil, err } + itemsPerPage := 6 + model.EnablePagination(itemsPerPage) + model.EnableFilter() + p := tea.NewProgram(model) projectModel, err := p.StartReturningModel() if err != nil { @@ -93,7 +97,7 @@ func GetSelectedProject(pageSize int64, client TiDBCloudClient) (*Project, error if m, _ := projectModel.(ui.SelectModel); m.Interrupted { os.Exit(130) } - res := projectModel.(ui.SelectModel).Choices[projectModel.(ui.SelectModel).Selected].(*Project) + res := projectModel.(ui.SelectModel).GetSelectedItem().(*Project) return res, nil } @@ -118,6 +122,10 @@ func GetSelectedCluster(projectID string, pageSize int64, client TiDBCloudClient if err != nil { return nil, errors.Trace(err) } + itemsPerPage := 6 + model.EnablePagination(itemsPerPage) + model.EnableFilter() + p := tea.NewProgram(model) clusterModel, err := p.StartReturningModel() if err != nil { @@ -126,7 +134,7 @@ func GetSelectedCluster(projectID string, pageSize int64, client TiDBCloudClient if m, _ := clusterModel.(ui.SelectModel); m.Interrupted { os.Exit(130) } - cluster := clusterModel.(ui.SelectModel).Choices[clusterModel.(ui.SelectModel).Selected].(*Cluster) + cluster := clusterModel.(ui.SelectModel).GetSelectedItem().(*Cluster) return cluster, nil } @@ -153,6 +161,10 @@ func GetSelectedImport(pID string, cID string, pageSize int64, client TiDBCloudC if err != nil { return nil, err } + itemsPerPage := 6 + model.EnablePagination(itemsPerPage) + model.EnableFilter() + p := tea.NewProgram(model) importModel, err := p.StartReturningModel() if err != nil { @@ -161,7 +173,7 @@ func GetSelectedImport(pID string, cID string, pageSize int64, client TiDBCloudC if m, _ := importModel.(ui.SelectModel); m.Interrupted { os.Exit(130) } - res := importModel.(ui.SelectModel).Choices[importModel.(ui.SelectModel).Selected].(*Import) + res := importModel.(ui.SelectModel).GetSelectedItem().(*Import) return res, nil } diff --git a/internal/ui/select_model.go b/internal/ui/select_model.go index 4c5e26bc..1f6b6ced 100644 --- a/internal/ui/select_model.go +++ b/internal/ui/select_model.go @@ -17,6 +17,8 @@ package ui import ( "fmt" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/paginator" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" @@ -33,9 +35,9 @@ var ( type SelectModel struct { Hint string - Choices []interface{} // items on the to-do list - cursor int // which to-do list item our cursor is pointing at - Selected int // which to-do items are Selected + Choices []interface{} // items to be selected, which need implement fmt.Stringer interface + cursor int // which item our cursor is pointing at + Selected int // which items are Selected Interrupted bool showPagination bool showFilter bool @@ -44,6 +46,8 @@ type SelectModel struct { // VisibleChoices is part of Choices and is filtered by user input. VisibleChoices []interface{} + helper help.Model + keys keyMap Paginator paginator.Model FilterInput textinput.Model @@ -52,6 +56,57 @@ type SelectModel struct { ChoiceValue func(choice interface{}) string } +type keyMap struct { + Up key.Binding + Down key.Binding + Left key.Binding + Right key.Binding + Select key.Binding + Quit key.Binding +} + +// ShortHelp returns keybindings to be shown in the mini help view. It's part +// of the key.Map interface. +func (k keyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Left, k.Right} +} + +// FullHelp returns keybindings for the expanded help view. It's part of the +// key.Map interface. +func (k keyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Up, k.Down, k.Left, k.Right}, // first column + {k.Select, k.Quit}, // second column + } +} + +var keys = keyMap{ + Up: key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "move up"), + ), + Down: key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "move down"), + ), + Left: key.NewBinding( + key.WithKeys("left", "pgup"), + key.WithHelp("←/pgup", "prev page"), + ), + Right: key.NewBinding( + key.WithKeys("right", "pgdown"), + key.WithHelp("→/pgdown", "next page"), + ), + Select: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select"), + ), + Quit: key.NewBinding( + key.WithKeys("esc", "ctrl+c"), + key.WithHelp("esc", "quit"), + ), +} + func InitialSelectModel(choices []interface{}, hint string) (*SelectModel, error) { if len(choices) == 0 { return nil, errors.New("There are no available choices") @@ -78,6 +133,8 @@ func InitialSelectModel(choices []interface{}, hint string) (*SelectModel, error FilterInput: f, Paginator: p, ChoiceValue: df, + keys: keys, + helper: help.New(), }, nil } @@ -99,7 +156,7 @@ func buildFilterInput() textinput.Model { func defaultChoicesValueFunc() func(choice interface{}) string { return func(choice interface{}) string { - return choice.(string) + return fmt.Sprintf("%s", choice) } } @@ -115,34 +172,34 @@ func (m SelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: // Cool, what was the actual key pressed? - switch msg.String() { + switch { // These keys should exit the program. - case "ctrl+c", "esc": + case key.Matches(msg, m.keys.Quit): m.Interrupted = true return m, tea.Quit // The "up" and "k" keys move the cursor up - case "up", "k": + case key.Matches(msg, m.keys.Up): if m.cursor > 0 { m.cursor-- } // The "down" and "j" keys move the cursor down - case "down", "j": + case key.Matches(msg, m.keys.Down): if m.cursor < m.ItemsOnPage()-1 { m.cursor++ } - // The "pgup" and "left" keys skip to the pre page - case "pgup", "left": + // The "h" and "left" keys skip to the pre page + case key.Matches(msg, m.keys.Left): if m.showPagination { m.Paginator.PrevPage() m.ResetCursor() } - // The "pgdown" and "right" keys skip to the next page - case "pgdown", "right": + // The "j" and "right" keys skip to the next page + case key.Matches(msg, m.keys.Right): if m.showPagination { m.Paginator.NextPage() m.ResetCursor() @@ -150,7 +207,7 @@ func (m SelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // The "enter" key and the spacebar (a literal space) toggle // the selected state for the item that the cursor is pointing at. - case "enter": + case key.Matches(msg, m.keys.Select): m.Selected = m.Index() return m, tea.Quit } @@ -190,7 +247,6 @@ func (m SelectModel) View() string { var start, end int if m.showPagination { start, end = m.Paginator.GetSliceBounds(len(m.VisibleChoices)) - } else { start = 0 end = len(m.VisibleChoices) @@ -211,7 +267,11 @@ func (m SelectModel) View() string { // Show paginator dot if m.showPagination { - s += m.Paginator.View() + // Only show when there are more than one page + if m.Paginator.TotalPages > 1 { + s += m.Paginator.View() + "\n" + s += m.helper.View(m.keys) + } } // Send the UI for rendering