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.
This commit is contained in:
Leon Mika 2022-03-27 15:53:58 +11:00
parent 81cd1d0971
commit 5d213c4ee8
7 changed files with 245 additions and 13 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/common/ui/commandctrl"
"github.com/lmika/awstools/internal/common/ui/dispatcher" "github.com/lmika/awstools/internal/common/ui/dispatcher"
"github.com/lmika/awstools/internal/common/ui/uimodels" "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/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" "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/statusandprompt"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/tableselect"
"github.com/lmika/gopkgs/cli" "github.com/lmika/gopkgs/cli"
"log" "log"
"os" "os"
@ -65,13 +67,17 @@ func main() {
_ = uiModel _ = uiModel
// END TEMP // END TEMP
model := layout.FullScreen(statusandprompt.New( var model tea.Model = statusandprompt.New(
layout.NewVBox( layout.NewVBox(
frame.NewFrame("This is the header", true, layout.Model(newTestModel("this is the top"))), 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"))), frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))),
), ),
"Hello world", "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{ //frameSet := frameset.New([]frameset.Frame{
// { // {
@ -129,8 +135,8 @@ func newTestModel(descr string) tea.Model {
OnKeyPressed: func(k string) tea.Cmd { OnKeyPressed: func(k string) tea.Cmd {
log.Println("got key press: " + k) log.Println("got key press: " + k)
if k == "enter" { if k == "enter" {
return statusandprompt.Prompt("What is your car? ", func(val string) tea.Cmd { return tableselect.ShowTableSelect(func(n string) tea.Cmd {
return statusandprompt.SetStatus("Your car is = " + val) return statusandprompt.SetStatus("New table = " + n)
}) })
} else if k == "k" { } else if k == "k" {
return modal.PopMode return modal.PopMode

View file

@ -15,6 +15,15 @@ type ResizingModel interface {
Resize(w, h int) ResizingModel 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 // Model takes a tea-model and displays it as a resizing model. The model will be
// displayed with all the available space provided // displayed with all the available space provided
func Model(m tea.Model) ResizingModel { func Model(m tea.Model) ResizingModel {
@ -46,3 +55,54 @@ func (t teaModel) Resize(w, h int) ResizingModel {
t.w, t.h = w, h t.w, t.h = w, h
return t 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()
}

View file

@ -2,6 +2,7 @@ package modal
import ( import (
tea "github.com/charmbracelet/bubbletea" 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" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
"log" "log"
) )
@ -18,30 +19,34 @@ func New(baseMode tea.Model) Modal {
} }
func (m Modal) Init() tea.Cmd { 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) m.modeStack = append(m.modeStack, model)
log.Printf("pusing new mode: len = %v", len(m.modeStack)) 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 { if len(m.modeStack) > 0 {
p = m.modeStack[len(m.modeStack)-1]
m.modeStack = 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) { func (m Modal) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector var cc utils.CmdCollector
switch msg := msg.(type) { switch msg := msg.(type) {
case newModePushed:
m.pushMode(msg)
return m, nil
case modePopped:
m.popMode()
return m, nil
case tea.KeyMsg, tea.MouseMsg: case tea.KeyMsg, tea.MouseMsg:
// only notify top level stack // only notify top level stack
if len(m.modeStack) > 0 { if len(m.modeStack) > 0 {
@ -69,3 +74,11 @@ func (m Modal) View() string {
} }
return m.baseMode.View() 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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}