From 2638597f42475b73df22117cdf6e6a0223b5a22c Mon Sep 17 00:00:00 2001
From: Leon Mika <lmika@lmika.org>
Date: Sun, 27 Mar 2022 21:21:52 +0000
Subject: [PATCH] Have got the table view working again

---
 cmd/dynamo-browse/main.go                     |  26 ++--
 .../dynamo-browse/controllers/tableread.go    |  36 +++++-
 .../dynamo-browse/controllers/tablewrite.go   |  12 +-
 internal/dynamo-browse/ui/model.go            |  25 ++--
 .../ui/teamodels/dynamotableview/model.go     | 120 ++++++++++++++++++
 .../dynamotableview}/tblmodel.go              |   2 +-
 .../ui/teamodels/layout/boxsize.go            |  36 ++++++
 .../dynamo-browse/ui/teamodels/layout/vbox.go |  17 +--
 test/cmd/load-test-table/main.go              |   7 +-
 9 files changed, 238 insertions(+), 43 deletions(-)
 create mode 100644 internal/dynamo-browse/ui/teamodels/dynamotableview/model.go
 rename internal/dynamo-browse/ui/{ => teamodels/dynamotableview}/tblmodel.go (97%)
 create mode 100644 internal/dynamo-browse/ui/teamodels/layout/boxsize.go

diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go
index 9035221..803afd4 100644
--- a/cmd/dynamo-browse/main.go
+++ b/cmd/dynamo-browse/main.go
@@ -4,7 +4,12 @@ import (
 	"context"
 	"flag"
 	"fmt"
+	"log"
+	"os"
+	"time"
+
 	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/aws/aws-sdk-go-v2/credentials"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/charmbracelet/lipgloss"
@@ -16,15 +21,13 @@ import (
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
 	"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/dynamotableview"
 	"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
 	"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"
-	"time"
 )
 
 func main() {
@@ -33,7 +36,13 @@ func main() {
 	flag.Parse()
 
 	ctx := context.Background()
-	cfg, err := config.LoadDefaultConfig(ctx)
+
+	// TEMP
+	cfg, err := config.LoadDefaultConfig(ctx,
+		config.WithRegion("ap-southeast-2"),
+		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("abc", "123", "")))
+
+	// END TEMP
 	if err != nil {
 		cli.Fatalf("cannot load AWS config: %v", err)
 	}
@@ -57,9 +66,9 @@ func main() {
 	tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable)
 
 	commandController := commandctrl.NewCommandController(map[string]uimodels.Operation{
-		"scan": tableReadController.Scan(),
-		"rw":   tableWriteController.ToggleReadWrite(),
-		"dup":  tableWriteController.Duplicate(),
+		// "scan": tableReadController.Scan(),
+		"rw":  tableWriteController.ToggleReadWrite(),
+		"dup": tableWriteController.Duplicate(),
 	})
 
 	uiModel := ui.NewModel(uiDispatcher, commandController, tableReadController, tableWriteController)
@@ -70,7 +79,8 @@ func main() {
 
 	var model tea.Model = statusandprompt.New(
 		layout.NewVBox(
-			frame.NewFrame("This is the header", true, layout.Model(newTestModel("this is the top"))),
+			layout.LastChildFixedAt(11),
+			frame.NewFrame("This is the header", true, dynamotableview.New(tableReadController)),
 			frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))),
 		),
 		"Hello world",
diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go
index 94ade82..85f4737 100644
--- a/internal/dynamo-browse/controllers/tableread.go
+++ b/internal/dynamo-browse/controllers/tableread.go
@@ -2,8 +2,9 @@ package controllers
 
 import (
 	"context"
+	"log"
 
-	"github.com/lmika/awstools/internal/common/ui/uimodels"
+	tea "github.com/charmbracelet/bubbletea"
 	"github.com/lmika/awstools/internal/dynamo-browse/models"
 	"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
 	"github.com/pkg/errors"
@@ -21,6 +22,30 @@ func NewTableReadController(tableService *tables.Service, tableName string) *Tab
 	}
 }
 
