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