dynamo-browse/internal/dynamo-browse/ui/model.go
Leon Mika c8b65f6b0a
All checks were successful
ci / Build (push) Successful in 3m40s
Added some quality of life changes
- Fixed the 'M' bind to mark all/none items, rather than toggle
- Fixed panic which was raised when reqesting @item with an index beyond the length of resultset
- Added changing the table when calling @table with a table or string name
2025-10-29 22:15:26 +11:00

233 lines
8.4 KiB
Go

package ui
import (
"log"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
bus "github.com/lmika/events"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/itemrenderer"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/colselector"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/dialogprompt"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamoitemedit"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamoitemview"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamotableview"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/relselector"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/statusandprompt"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/tableselect"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils"
)
const (
ViewModeTablePrimary = 0
ViewModeTableItemEqual = 1
ViewModeItemPrimary = 2
ViewModeItemOnly = 3
ViewModeTableOnly = 4
ViewModeCount = 5
)
type Model struct {
tableReadController *controllers.TableReadController
tableWriteController *controllers.TableWriteController
settingsController *controllers.SettingsController
exportController *controllers.ExportController
commandController *commandctrl.CommandController
jobController *controllers.JobsController
colSelector *colselector.Model
relSelector *relselector.Model
itemEdit *dynamoitemedit.Model
statusAndPrompt *statusandprompt.StatusAndPrompt
tableSelect *tableselect.Model
eventBus *bus.Bus
mainViewIndex int
root tea.Model
tableView *dynamotableview.Model
itemView *dynamoitemview.Model
mainView tea.Model
keyMap *keybindings.ViewKeyBindings
keyBindingController *controllers.KeyBindingController
}
func NewModel(
rc *controllers.TableReadController,
wc *controllers.TableWriteController,
columnsController *controllers.ColumnsController,
exportController *controllers.ExportController,
settingsController *controllers.SettingsController,
jobController *controllers.JobsController,
itemRendererService *itemrenderer.Service,
cc *commandctrl.CommandController,
eventBus *bus.Bus,
keyBindingController *controllers.KeyBindingController,
pasteboardProvider services.PasteboardProvider,
defaultKeyMap *keybindings.KeyBindings,
) Model {
uiStyles := styles.DefaultStyles
dtv := dynamotableview.New(defaultKeyMap.TableView, columnsController, settingsController, eventBus, uiStyles)
div := dynamoitemview.New(itemRendererService, uiStyles)
mainView := layout.NewVBox(layout.LastChildFixedAt(14), dtv, div)
colSelector := colselector.New(mainView, defaultKeyMap, columnsController)
relSelector := relselector.New(colSelector)
itemEdit := dynamoitemedit.NewModel(relSelector)
statusAndPrompt := statusandprompt.New(itemEdit, pasteboardProvider, "", uiStyles.StatusAndPrompt)
dialogPrompt := dialogprompt.New(statusAndPrompt)
tableSelect := tableselect.New(dialogPrompt, uiStyles)
root := layout.FullScreen(tableSelect)
return Model{
tableReadController: rc,
tableWriteController: wc,
commandController: cc,
exportController: exportController,
jobController: jobController,
itemEdit: itemEdit,
colSelector: colSelector,
relSelector: relSelector,
statusAndPrompt: statusAndPrompt,
tableSelect: tableSelect,
root: root,
tableView: dtv,
itemView: div,
mainView: mainView,
keyMap: defaultKeyMap.View,
keyBindingController: keyBindingController,
}
}
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 events.ResultSetUpdated:
return m, tea.Batch(
m.tableView.Refresh(),
events.SetStatus(msg.StatusMessage),
)
case tea.KeyMsg:
// TODO: use modes here
if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() && !m.colSelector.ColSelectorVisible() && !m.relSelector.SelectorVisible() {
switch {
case key.Matches(msg, m.keyMap.Mark):
if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
return m, events.SetTeaMessage(m.tableWriteController.ToggleMark(idx))
}
case key.Matches(msg, m.keyMap.CopyItemToClipboard):
if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
return m, events.SetTeaMessage(m.tableReadController.CopyItemToClipboard(idx))
}
case key.Matches(msg, m.keyMap.CopyTableToClipboard):
return m, events.SetTeaMessage(m.exportController.ExportCSVToClipboard())
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.PromptForFilter
case key.Matches(msg, m.keyMap.FetchNextPage):
return m, m.tableReadController.NextPage
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, events.SetTeaMessage(controllers.SetTableItemView{ViewIndex: utils.Cycle(m.mainViewIndex, 1, ViewModeCount)})
case key.Matches(msg, m.keyMap.CycleLayoutBackwards):
return m, events.SetTeaMessage(controllers.SetTableItemView{ViewIndex: utils.Cycle(m.mainViewIndex, -1, ViewModeCount)})
//case "e":
// m.itemEdit.Visible()
// return m, nil
case key.Matches(msg, m.keyMap.ShowColumnOverlay):
return m, events.SetTeaMessage(controllers.ShowColumnOverlay{})
//case key.Matches(msg, m.keyMap.ShowRelItemsOverlay):
// if idx := m.tableView.SelectedItemIndex(); idx >= 0 {
// return m, events.SetTeaMessage(m.scriptController.LookupRelatedItems(idx))
// }
case key.Matches(msg, m.keyMap.PromptForCommand):
return m, m.commandController.Prompt
case key.Matches(msg, m.keyMap.PromptForTable):
return m, events.SetTeaMessage(m.tableReadController.ListTables(false))
case key.Matches(msg, m.keyMap.CancelRunningJob):
return m, events.SetTeaMessage(m.jobController.CancelRunningJob(m.promptToQuit))
case key.Matches(msg, m.keyMap.Quit):
return m, m.promptToQuit
default:
if cmd := m.keyBindingController.LookupCustomBinding(msg.String()); cmd != nil {
return m, cmd
}
}
}
}
var cmd tea.Cmd
m.root, cmd = m.root.Update(msg)
return m, cmd
}
func (m Model) Init() tea.Cmd {
return tea.Batch(
m.tableReadController.Init,
m.root.Init(),
)
}
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()
}
func (m *Model) promptToQuit() tea.Msg {
return events.Confirm("Quit dynamo-browse? ", func(yes bool) tea.Msg {
if yes {
return tea.Quit()
}
return nil
})
}
func (m *Model) SelectedItemIndex() int {
return m.tableView.SelectedItemIndex()
}
func (m *Model) SetSelectedItemIndex(newIdx int) tea.Msg {
return m.tableView.SetSelectedItemIndex(newIdx)
}