dynamo-browse/internal/dynamo-browse/ui/model.go
Leon Mika 7c5bfd27a3 issue-9: finishing keybinding service and implemented controller
Have now got rebinding keys working with the "rebind" command.
Still need to make sure key names are correct and implement rebinding
as part of an RC file and add bindings for the table.
2022-08-25 22:14:36 +10:00

249 lines
7.6 KiB
Go

package ui
import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/common/ui/commandctrl"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/models"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dialogprompt"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamoitemedit"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamoitemview"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamotableview"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/statusandprompt"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/tableselect"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils"
"github.com/pkg/errors"
"log"
"strings"
)
const (
ViewModeTablePrimary = 0
ViewModeTableItemEqual = 1
ViewModeItemPrimary = 2
ViewModeItemOnly = 3
ViewModeTableOnly = 4
ViewModeCount = 5
)
type Model struct {
tableReadController *controllers.TableReadController
tableWriteController *controllers.TableWriteController
commandController *commandctrl.CommandController
itemEdit *dynamoitemedit.Model
statusAndPrompt *statusandprompt.StatusAndPrompt
tableSelect *tableselect.Model
mainViewIndex int
root tea.Model
tableView *dynamotableview.Model
itemView *dynamoitemview.Model
mainView tea.Model
keyMap *ViewKeyBindings
}
func NewModel(
rc *controllers.TableReadController,
wc *controllers.TableWriteController,
itemRendererService *itemrenderer.Service,
cc *commandctrl.CommandController,
keyBindingController *controllers.KeyBindingController,
defaultKeyMap *KeyBindings,
) Model {
uiStyles := styles.DefaultStyles
dtv := dynamotableview.New(uiStyles)
div := dynamoitemview.New(itemRendererService, uiStyles)
mainView := layout.NewVBox(layout.LastChildFixedAt(14), dtv, div)
itemEdit := dynamoitemedit.NewModel(mainView)
statusAndPrompt := statusandprompt.New(itemEdit, "", uiStyles.StatusAndPrompt)
dialogPrompt := dialogprompt.New(statusAndPrompt)
tableSelect := tableselect.New(dialogPrompt, uiStyles)
cc.AddCommands(&commandctrl.CommandContext{
Commands: map[string]commandctrl.Command{
"quit": commandctrl.NoArgCommand(tea.Quit),
"table": func(args []string) tea.Msg {
if len(args) == 0 {
return rc.ListTables()
} else {
return rc.ScanTable(args[0])
}
},
"export": func(args []string) tea.Msg {
if len(args) == 0 {
return events.Error(errors.New("expected filename"))
}
return rc.ExportCSV(args[0])
},
"unmark": commandctrl.NoArgCommand(rc.Unmark),
"delete": commandctrl.NoArgCommand(wc.DeleteMarked),
// TEMP
"new-item": commandctrl.NoArgCommand(wc.NewItem),
"set-attr": func(args []string) tea.Msg {
if len(args) == 0 {
return events.Error(errors.New("expected field"))
}
var itemType = models.UnsetItemType
if len(args) == 2 {
switch strings.ToUpper(args[0]) {
case "-S":
itemType = models.StringItemType
case "-N":
itemType = models.NumberItemType
case "-BOOL":
itemType = models.BoolItemType
case "-NULL":
itemType = models.NullItemType
default:
return events.Error(errors.New("unrecognised item type"))
}
args = args[1:]
}
return wc.SetAttributeValue(dtv.SelectedItemIndex(), itemType, args[0])
},
"del-attr": func(args []string) tea.Msg {
if len(args) == 0 {
return events.Error(errors.New("expected field"))
}
return wc.DeleteAttribute(dtv.SelectedItemIndex(), args[0])
},
"put": func(args []string) tea.Msg {
return wc.PutItems()
},
"touch": func(args []string) tea.Msg {
return wc.TouchItem(dtv.SelectedItemIndex())
},
"noisy-touch": func(args []string) tea.Msg {
return wc.NoisyTouchItem(dtv.SelectedItemIndex())
},
"rebind": func(args []string) tea.Msg {
if len(args) != 2 {
return events.Error(errors.New("expected: name newKey"))
}
return keyBindingController.Rebind(args[0], args[1])
},
// Aliases
"sa": cc.Alias("set-attr"),
"da": cc.Alias("del-attr"),
"w": cc.Alias("put"),
"q": cc.Alias("quit"),
},
})
root := layout.FullScreen(tableSelect)
return Model{
tableReadController: rc,
tableWriteController: wc,
commandController: cc,
itemEdit: itemEdit,
statusAndPrompt: statusAndPrompt,
tableSelect: tableSelect,
root: root,
tableView: dtv,
itemView: div,
mainView: mainView,
keyMap: defaultKeyMap.View,
}
}
func (m Model) Init() tea.Cmd {
return m.tableReadController.Init
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case controllers.SetTableItemView:
cmd := m.setMainViewIndex(msg.ViewIndex)
return m, cmd
case controllers.ResultSetUpdated:
return m, m.tableView.Refresh()
case tea.KeyMsg:
if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() {
switch {
case key.Matches(msg, m.keyMap.Mark):
if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
return m, func() tea.Msg { return m.tableWriteController.ToggleMark(idx) }
}
case key.Matches(msg, m.keyMap.CopyItemToClipboard):
if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
return m, func() tea.Msg { return m.tableReadController.CopyItemToClipboard(idx) }
}
case key.Matches(msg, m.keyMap.Rescan):
return m, m.tableReadController.Rescan
case key.Matches(msg, m.keyMap.PromptForQuery):
return m, m.tableReadController.PromptForQuery
case key.Matches(msg, m.keyMap.PromptForFilter):
return m, m.tableReadController.Filter
case key.Matches(msg, m.keyMap.ViewBack):
return m, m.tableReadController.ViewBack
case key.Matches(msg, m.keyMap.ViewForward):
return m, m.tableReadController.ViewForward
case key.Matches(msg, m.keyMap.CycleLayoutForward):
return m, func() tea.Msg {
return controllers.SetTableItemView{ViewIndex: utils.Cycle(m.mainViewIndex, 1, ViewModeCount)}
}
case key.Matches(msg, m.keyMap.CycleLayoutBackwards):
return m, func() tea.Msg {
return controllers.SetTableItemView{ViewIndex: utils.Cycle(m.mainViewIndex, -1, ViewModeCount)}
}
//case "e":
// m.itemEdit.Visible()
// return m, nil
case key.Matches(msg, m.keyMap.PromptForCommand):
return m, m.commandController.Prompt
case key.Matches(msg, m.keyMap.Quit):
return m, tea.Quit
}
}
}
var cmd tea.Cmd
m.root, cmd = m.root.Update(msg)
return m, cmd
}
func (m Model) View() string {
return m.root.View()
}
func (m *Model) setMainViewIndex(viewIndex int) tea.Cmd {
log.Printf("setting view index = %v", viewIndex)
var newMainView tea.Model
switch viewIndex {
case ViewModeTablePrimary:
newMainView = layout.NewVBox(layout.LastChildFixedAt(14), m.tableView, m.itemView)
case ViewModeTableItemEqual:
newMainView = layout.NewVBox(layout.EqualSize(), m.tableView, m.itemView)
case ViewModeItemPrimary:
newMainView = layout.NewVBox(layout.FirstChildFixedAt(7), m.tableView, m.itemView)
case ViewModeItemOnly:
newMainView = layout.NewZStack(m.itemView, m.tableView)
case ViewModeTableOnly:
newMainView = layout.NewZStack(m.tableView, m.tableView)
default:
newMainView = m.mainView
}
m.mainViewIndex = viewIndex
m.mainView = newMainView
m.itemEdit.SetSubmodel(m.mainView)
return m.tableView.Refresh()
}