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