Issue 18: Added a popup to modify table columns (#31)

Added a new popup to modify the columns of the table. With this new popup, the user can:

- Show and hide columns
- Move columns around
- Add new columns which are derived from the value of an expression
- Delete columns

Also got the overlay mechanisms working.
This commit is contained in:
Leon Mika 2022-10-04 22:23:48 +11:00 committed by GitHub
parent f373a3313a
commit 982d3a9ca7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1050 additions and 166 deletions

View file

@ -0,0 +1,158 @@
package colselector
import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/models/columns"
"github.com/lmika/audax/internal/dynamo-browse/ui/keybindings"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
table "github.com/lmika/go-bubble-table"
"strings"
)
var frameColor = lipgloss.Color("63")
var frameStyle = lipgloss.NewStyle().
Foreground(frameColor)
var style = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(frameColor)
type colListModel struct {
keyBinding *keybindings.KeyBindings
colController *controllers.ColumnsController
rows []table.Row
table table.Model
}
func newColListModel(keyBinding *keybindings.KeyBindings, colController *controllers.ColumnsController) *colListModel {
tbl := table.New(table.SimpleColumns([]string{"", "Name"}), 100, 100)
tbl.SetRows([]table.Row{})
return &colListModel{
keyBinding: keyBinding,
colController: colController,
table: tbl,
}
}
func (c *colListModel) Init() tea.Cmd {
return nil
}
func (m *colListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
// Column operations
case key.Matches(msg, m.keyBinding.ColumnPopup.ShiftColumnLeft):
return m, events.SetTeaMessage(m.shiftColumnUp(m.table.Cursor()))
case key.Matches(msg, m.keyBinding.ColumnPopup.ShiftColumnRight):
return m, events.SetTeaMessage(m.shiftColumnDown(m.table.Cursor()))
case key.Matches(msg, m.keyBinding.ColumnPopup.ToggleVisible):
return m, events.SetTeaMessage(m.colController.ToggleVisible(m.table.Cursor()))
case key.Matches(msg, m.keyBinding.ColumnPopup.Close):
return m, events.SetTeaMessage(controllers.HideColumnOverlay{})
case key.Matches(msg, m.keyBinding.ColumnPopup.ResetColumns):
return m, events.SetTeaMessage(m.colController.SetColumnsToResultSet())
case key.Matches(msg, m.keyBinding.ColumnPopup.AddColumn):
return m, events.SetTeaMessage(m.colController.AddColumn(m.table.Cursor()))
case key.Matches(msg, m.keyBinding.ColumnPopup.DeleteColumn):
return m, events.SetTeaMessage(m.colController.DeleteColumn(m.table.Cursor()))
// Main table nav
case key.Matches(msg, m.keyBinding.TableView.ColLeft):
return m, events.SetTeaMessage(controllers.MoveLeftmostDisplayedColumnInTableViewBy(-1))
case key.Matches(msg, m.keyBinding.TableView.ColRight):
return m, events.SetTeaMessage(controllers.MoveLeftmostDisplayedColumnInTableViewBy(1))
// Table nav
case key.Matches(msg, m.keyBinding.TableView.MoveUp):
m.table.GoUp()
return m, nil
case key.Matches(msg, m.keyBinding.TableView.MoveDown):
m.table.GoDown()
return m, nil
case key.Matches(msg, m.keyBinding.TableView.PageUp):
m.table.GoPageUp()
return m, nil
case key.Matches(msg, m.keyBinding.TableView.PageDown):
m.table.GoPageDown()
return m, nil
case key.Matches(msg, m.keyBinding.TableView.Home):
m.table.GoTop()
return m, nil
case key.Matches(msg, m.keyBinding.TableView.End):
m.table.GoBottom()
return m, nil
}
}
var cmd tea.Cmd
m.table, cmd = m.table.Update(msg)
return m, cmd
}
func (c *colListModel) View() string {
innerView := lipgloss.JoinVertical(
lipgloss.Top,
lipgloss.PlaceHorizontal(overlayWidth-2, lipgloss.Center, "Columns"),
frameStyle.Render(strings.Repeat(lipgloss.NormalBorder().Top, 48)),
c.table.View(),
)
view := style.Width(overlayWidth - 2).Height(overlayHeight - 2).Render(innerView)
return view
}
func (c *colListModel) Resize(w, h int) layout.ResizingModel {
c.table.SetSize(overlayWidth-4, overlayHeight-4)
return c
}
func (c *colListModel) refreshTable() {
colsFromController := c.colController.Columns()
if len(c.rows) != len(colsFromController.Columns) {
c.setColumnsFromModel(colsFromController)
}
c.table.UpdateView()
}
func (c *colListModel) setColumnsFromModel(cols *columns.Columns) {
if cols == nil {
c.table.SetRows([]table.Row{})
return
}
colNames := make([]table.Row, len(cols.Columns))
for i := range cols.Columns {
colNames[i] = colListRowModel{c}
}
c.rows = colNames
c.table.SetRows(colNames)
if c.table.Cursor() >= len(c.rows) {
c.table.GoBottom()
}
}
func (c *colListModel) shiftColumnUp(cursor int) tea.Msg {
msg := c.colController.ShiftColumnLeft(cursor)
if msg != nil {
c.table.GoUp()
}
return msg
}
func (c *colListModel) shiftColumnDown(cursor int) tea.Msg {
msg := c.colController.ShiftColumnRight(cursor)
if msg != nil {
c.table.GoDown()
}
return msg
}

