diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index 4f5312f..ff5b95d 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -9,6 +9,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/lmika/awstools/internal/common/ui/commandctrl" + "github.com/lmika/awstools/internal/common/ui/logging" "github.com/lmika/awstools/internal/dynamo-browse/controllers" "github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo" "github.com/lmika/awstools/internal/dynamo-browse/services/tables" @@ -46,10 +47,18 @@ func main() { tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable) _ = tableWriteController - commandController := commandctrl.NewCommandController(map[string]commandctrl.Command{ - "q": commandctrl.NoArgCommand(tea.Quit), - //"rw": tableWriteController.ToggleReadWrite(), - //"dup": tableWriteController.Duplicate(), + commandController := commandctrl.NewCommandController() + commandController.AddCommands(&commandctrl.CommandContext{ + Commands: map[string]commandctrl.Command{ + "q": commandctrl.NoArgCommand(tea.Quit), + "table": func(args []string) tea.Cmd { + if len(args) == 0 { + return tableReadController.ListTables() + } else { + return tableReadController.ScanTable(args[0]) + } + }, + }, }) model := ui.NewModel(tableReadController, commandController) @@ -59,12 +68,8 @@ func main() { p := tea.NewProgram(model, tea.WithAltScreen()) - f, err := tea.LogToFile("debug.log", "debug") - if err != nil { - fmt.Println("fatal:", err) - os.Exit(1) - } - defer f.Close() + closeFn := logging.EnableLogging() + defer closeFn() log.Println("launching") if err := p.Start(); err != nil { @@ -72,12 +77,3 @@ func main() { os.Exit(1) } } - -// -//type msgLoopback struct { -// program *tea.Program -//} -// -//func (m *msgLoopback) Send(msg tea.Msg) { -// m.program.Send(msg) -//} diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index 90b7d00..1ff1494 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -7,30 +7,36 @@ import ( "github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/pkg/errors" + "sync" ) type TableReadController struct { tableService *tables.Service tableName string + + // state + mutex *sync.Mutex + resultSet *models.ResultSet } func NewTableReadController(tableService *tables.Service, tableName string) *TableReadController { return &TableReadController{ tableService: tableService, tableName: tableName, + mutex: new(sync.Mutex), } } // 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() + return c.ListTables() } else { - return c.scanTable(c.tableName) + return c.ScanTable(c.tableName) } } -func (c *TableReadController) listTables() tea.Cmd { +func (c *TableReadController) ListTables() tea.Cmd { return func() tea.Msg { tables, err := c.tableService.ListTables(context.Background()) if err != nil { @@ -40,13 +46,13 @@ func (c *TableReadController) listTables() tea.Cmd { return PromptForTableMsg{ Tables: tables, OnSelected: func(tableName string) tea.Cmd { - return c.scanTable(tableName) + return c.ScanTable(tableName) }, } } } -func (c *TableReadController) scanTable(name string) tea.Cmd { +func (c *TableReadController) ScanTable(name string) tea.Cmd { return func() tea.Msg { ctx := context.Background() @@ -60,23 +66,31 @@ func (c *TableReadController) scanTable(name string) tea.Cmd { return events.Error(err) } - return NewResultSet{resultSet} + return c.setResultSet(resultSet) } } -func (c *TableReadController) Rescan(resultSet *models.ResultSet) tea.Cmd { +func (c *TableReadController) Rescan() tea.Cmd { return func() tea.Msg { ctx := context.Background() - resultSet, err := c.tableService.Scan(ctx, resultSet.TableInfo) + resultSet, err := c.tableService.Scan(ctx, c.resultSet.TableInfo) if err != nil { return events.Error(err) } - return NewResultSet{resultSet} + return c.setResultSet(resultSet) } } +func (c *TableReadController) setResultSet(resultSet *models.ResultSet) tea.Msg { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.resultSet = resultSet + return NewResultSet{resultSet} +} + /* func (c *TableReadController) Scan() uimodels.Operation { return uimodels.OperationFn(func(ctx context.Context) error { diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index e2b8362..b4a8835 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -14,23 +14,25 @@ import ( type Model struct { tableReadController *controllers.TableReadController commandController *commandctrl.CommandController + statusAndPrompt *statusandprompt.StatusAndPrompt + tableSelect *tableselect.Model root tea.Model } func NewModel(rc *controllers.TableReadController, cc *commandctrl.CommandController) Model { - dtv := dynamotableview.New(rc, cc) + dtv := dynamotableview.New() div := dynamoitemview.New() + statusAndPrompt := statusandprompt.New(layout.NewVBox(layout.LastChildFixedAt(17), dtv, div), "") + tableSelect := tableselect.New(statusAndPrompt) - m := statusandprompt.New( - layout.NewVBox(layout.LastChildFixedAt(17), dtv, div), - "Hello world", - ) - root := layout.FullScreen(tableselect.New(m)) + root := layout.FullScreen(tableSelect) return Model{ tableReadController: rc, commandController: cc, + statusAndPrompt: statusAndPrompt, + tableSelect: tableSelect, root: root, } } @@ -40,6 +42,20 @@ func (m Model) Init() tea.Cmd { } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() { + switch msg.String() { + case "s": + return m, m.tableReadController.Rescan() + case ":": + return m, m.commandController.Prompt() + case "ctrl+c", "esc": + return m, tea.Quit + } + } + } + var cmd tea.Cmd m.root, cmd = m.root.Update(msg) return m, cmd diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go index 132ef87..1f6cd98 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go @@ -25,18 +25,18 @@ type Model struct { selectedItem models.Item } -func New() Model { - return Model{ +func New() *Model { + return &Model{ frameTitle: frame.NewFrameTitle("Item", false), viewport: viewport.New(100, 100), } } -func (Model) Init() tea.Cmd { +func (*Model) Init() tea.Cmd { return nil } -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case NewItemSelected: m.currentResultSet = msg.ResultSet @@ -47,14 +47,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m Model) View() string { +func (m *Model) View() string { if !m.ready { return "" } return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.viewport.View()) } -func (m Model) Resize(w, h int) layout.ResizingModel { +func (m *Model) Resize(w, h int) layout.ResizingModel { m.w, m.h = w, h if !m.ready { m.viewport = viewport.New(w, h-m.frameTitle.HeaderHeight()) diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go index 6245be4..44a818c 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go @@ -4,7 +4,6 @@ import ( table "github.com/calyptia/go-bubble-table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/dynamo-browse/controllers" "github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamoitemview" @@ -13,9 +12,6 @@ import ( ) type Model struct { - tableReadControllers *controllers.TableReadController - commandCtrl *commandctrl.CommandController - frameTitle frame.FrameTitle table table.Model w, h int @@ -24,26 +20,24 @@ type Model struct { resultSet *models.ResultSet } -func New(tableReadControllers *controllers.TableReadController, commandCtrl *commandctrl.CommandController) Model { +func New() *Model { tbl := table.New([]string{"pk", "sk"}, 100, 100) rows := make([]table.Row, 0) tbl.SetRows(rows) frameTitle := frame.NewFrameTitle("No table", true) - return Model{ - tableReadControllers: tableReadControllers, - commandCtrl: commandCtrl, + return &Model{ frameTitle: frameTitle, table: tbl, } } -func (m Model) Init() tea.Cmd { +func (m *Model) Init() tea.Cmd { return nil } -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case controllers.NewResultSet: m.resultSet = msg.ResultSet @@ -58,26 +52,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "k", "down": m.table.GoDown() return m, m.postSelectedItemChanged - - // TEMP - case "s": - return m, m.tableReadControllers.Rescan(m.resultSet) - case ":": - return m, m.commandCtrl.Prompt() - // END TEMP - case "ctrl+c", "esc": - return m, tea.Quit } } return m, nil } -func (m Model) View() string { +func (m *Model) View() string { return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View()) } -func (m Model) Resize(w, h int) layout.ResizingModel { +func (m *Model) Resize(w, h int) layout.ResizingModel { m.w, m.h = w, h tblHeight := h - m.frameTitle.HeaderHeight() m.table.SetSize(w, tblHeight) @@ -120,31 +105,3 @@ func (m *Model) postSelectedItemChanged() tea.Msg { return dynamoitemview.NewItemSelected{ResultSet: item.resultSet, Item: item.item} } - -/* -func (m *Model) updateViewportToSelectedMessage() { - selectedItem, ok := m.selectedItem() - if !ok { - m.viewport.SetContent("(no row selected)") - return - } - - viewportContent := &strings.Builder{} - tabWriter := tabwriter.NewWriter(viewportContent, 0, 1, 1, ' ', 0) - for _, colName := range selectedItem.resultSet.Columns { - switch colVal := selectedItem.item[colName].(type) { - case nil: - break - case *types.AttributeValueMemberS: - fmt.Fprintf(tabWriter, "%v\tS\t%s\n", colName, colVal.Value) - case *types.AttributeValueMemberN: - fmt.Fprintf(tabWriter, "%v\tN\t%s\n", colName, colVal.Value) - default: - fmt.Fprintf(tabWriter, "%v\t?\t%s\n", colName, "(other)") - } - } - - tabWriter.Flush() - m.viewport.SetContent(viewportContent.String()) -} -*/ diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/model.go b/internal/dynamo-browse/ui/teamodels/tableselect/model.go index dc3f24b..b982b1b 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/model.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/model.go @@ -19,16 +19,16 @@ type Model struct { w, h int } -func New(submodel tea.Model) Model { +func New(submodel tea.Model) *Model { frameTitle := frame.NewFrameTitle("Select table", false) - return Model{frameTitle: frameTitle, submodel: submodel} + return &Model{frameTitle: frameTitle, submodel: submodel} } -func (m Model) Init() tea.Cmd { +func (m *Model) Init() tea.Cmd { return m.submodel.Init() } -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cc utils.CmdCollector switch msg := msg.(type) { case controllers.PromptForTableMsg: @@ -60,7 +60,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cc.Cmd() } -func (m Model) View() string { +func (m *Model) View() string { if m.pendingSelection != nil { return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.listController.View()) } else if m.isLoading { @@ -70,11 +70,11 @@ func (m Model) View() string { return m.submodel.View() } -func (m Model) shouldShow() bool { +func (m *Model) Visible() bool { return m.pendingSelection != nil || m.isLoading } -func (m Model) Resize(w, h int) layout.ResizingModel { +func (m *Model) Resize(w, h int) layout.ResizingModel { m.w, m.h = w, h m.submodel = layout.Resize(m.submodel, w, h)