table.go

  1package logs
  2
  3import (
  4	"encoding/json"
  5	"slices"
  6
  7	"github.com/charmbracelet/bubbles/key"
  8	"github.com/charmbracelet/bubbles/table"
  9	tea "github.com/charmbracelet/bubbletea"
 10	"github.com/kujtimiihoxha/termai/internal/logging"
 11	"github.com/kujtimiihoxha/termai/internal/pubsub"
 12	"github.com/kujtimiihoxha/termai/internal/tui/layout"
 13	"github.com/kujtimiihoxha/termai/internal/tui/styles"
 14	"github.com/kujtimiihoxha/termai/internal/tui/util"
 15)
 16
 17type TableComponent interface {
 18	tea.Model
 19	layout.Focusable
 20	layout.Sizeable
 21	layout.Bindings
 22	layout.Bordered
 23}
 24
 25type tableCmp struct {
 26	table table.Model
 27}
 28
 29func (i *tableCmp) BorderText() map[layout.BorderPosition]string {
 30	return map[layout.BorderPosition]string{
 31		layout.TopLeftBorder: "Logs",
 32	}
 33}
 34
 35type selectedLogMsg logging.LogMessage
 36
 37func (i *tableCmp) Init() tea.Cmd {
 38	i.setRows()
 39	return nil
 40}
 41
 42func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 43	var cmds []tea.Cmd
 44	if i.table.Focused() {
 45		switch msg.(type) {
 46		case pubsub.Event[logging.LogMessage]:
 47			i.setRows()
 48			return i, nil
 49		}
 50		prevSelectedRow := i.table.SelectedRow()
 51		t, cmd := i.table.Update(msg)
 52		cmds = append(cmds, cmd)
 53		i.table = t
 54		selectedRow := i.table.SelectedRow()
 55		if selectedRow != nil {
 56			if prevSelectedRow == nil || selectedRow[0] == prevSelectedRow[0] {
 57				var log logging.LogMessage
 58				for _, row := range logging.List() {
 59					if row.ID == selectedRow[0] {
 60						log = row
 61						break
 62					}
 63				}
 64				if log.ID != "" {
 65					cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
 66				}
 67			}
 68		}
 69	}
 70	return i, tea.Batch(cmds...)
 71}
 72
 73func (i *tableCmp) View() string {
 74	return i.table.View()
 75}
 76
 77func (i *tableCmp) Blur() tea.Cmd {
 78	i.table.Blur()
 79	return nil
 80}
 81
 82func (i *tableCmp) Focus() tea.Cmd {
 83	i.table.Focus()
 84	return nil
 85}
 86
 87func (i *tableCmp) IsFocused() bool {
 88	return i.table.Focused()
 89}
 90
 91func (i *tableCmp) GetSize() (int, int) {
 92	return i.table.Width(), i.table.Height()
 93}
 94
 95func (i *tableCmp) SetSize(width int, height int) {
 96	i.table.SetWidth(width)
 97	i.table.SetHeight(height)
 98	cloumns := i.table.Columns()
 99	for i, col := range cloumns {
100		col.Width = (width / len(cloumns)) - 2
101		cloumns[i] = col
102	}
103	i.table.SetColumns(cloumns)
104}
105
106func (i *tableCmp) BindingKeys() []key.Binding {
107	return layout.KeyMapToSlice(i.table.KeyMap)
108}
109
110func (i *tableCmp) setRows() {
111	rows := []table.Row{}
112
113	logs := logging.List()
114	slices.SortFunc(logs, func(a, b logging.LogMessage) int {
115		if a.Time.Before(b.Time) {
116			return 1
117		}
118		if a.Time.After(b.Time) {
119			return -1
120		}
121		return 0
122	})
123
124	for _, log := range logs {
125		bm, _ := json.Marshal(log.Attributes)
126
127		row := table.Row{
128			log.ID,
129			log.Time.Format("15:04:05"),
130			log.Level,
131			log.Message,
132			string(bm),
133		}
134		rows = append(rows, row)
135	}
136	i.table.SetRows(rows)
137}
138
139func NewLogsTable() TableComponent {
140	columns := []table.Column{
141		{Title: "ID", Width: 4},
142		{Title: "Time", Width: 4},
143		{Title: "Level", Width: 10},
144		{Title: "Message", Width: 10},
145		{Title: "Attributes", Width: 10},
146	}
147	defaultStyles := table.DefaultStyles()
148	defaultStyles.Selected = defaultStyles.Selected.Foreground(styles.Primary)
149	tableModel := table.New(
150		table.WithColumns(columns),
151		table.WithStyles(defaultStyles),
152	)
153	return &tableCmp{
154		table: tableModel,
155	}
156}