View file

@ -0,0 +1,74 @@
package colselector
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/ui/keybindings"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils"
)
const (
overlayWidth = 50
overlayHeight = 25
)
type Model struct {
columnsController *controllers.ColumnsController
subModel tea.Model
colListModel *colListModel
compositor *layout.Compositor
w, h int
}
func New(submodel tea.Model, keyBinding *keybindings.KeyBindings, columnsController *controllers.ColumnsController) *Model {
colListModel := newColListModel(keyBinding, columnsController)
compositor := layout.NewCompositor(submodel)
return &Model{
columnsController: columnsController,
subModel: submodel,
compositor: compositor,
colListModel: colListModel,
}
}
func (m *Model) Init() tea.Cmd {
return m.subModel.Init()
}
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector
switch msg := msg.(type) {
case controllers.ShowColumnOverlay:
m.colListModel.setColumnsFromModel(m.columnsController.Columns())
m.compositor.SetOverlay(m.colListModel, m.w/2-overlayWidth/2, m.h/2-overlayHeight/2, overlayWidth, overlayHeight)
case controllers.HideColumnOverlay:
m.compositor.ClearOverlay()
case controllers.ColumnsUpdated:
m.colListModel.refreshTable()
m.subModel = cc.Collect(m.subModel.Update(msg))
case tea.KeyMsg:
m.compositor = cc.Collect(m.compositor.Update(msg)).(*layout.Compositor)
default:
m.subModel = cc.Collect(m.subModel.Update(msg))
}
return m, cc.Cmd()
}
func (m *Model) View() string {
return m.compositor.View()
}
func (m *Model) Resize(w, h int) layout.ResizingModel {
m.w, m.h = w, h
m.compositor.MoveOverlay(m.w/2-overlayWidth/2, m.h/2-overlayHeight/2)
m.subModel = layout.Resize(m.subModel, w, h)
m.colListModel = layout.Resize(m.colListModel, w, h).(*colListModel)
return m
}
func (m *Model) ColSelectorVisible() bool {
return m.compositor.HasOverlay()
}

View file

@ -0,0 +1,31 @@
package colselector
import (
"fmt"
"github.com/charmbracelet/lipgloss"
table "github.com/lmika/go-bubble-table"
"io"
)
type colListRowModel struct {
m *colListModel
}
func (clr colListRowModel) Render(w io.Writer, model table.Model, index int) {
cols := clr.m.colController.Columns()
if cols == nil {
return
}
var style lipgloss.Style
if index == model.Cursor() {
style = model.Styles.SelectedRow
}
col := clr.m.colController.Columns().Columns[index]
if !col.Hidden {
fmt.Fprintln(w, style.Render(fmt.Sprintf(".\t%v", col.Name)))
} else {
fmt.Fprintln(w, style.Render(fmt.Sprintf("✕\t%v", col.Name)))
}
}