Most of the new models have been reimplemented

This commit is contained in:
Leon Mika 2022-03-27 23:19:38 +00:00 committed by GitHub
parent 7a5584cf9a
commit aa828df3ae
19 changed files with 226 additions and 156 deletions

View file

@ -22,7 +22,6 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/ui"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels"
"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"
)
@ -157,7 +156,8 @@ func newTestModel(descr string) tea.Model {
return nil
},
tableselect.ShowTableSelect(func(n string) tea.Cmd {
return statusandprompt.SetStatus("New table = " + n)
// return statusandprompt.SetStatus("New table = " + n)
return nil
}),
),
)

View file

@ -23,9 +23,9 @@ func NewCommandController(commands map[string]uimodels.Operation) *CommandContro
func (c *CommandController) Prompt() uimodels.Operation {
return uimodels.OperationFn(func(ctx context.Context) error {
uiCtx := uimodels.Ctx(ctx)
uiCtx.Send(events.PromptForInput{
uiCtx.Send(events.PromptForInputMsg{
Prompt: ":",
OnDone: c.Execute(),
// OnDone: c.Execute(),
})
return nil
})

View file

@ -1,10 +1,7 @@
package dispatcher
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/common/ui/uimodels"
)
@ -13,20 +10,20 @@ type DispatcherContext struct {
}
func (dc DispatcherContext) Messagef(format string, args ...interface{}) {
dc.Publisher.Send(events.Message(fmt.Sprintf(format, args...)))
// dc.Publisher.Send(events.Message(fmt.Sprintf(format, args...)))
}
func (dc DispatcherContext) Send(teaMessage tea.Msg) {
dc.Publisher.Send(teaMessage)
// dc.Publisher.Send(teaMessage)
}
func (dc DispatcherContext) Message(msg string) {
dc.Publisher.Send(events.Message(msg))
// dc.Publisher.Send(events.Message(msg))
}
func (dc DispatcherContext) Input(prompt string, onDone uimodels.Operation) {
dc.Publisher.Send(events.PromptForInput{
Prompt: prompt,
OnDone: onDone,
})
// dc.Publisher.Send(events.PromptForInput{
// Prompt: prompt,
// OnDone: onDone,
// })
}

View file

@ -0,0 +1,26 @@
package events
import tea "github.com/charmbracelet/bubbletea"
func Error(err error) tea.Msg {
return ErrorMsg(err)
}
func SetStatus(msg string) tea.Cmd {
return func() tea.Msg {
return StatusMsg(msg)
}
}
func PromptForInput(prompt string, onDone func(value string) tea.Cmd) tea.Cmd {
return func() tea.Msg {
return PromptForInputMsg{
Prompt: prompt,
OnDone: onDone,
}
}
}
type MessageWithStatus interface {
StatusMessage() string
}

View file

@ -1,17 +1,17 @@
package events
import (
"github.com/lmika/awstools/internal/common/ui/uimodels"
tea "github.com/charmbracelet/bubbletea"
)
// Error indicates that an error occurred
type Error error
type ErrorMsg error
// Message indicates that a message should be shown to the user
type Message string
type StatusMsg string
// PromptForInput indicates that the context is requesting a line of input
type PromptForInput struct {
type PromptForInputMsg struct {
Prompt string
OnDone uimodels.Operation
OnDone func(value string) tea.Cmd
}

View file

@ -1,11 +1,25 @@
package controllers
import "github.com/lmika/awstools/internal/dynamo-browse/models"
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/models"
)
type NewResultSet struct {
ResultSet *models.ResultSet
}
func (rs NewResultSet) StatusMessage() string {
return fmt.Sprintf("%d items returned", len(rs.ResultSet.Items))
}
type SetReadWrite struct {
NewValue bool
}
type PromptForTableMsg struct {
Tables []string
OnSelected func(tableName string) tea.Cmd
}

View file

@ -5,6 +5,7 @@ import (
"log"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/pkg/errors"
@ -22,22 +23,62 @@ func NewTableReadController(tableService *tables.Service, tableName string) *Tab
}
}
func (c *TableReadController) Scan() tea.Cmd {
// Init does an initial scan of the table. If no table is specified, it prompts for a table, then does a scan.
func (c *TableReadController) Init() tea.Cmd {
if c.tableName == "" {
return c.listTables()
} else {
return c.scanTable(c.tableName)
}
}
func (c *TableReadController) listTables() tea.Cmd {
return func() tea.Msg {
tables, err := c.tableService.ListTables(context.Background())
if err != nil {
return events.Error(err)
}
return PromptForTableMsg{
Tables: tables,
OnSelected: func(tableName string) tea.Cmd {
return c.scanTable(tableName)
},
}
}
}
func (c *TableReadController) scanTable(name string) tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
log.Println("Fetching table info")
tableInfo, err := c.tableInfo(ctx)
tableInfo, err := c.tableService.Describe(ctx, name)
if err != nil {
log.Println("error: ", err)
return err
return events.Error(errors.Wrapf(err, "cannot describe %v", c.tableName))
}
log.Println("Scanning")
resultSet, err := c.tableService.Scan(ctx, tableInfo)
if err != nil {
log.Println("error: ", err)
return err
return events.Error(err)
}
log.Println("Scan done")
return NewResultSet{resultSet}
}
}
func (c *TableReadController) Rescan(resultSet *models.ResultSet) tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
log.Println("Scanning")
resultSet, err := c.tableService.Scan(ctx, resultSet.TableInfo)
if err != nil {
log.Println("error: ", err)
return events.Error(err)
}
log.Println("Scan done")
@ -79,16 +120,16 @@ func (c *TableReadController) doScan(ctx context.Context, quiet bool) (err error
// tableInfo returns the table info from the state if a result set exists. If not, it fetches the
// table information from the service.
func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) {
/*
if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil {
return existingResultSet.TableInfo, nil
}
*/
// func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) {
// /*
// if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil {
// return existingResultSet.TableInfo, nil
// }
// */
tableInfo, err := c.tableService.Describe(ctx, c.tableName)
if err != nil {
return nil, errors.Wrapf(err, "cannot describe %v", c.tableName)
}
return tableInfo, nil
}
// tableInfo, err := c.tableService.Describe(ctx, c.tableName)
// if err != nil {
// return nil, errors.Wrapf(err, "cannot describe %v", c.tableName)
// }
// return tableInfo, nil
// }

