From 5d213c4ee80d4aba1a5dae57b1c314ac46188576 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 27 Mar 2022 15:53:58 +1100 Subject: [PATCH] Have got a modal table-selection list working Also tracked down what was causing major pauses when creating new tables. It was due to querying whether terminal is light or not. So making a call to get that info on launch. --- cmd/dynamo-browse/main.go | 14 +++-- .../ui/teamodels/layout/model.go | 60 +++++++++++++++++++ .../dynamo-browse/ui/teamodels/modal/model.go | 31 +++++++--- .../ui/teamodels/tableselect/events.go | 15 +++++ .../ui/teamodels/tableselect/items.go | 27 +++++++++ .../ui/teamodels/tableselect/list.go | 55 +++++++++++++++++ .../ui/teamodels/tableselect/model.go | 56 +++++++++++++++++ 7 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 internal/dynamo-browse/ui/teamodels/tableselect/events.go create mode 100644 internal/dynamo-browse/ui/teamodels/tableselect/items.go create mode 100644 internal/dynamo-browse/ui/teamodels/tableselect/list.go create mode 100644 internal/dynamo-browse/ui/teamodels/tableselect/model.go diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index c2b2385..dd06a8c 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/common/ui/dispatcher" "github.com/lmika/awstools/internal/common/ui/uimodels" @@ -19,6 +20,7 @@ import ( "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt" + "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/tableselect" "github.com/lmika/gopkgs/cli" "log" "os" @@ -65,13 +67,17 @@ func main() { _ = uiModel // END TEMP - model := layout.FullScreen(statusandprompt.New( + var model tea.Model = statusandprompt.New( layout.NewVBox( frame.NewFrame("This is the header", true, layout.Model(newTestModel("this is the top"))), frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))), ), "Hello world", - )) + ) + model = layout.FullScreen(tableselect.New(model)) + + // Pre-determine if layout has dark background. This prevents calls for creating a list to hang. + lipgloss.HasDarkBackground() //frameSet := frameset.New([]frameset.Frame{ // { @@ -129,8 +135,8 @@ func newTestModel(descr string) tea.Model { OnKeyPressed: func(k string) tea.Cmd { log.Println("got key press: " + k) if k == "enter" { - return statusandprompt.Prompt("What is your car? ", func(val string) tea.Cmd { - return statusandprompt.SetStatus("Your car is = " + val) + return tableselect.ShowTableSelect(func(n string) tea.Cmd { + return statusandprompt.SetStatus("New table = " + n) }) } else if k == "k" { return modal.PopMode diff --git a/internal/dynamo-browse/ui/teamodels/layout/model.go b/internal/dynamo-browse/ui/teamodels/layout/model.go index a958a47..43fdd87 100644 --- a/internal/dynamo-browse/ui/teamodels/layout/model.go +++ b/internal/dynamo-browse/ui/teamodels/layout/model.go @@ -15,6 +15,15 @@ type ResizingModel interface { Resize(w, h int) ResizingModel } +// Resize sends a resize message to the passed in model. If m implements ResizingModel, then Resize is called; +// otherwise, m is returned without any messages. +func Resize(m tea.Model, w, h int) tea.Model { + if rm, isRm := m.(ResizingModel); isRm { + return rm.Resize(w, h) + } + return m +} + // Model takes a tea-model and displays it as a resizing model. The model will be // displayed with all the available space provided func Model(m tea.Model) ResizingModel { @@ -46,3 +55,54 @@ func (t teaModel) Resize(w, h int) ResizingModel { t.w, t.h = w, h return t } + +type ResizableModelHandler struct { + new func(w, h int) tea.Model + resize func(m tea.Model, w, h int) tea.Model + model tea.Model +} + +// NewResizableModelHandler takes a tea model that requires a with and height during construction +// and has a resize method, and wraps it as a resizing model. +func NewResizableModelHandler(newModel func(w, h int) tea.Model) ResizableModelHandler { + return ResizableModelHandler{ + new: newModel, + } +} + +func (rmh ResizableModelHandler) WithResize(resizeFn func(m tea.Model, w, h int) tea.Model) ResizableModelHandler { + rmh.resize = resizeFn + return rmh +} + +func (rmh ResizableModelHandler) Resize(w, h int) ResizingModel { + if rmh.model == nil { + rmh.model = rmh.new(w, h) + // TODO: handle init + } else if rmh.resize != nil { + rmh.model = rmh.resize(rmh.model, w, h) + } + return rmh +} + +func (rmh ResizableModelHandler) Init() tea.Cmd { + return nil +} + +func (rmh ResizableModelHandler) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if rmh.model == nil { + return rmh, nil + } + + newModel, cmd := rmh.model.Update(msg) + rmh.model = newModel + return rmh, cmd +} + +func (rmh ResizableModelHandler) View() string { + if rmh.model == nil { + return "" + } + + return rmh.model.View() +} diff --git a/internal/dynamo-browse/ui/teamodels/modal/model.go b/internal/dynamo-browse/ui/teamodels/modal/model.go index 243aaf7..20484d2 100644 --- a/internal/dynamo-browse/ui/teamodels/modal/model.go +++ b/internal/dynamo-browse/ui/teamodels/modal/model.go @@ -2,6 +2,7 @@ package modal import ( tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils" "log" ) @@ -18,30 +19,34 @@ func New(baseMode tea.Model) Modal { } func (m Modal) Init() tea.Cmd { - return nil + return m.baseMode.Init() } -func (m *Modal) pushMode(model tea.Model) { +// Push pushes a new model onto the modal stack +func (m *Modal) Push(model tea.Model) { m.modeStack = append(m.modeStack, model) log.Printf("pusing new mode: len = %v", len(m.modeStack)) } -func (m *Modal) popMode() { +// Pop pops a model from the stack +func (m *Modal) Pop() (p tea.Model) { if len(m.modeStack) > 0 { + p = m.modeStack[len(m.modeStack)-1] m.modeStack = m.modeStack[:len(m.modeStack)-1] + return p } + return nil +} + +// Len returns the number of models on the mode stack +func (m Modal) Len() int { + return len(m.modeStack) } func (m Modal) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cc utils.CmdCollector switch msg := msg.(type) { - case newModePushed: - m.pushMode(msg) - return m, nil - case modePopped: - m.popMode() - return m, nil case tea.KeyMsg, tea.MouseMsg: // only notify top level stack if len(m.modeStack) > 0 { @@ -69,3 +74,11 @@ func (m Modal) View() string { } return m.baseMode.View() } + +func (m Modal) Resize(w, h int) layout.ResizingModel { + m.baseMode = layout.Resize(m.baseMode, w, h) + for i := range m.modeStack { + m.modeStack[i] = layout.Resize(m.modeStack[i], w, h) + } + return m +} diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/events.go b/internal/dynamo-browse/ui/teamodels/tableselect/events.go new file mode 100644 index 0000000..dade419 --- /dev/null +++ b/internal/dynamo-browse/ui/teamodels/tableselect/events.go @@ -0,0 +1,15 @@ +package tableselect + +import tea "github.com/charmbracelet/bubbletea" + +func ShowTableSelect(onSelected func(n string) tea.Cmd) tea.Cmd { + return func() tea.Msg { + return showTableSelectMsg{ + onSelected: onSelected, + } + } +} + +type showTableSelectMsg struct { + onSelected func(n string) tea.Cmd +} diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/items.go b/internal/dynamo-browse/ui/teamodels/tableselect/items.go new file mode 100644 index 0000000..36d76de --- /dev/null +++ b/internal/dynamo-browse/ui/teamodels/tableselect/items.go @@ -0,0 +1,27 @@ +package tableselect + +import "github.com/charmbracelet/bubbles/list" + +type tableItem struct { + name string +} + +func (ti tableItem) FilterValue() string { + return "" +} + +func (ti tableItem) Title() string { + return ti.name +} + +func (ti tableItem) Description() string { + return "abc" +} + +func toListItems[T list.Item](xs []T) []list.Item { + ls := make([]list.Item, len(xs)) + for i, x := range xs { + ls[i] = x + } + return ls +} diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/list.go b/internal/dynamo-browse/ui/teamodels/tableselect/list.go new file mode 100644 index 0000000..167ee1e --- /dev/null +++ b/internal/dynamo-browse/ui/teamodels/tableselect/list.go @@ -0,0 +1,55 @@ +package tableselect + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" +) + +var ( + titleStyle = lipgloss.NewStyle().MarginLeft(2) + itemStyle = lipgloss.NewStyle().PaddingLeft(4) + selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) + helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) + quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) +) + +type listController struct { + list list.Model +} + +func newListController(w, h int) listController { + tableItems := []tableItem{ + {name: "alpha"}, + {name: "beta"}, + {name: "gamma"}, + } + + items := toListItems(tableItems) + + delegate := list.NewDefaultDelegate() + delegate.ShowDescription = false + + return listController{list.New(items, delegate, w, h)} +} + +func (l listController) Init() tea.Cmd { + return nil +} + +func (l listController) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + newList, cmd := l.list.Update(msg) + l.list = newList + return l, cmd +} + +func (l listController) View() string { + return l.list.View() +} + +func (l listController) Resize(w, h int) layout.ResizingModel { + l.list.SetSize(w, h) + return l +} diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/model.go b/internal/dynamo-browse/ui/teamodels/tableselect/model.go new file mode 100644 index 0000000..481ee41 --- /dev/null +++ b/internal/dynamo-browse/ui/teamodels/tableselect/model.go @@ -0,0 +1,56 @@ +package tableselect + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" +) + +type Model struct { + pendingSelection *showTableSelectMsg + modal modal.Modal + w, h int +} + +func New(submodel tea.Model) Model { + return Model{modal: modal.New(submodel)} +} + +func (m Model) Init() tea.Cmd { + return m.modal.Init() +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case showTableSelectMsg: + m.pendingSelection = &msg + m.modal.Push(newListController(m.w, m.h)) + return m, nil + case tea.KeyMsg: + if m.modal.Len() > 0 { + switch msg.String() { + case "enter": + listController := m.modal.Pop().(listController) + + var sel showTableSelectMsg + sel, m.pendingSelection = *m.pendingSelection, nil + + return m, sel.onSelected(listController.list.SelectedItem().(tableItem).name) + } + } + } + + newModal, cmd := m.modal.Update(msg) + m.modal = newModal.(modal.Modal) + return m, cmd +} + +func (m Model) View() string { + return m.modal.View() +} + +func (m Model) Resize(w, h int) layout.ResizingModel { + m.w, m.h = w, h + m.modal = layout.Resize(m.modal, w, h).(modal.Modal) + return m +}