+func (c *TableReadController) Scan() tea.Cmd {
+	return func() tea.Msg {
+		ctx := context.Background()
+
+		log.Println("Fetching table info")
+		tableInfo, err := c.tableInfo(ctx)
+		if err != nil {
+			log.Println("error: ", err)
+			return err
+		}
+
+		log.Println("Scanning")
+		resultSet, err := c.tableService.Scan(ctx, tableInfo)
+		if err != nil {
+			log.Println("error: ", err)
+			return err
+		}
+
+		log.Println("Scan done")
+		return NewResultSet{resultSet}
+	}
+}
+
+/*
 func (c *TableReadController) Scan() uimodels.Operation {
 	return uimodels.OperationFn(func(ctx context.Context) error {
 		return c.doScan(ctx, false)
@@ -50,13 +75,16 @@ func (c *TableReadController) doScan(ctx context.Context, quiet bool) (err error
 	uiCtx.Send(NewResultSet{resultSet})
 	return nil
 }
+*/
 
 // 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
-	}
+	/*
+		if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil {
+			return existingResultSet.TableInfo, nil
+		}
+	*/
 
 	tableInfo, err := c.tableService.Describe(ctx, c.tableName)
 	if err != nil {
diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go
index 57b252c..938fd47 100644
--- a/internal/dynamo-browse/controllers/tablewrite.go
+++ b/internal/dynamo-browse/controllers/tablewrite.go
@@ -81,9 +81,9 @@ func (c *TableWriteController) Duplicate() uimodels.Operation {
 				}
 
 				// Rescan to get updated items
-				if err := c.tableReadControllers.doScan(ctx, true); err != nil {
-					return err
-				}
+				// if err := c.tableReadControllers.doScan(ctx, true); err != nil {
+				// 	return err
+				// }
 
 				return nil
 			}))
@@ -122,9 +122,9 @@ func (c *TableWriteController) Delete() uimodels.Operation {
 			}
 
 			// Rescan to get updated items
-			if err := c.tableReadControllers.doScan(ctx, true); err != nil {
-				return err
-			}
+			// if err := c.tableReadControllers.doScan(ctx, true); err != nil {
+			// 	return err
+			// }
 
 			uiCtx.Message("Item deleted")
 			return nil
diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go
index b9f4eee..4d86cc1 100644
--- a/internal/dynamo-browse/ui/model.go
+++ b/internal/dynamo-browse/ui/model.go
@@ -2,11 +2,8 @@ package ui
 
 import (
 	"context"
-	"fmt"
 	"strings"
-	"text/tabwriter"
 
-	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	table "github.com/calyptia/go-bubble-table"
 	"github.com/charmbracelet/bubbles/textinput"
 	"github.com/charmbracelet/bubbles/viewport"
@@ -83,6 +80,7 @@ func (m uiModel) Init() tea.Cmd {
 	return nil
 }
 
+/*
 func (m *uiModel) updateTable() {
 	if !m.ready {
 		return
@@ -99,6 +97,8 @@ func (m *uiModel) updateTable() {
 	m.table = newTbl
 }
 
+
+
 func (m *uiModel) selectedItem() (itemTableRow, bool) {
 	resultSet := m.state.ResultSet
 	if m.ready && resultSet != nil && len(resultSet.Items) > 0 {
@@ -136,6 +136,7 @@ func (m *uiModel) updateViewportToSelectedMessage() {
 	tabWriter.Flush()
 	m.viewport.SetContent(viewportContent.String())
 }
+*/
 
 func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	var textInputCommands tea.Cmd
@@ -145,8 +146,8 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	// Local events
 	case controllers.NewResultSet:
 		m.state.ResultSet = msg.ResultSet
-		m.updateTable()
-		m.updateViewportToSelectedMessage()
+		// m.updateTable()
+		// m.updateViewportToSelectedMessage()
 	case controllers.SetReadWrite:
 		m.state.InReadWriteMode = msg.NewValue
 
@@ -203,16 +204,16 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return m, tea.Quit
 		case "up", "i":
 			m.table.GoUp()
-			m.updateViewportToSelectedMessage()
+			// m.updateViewportToSelectedMessage()
 		case "down", "k":
 			m.table.GoDown()
-			m.updateViewportToSelectedMessage()
+			// m.updateViewportToSelectedMessage()
 
 		// TODO: these should be moved somewhere else
 		case ":":
 			m.invokeOperation(context.Background(), m.commandController.Prompt())
-		case "s":
-			m.invokeOperation(context.Background(), m.tableReadController.Scan())
+		// case "s":
+		// m.invokeOperation(context.Background(), m.tableReadController.Scan())
 		case "D":
 			m.invokeOperation(context.Background(), m.tableWriteController.Delete())
 		}