View file

@ -4,7 +4,6 @@ import (
"context"
"github.com/lmika/awstools/internal/common/ui/uimodels"
"github.com/lmika/awstools/internal/dynamo-browse/models/modexpr"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/pkg/errors"
)
@ -41,6 +40,8 @@ func (c *TableWriteController) ToggleReadWrite() uimodels.Operation {
}
func (c *TableWriteController) Duplicate() uimodels.Operation {
return nil
/*
return uimodels.OperationFn(func(ctx context.Context) error {
uiCtx := uimodels.Ctx(ctx)
state := CurrentState(ctx)
@ -91,6 +92,7 @@ func (c *TableWriteController) Duplicate() uimodels.Operation {
}))
return nil
})
*/
}
func (c *TableWriteController) Delete() uimodels.Operation {
@ -111,6 +113,7 @@ func (c *TableWriteController) Delete() uimodels.Operation {
return errors.New("operation aborted")
}
/*
tableInfo, err := c.tableReadControllers.tableInfo(ctx)
if err != nil {
return err
@ -120,6 +123,7 @@ func (c *TableWriteController) Delete() uimodels.Operation {
if err := c.tableService.Delete(ctx, tableInfo, state.SelectedItem); err != nil {
return err
}
*/
// Rescan to get updated items
// if err := c.tableReadControllers.doScan(ctx, true); err != nil {

View file

@ -14,6 +14,15 @@ type Provider struct {
client *dynamodb.Client
}
func (p *Provider) ListTables(ctx context.Context) ([]string, error) {
out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{})
if err != nil {
return nil, errors.Wrapf(err, "cannot list tables")
}
return out.TableNames, nil
}
func (p *Provider) DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error) {
out, err := p.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),

View file

@ -8,6 +8,7 @@ import (
)
type TableProvider interface {
ListTables(ctx context.Context) ([]string, error)
DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error)
ScanItems(ctx context.Context, tableName string) ([]models.Item, error)
DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error

View file

@ -18,6 +18,10 @@ func NewService(provider TableProvider) *Service {
}
}
func (s *Service) ListTables(ctx context.Context) ([]string, error) {
return s.provider.ListTables(ctx)
}
func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo, error) {
return s.provider.DescribeTable(ctx, table)
}

View file

@ -33,7 +33,7 @@ func NewModel(rc *controllers.TableReadController) Model {
}
func (m Model) Init() tea.Cmd {
return m.tableReadController.Scan()
return m.tableReadController.Init()
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

View file

@ -11,7 +11,6 @@ import (
"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/events"
"github.com/lmika/awstools/internal/common/ui/uimodels"
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
)
@ -40,7 +39,7 @@ type uiModel struct {
state controllers.State
message string
pendingInput *events.PromptForInput
// pendingInput *events.PromptForInput
textInput textinput.Model
dispatcher *dispatcher.Dispatcher
@ -152,15 +151,15 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.state.InReadWriteMode = msg.NewValue
// Shared events
case events.Error:
m.message = "Error: " + msg.Error()
case events.Message:
m.message = string(msg)
case events.PromptForInput:
m.textInput.Prompt = msg.Prompt
m.textInput.Focus()
m.textInput.SetValue("")
m.pendingInput = &msg
// case events.Error:
// m.message = "Error: " + msg.Error()
// case events.Message:
// m.message = string(msg)
// case events.PromptForInput:
// m.textInput.Prompt = msg.Prompt
// m.textInput.Focus()
// m.textInput.SetValue("")
// m.pendingInput = &msg
// Tea events
case tea.WindowSizeMsg:
@ -186,18 +185,18 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
// If text input in focus, allow that to accept input messages
if m.pendingInput != nil {
switch msg.String() {
case "ctrl+c", "esc":
m.pendingInput = nil
case "enter":
m.invokeOperation(uimodels.WithPromptValue(context.Background(), m.textInput.Value()), m.pendingInput.OnDone)
m.pendingInput = nil
default:
m.textInput, textInputCommands = m.textInput.Update(msg)
}
break
}
// if m.pendingInput != nil {
// switch msg.String() {
// case "ctrl+c", "esc":
// m.pendingInput = nil
// case "enter":
// m.invokeOperation(uimodels.WithPromptValue(context.Background(), m.textInput.Value()), m.pendingInput.OnDone)
// m.pendingInput = nil
// default:
// m.textInput, textInputCommands = m.textInput.Update(msg)
// }
// break
// }
switch msg.String() {
case "ctrl+c", "q":

View file

@ -57,7 +57,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// TEMP
case "s":
return m, m.tableReadControllers.Scan()
return m, m.tableReadControllers.Rescan(m.resultSet)
case "ctrl+c", "esc":
return m, tea.Quit
}

View file

@ -1,25 +0,0 @@
package statusandprompt
import tea "github.com/charmbracelet/bubbletea"
type setStatusMsg string
type startPromptMsg struct {
prompt string
onDone func(val string) tea.Cmd
}
func SetStatus(newStatus string) tea.Cmd {
return func() tea.Msg {
return setStatusMsg(newStatus)
}
}
func Prompt(prompt string, onDone func(val string) tea.Cmd) tea.Cmd {
return func() tea.Msg {
return startPromptMsg{
prompt: prompt,
onDone: onDone,
}
}
}

View file

@ -4,6 +4,7 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
)
@ -13,7 +14,7 @@ import (
type StatusAndPrompt struct {
model layout.ResizingModel
statusMessage string
pendingInput *startPromptMsg
pendingInput *events.PromptForInputMsg
textInput textinput.Model
width int
}
@ -29,15 +30,19 @@ func (s StatusAndPrompt) Init() tea.Cmd {
func (s StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case setStatusMsg:
s.statusMessage = string(msg)
case startPromptMsg:
case events.ErrorMsg:
s.statusMessage = "Error: " + msg.Error()
case events.StatusMsg:
s.statusMessage = string(s.statusMessage)
case events.MessageWithStatus:
s.statusMessage = msg.StatusMessage()
case events.PromptForInputMsg:
if s.pendingInput != nil {
// ignore, already in an input
return s, nil
}
s.textInput.Prompt = msg.prompt
s.textInput.Prompt = msg.Prompt
s.textInput.Focus()
s.textInput.SetValue("")
s.pendingInput = &msg
@ -51,7 +56,7 @@ func (s StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
pendingInput := s.pendingInput
s.pendingInput = nil
return s, pendingInput.onDone(s.textInput.Value())
return s, pendingInput.OnDone(s.textInput.Value())
}
}
}

View file

@ -15,13 +15,13 @@ func (ti tableItem) Title() string {
}
func (ti tableItem) Description() string {
return "abc"
return ""
}
func toListItems[T list.Item](xs []T) []list.Item {
func toListItems(xs []string) []list.Item {
ls := make([]list.Item, len(xs))
for i, x := range xs {
ls[i] = x
ls[i] = tableItem{name: x}
}
return ls
}

View file

@ -20,14 +20,8 @@ type listController struct {
list list.Model
}
func newListController(w, h int) listController {
tableItems := []tableItem{
{name: "alpha"},
{name: "beta"},
{name: "gamma"},
}
items := toListItems(tableItems)
func newListController(tableNames []string, w, h int) listController {
items := toListItems(tableNames)
delegate := list.NewDefaultDelegate()
delegate.ShowDescription = false

View file

@ -2,13 +2,14 @@ package tableselect
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
)
type Model struct {
submodel tea.Model
pendingSelection *showTableSelectMsg
pendingSelection *controllers.PromptForTableMsg
listController listController
isLoading bool
w, h int
@ -25,10 +26,10 @@ func (m Model) Init() tea.Cmd {
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector
switch msg := msg.(type) {
case showTableSelectMsg:
case controllers.PromptForTableMsg:
m.isLoading = false
m.pendingSelection = &msg
m.listController = newListController(m.w, m.h)
m.listController = newListController(msg.Tables, m.w, m.h)
return m, nil
case indicateLoadingTablesMsg:
m.isLoading = true
@ -37,10 +38,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.pendingSelection != nil {
switch msg.String() {
case "enter":
var sel showTableSelectMsg
var sel controllers.PromptForTableMsg
sel, m.pendingSelection = *m.pendingSelection, nil
return m, sel.onSelected(m.listController.list.SelectedItem().(tableItem).name)
return m, sel.OnSelected(m.listController.list.SelectedItem().(tableItem).name)
default:
m.listController = cc.Collect(m.listController.Update(msg)).(listController)
return m, cc.Cmd()