@@ -233,9 +234,9 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 func (m uiModel) invokeOperation(ctx context.Context, op uimodels.Operation) {
 	state := m.state
-	if selectedItem, ok := m.selectedItem(); ok {
-		state.SelectedItem = selectedItem.item
-	}
+	// if selectedItem, ok := m.selectedItem(); ok {
+	// 	state.SelectedItem = selectedItem.item
+	// }
 
 	ctx = controllers.ContextWithState(ctx, state)
 	m.dispatcher.Start(ctx, op)
diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go
new file mode 100644
index 0000000..925d067
--- /dev/null
+++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go
@@ -0,0 +1,120 @@
+package dynamotableview
+
+import (
+	table "github.com/calyptia/go-bubble-table"
+	tea "github.com/charmbracelet/bubbletea"
+	"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/layout"
+)
+
+type Model struct {
+	tableReadControllers *controllers.TableReadController
+	table                table.Model
+	w, h                 int
+
+	// model state
+	resultSet *models.ResultSet
+}
+
+func New(tableReadControllers *controllers.TableReadController) Model {
+	tbl := table.New([]string{"pk", "sk"}, 100, 100)
+	rows := make([]table.Row, 0)
+	tbl.SetRows(rows)
+
+	return Model{tableReadControllers: tableReadControllers, table: tbl}
+}
+
+func (m Model) Init() tea.Cmd {
+	return nil
+}
+
+func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	switch msg := msg.(type) {
+	case controllers.NewResultSet:
+		m.resultSet = msg.ResultSet
+		m.updateTable()
+		return m, nil
+	case tea.KeyMsg:
+		switch msg.String() {
+		// Table nav
+		case "i", "up":
+			m.table.GoUp()
+			return m, nil
+		case "k", "down":
+			m.table.GoDown()
+			return m, nil
+
+		// TEMP
+		case "s":
+			return m, m.tableReadControllers.Scan()
+		case "ctrl+c", "esc":
+			return m, tea.Quit
+		}
+	}
+
+	return m, nil
+}
+
+func (m Model) View() string {
+	return m.table.View()
+}
+
+func (m Model) Resize(w, h int) layout.ResizingModel {
+	m.w, m.h = w, h
+	m.table.SetSize(w, h)
+	return m
+}
+
+func (m *Model) updateTable() {
+	resultSet := m.resultSet
+
+	newTbl := table.New(resultSet.Columns, m.w, m.h)
+	newRows := make([]table.Row, len(resultSet.Items))
+	for i, r := range resultSet.Items {
+		newRows[i] = itemTableRow{resultSet, r}
+	}
+	newTbl.SetRows(newRows)
+
+	m.table = newTbl
+}
+
+func (m *Model) selectedItem() (itemTableRow, bool) {
+	resultSet := m.resultSet
+	if resultSet != nil && len(resultSet.Items) > 0 {
+		selectedItem, ok := m.table.SelectedRow().(itemTableRow)
+		if ok {
+			return selectedItem, true
+		}
+	}
+
+	return itemTableRow{}, false
+}
+
+/*
+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/tblmodel.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go
similarity index 97%
rename from internal/dynamo-browse/ui/tblmodel.go
rename to internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go
index 6cb8d41..1137062 100644
--- a/internal/dynamo-browse/ui/tblmodel.go
+++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go
@@ -1,4 +1,4 @@
-package ui
+package dynamotableview
 
 import (
 	"fmt"
diff --git a/internal/dynamo-browse/ui/teamodels/layout/boxsize.go b/internal/dynamo-browse/ui/teamodels/layout/boxsize.go
new file mode 100644
index 0000000..cfba488
--- /dev/null
+++ b/internal/dynamo-browse/ui/teamodels/layout/boxsize.go
@@ -0,0 +1,36 @@
+package layout
+
+type BoxSize interface {
+	childSize(idx, cnt, available int) int
+}
+
+func EqualSize() BoxSize {
+	return equalSize{}
+}
+
+type equalSize struct {
+}
+
+func (l equalSize) childSize(idx, cnt, available int) int {
+	childrenHeight := available / cnt
+	lastChildRem := available % cnt
+	if idx == cnt-1 {
+		return childrenHeight + lastChildRem
+	}
+	return childrenHeight
+}
+
+func LastChildFixedAt(size int) BoxSize {
+	return lastChildFixedAt{size}
+}
+
+type lastChildFixedAt struct {
+	lastChildSize int
+}
+
+func (l lastChildFixedAt) childSize(idx, cnt, available int) int {
+	if idx == cnt-1 {
+		return l.lastChildSize
+	}
+	return (equalSize{}).childSize(idx, cnt-1, available-l.lastChildSize)
+}
diff --git a/internal/dynamo-browse/ui/teamodels/layout/vbox.go b/internal/dynamo-browse/ui/teamodels/layout/vbox.go
index 1dac2c2..232099f 100644
--- a/internal/dynamo-browse/ui/teamodels/layout/vbox.go
+++ b/internal/dynamo-browse/ui/teamodels/layout/vbox.go
@@ -1,18 +1,20 @@
 package layout
 
 import (
+	"strings"
+
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
-	"strings"
 )
 
 // VBox is a model which will display its children vertically.
 type VBox struct {
+	boxSize  BoxSize
 	children []ResizingModel
 }
 
-func NewVBox(children ...ResizingModel) VBox {
-	return VBox{children: children}
+func NewVBox(boxSize BoxSize, children ...ResizingModel) VBox {
+	return VBox{boxSize: boxSize, children: children}
 }
 
 func (vb VBox) Init() tea.Cmd {
@@ -43,14 +45,9 @@ func (vb VBox) View() string {
 }
 
 func (vb VBox) Resize(w, h int) ResizingModel {
-	childrenHeight := h / len(vb.children)
-	lastChildRem := h % len(vb.children)
 	for i, c := range vb.children {
-		if i == len(vb.children)-1 {
-			vb.children[i] = c.Resize(w, childrenHeight+lastChildRem)
-		} else {
-			vb.children[i] = c.Resize(w, childrenHeight)
-		}
+		childHeight := vb.boxSize.childSize(i, len(vb.children), h)
+		vb.children[i] = c.Resize(w, childHeight)
 	}
 	return vb
 }
diff --git a/test/cmd/load-test-table/main.go b/test/cmd/load-test-table/main.go
index dfda661..1605966 100644
--- a/test/cmd/load-test-table/main.go
+++ b/test/cmd/load-test-table/main.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/aws/aws-sdk-go-v2/credentials"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
 	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
 	"github.com/brianvoe/gofakeit/v6"
@@ -21,7 +22,9 @@ func main() {
 	tableName := "awstools-test"
 	totalItems := 300
 
-	cfg, err := config.LoadDefaultConfig(ctx)
+	cfg, err := config.LoadDefaultConfig(ctx,
+		config.WithRegion("ap-southeast-2"),
+		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("abc", "123", "")))
 	if err != nil {
 		cli.Fatalf("cannot load AWS config: %v", err)
 	}
@@ -32,7 +35,7 @@ func main() {
 	if _, err = dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
 		TableName: aws.String(tableName),
 	}); err != nil {
-		log.Printf("warn: cannot delete table: %v", tableName)
+		log.Printf("warn: cannot delete table: %v: %v", tableName, err)
 	}
 
 	if _, err = dynamoClient.CreateTable(ctx, &dynamodb.CreateTableInput{