Sam Fredrickson
270534c0d5
All checks were successful
Build & Test / Main (push) Successful in 59s
Also, add a quick-and-dirty model for displaying basic performance stats, currently just the number of calls to the root Update() and View() methods.
210 lines
4.2 KiB
Go
210 lines
4.2 KiB
Go
package asset
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"code.humancabbage.net/sam/moonmath/coindesk"
|
|
"code.humancabbage.net/sam/moonmath/config"
|
|
"code.humancabbage.net/sam/moonmath/moon"
|
|
"github.com/charmbracelet/bubbles/spinner"
|
|
"github.com/charmbracelet/bubbles/table"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
type Model struct {
|
|
math moon.Math
|
|
|
|
refreshing bool
|
|
indicator spinner.Model
|
|
|
|
properties table.Model
|
|
projections table.Model
|
|
}
|
|
|
|
type Msg struct {
|
|
Asset coindesk.Asset
|
|
inner tea.Msg
|
|
}
|
|
|
|
func New(cfg config.Data) (m Model) {
|
|
m.math = moon.NewMath(
|
|
cfg.Asset,
|
|
cfg.Goals,
|
|
config.GetBases(&cfg),
|
|
)
|
|
|
|
tableStyle := table.DefaultStyles()
|
|
tableStyle.Selected = tableStyle.Cell.Copy().
|
|
Padding(0)
|
|
tableStyle.Header = tableStyle.Header.
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("214")).
|
|
Border(baseStyle.GetBorderStyle(), false, false, true, false)
|
|
|
|
// properties table
|
|
|
|
m.properties = table.New(
|
|
table.WithColumns([]table.Column{
|
|
{Title: "Property", Width: 9},
|
|
{Title: "Value", Width: 9},
|
|
}),
|
|
table.WithHeight(2),
|
|
table.WithStyles(tableStyle),
|
|
)
|
|
|
|
// projections table
|
|
|
|
labelsWidth := 0
|
|
for _, l := range m.math.Labels {
|
|
if len(l) > labelsWidth {
|
|
labelsWidth = len(l)
|
|
}
|
|
}
|
|
projectionCols := []table.Column{
|
|
{Title: "Labels", Width: labelsWidth},
|
|
}
|
|
for _, c := range m.math.Columns {
|
|
projectionCols = append(projectionCols,
|
|
table.Column{
|
|
Title: c.Base.Label(),
|
|
Width: 10,
|
|
})
|
|
}
|
|
m.projections = table.New(
|
|
table.WithColumns(projectionCols),
|
|
table.WithHeight(len(m.math.Labels)),
|
|
table.WithStyles(tableStyle),
|
|
)
|
|
|
|
// indicator spinner
|
|
|
|
m.indicator = spinner.New()
|
|
m.indicator.Spinner = spinner.Points
|
|
m.indicator.Style = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("69"))
|
|
|
|
return
|
|
}
|
|
|
|
func (m Model) Handles(a coindesk.Asset) bool {
|
|
return m.math.Asset == a
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return tea.Batch(
|
|
m.indicator.Tick,
|
|
func() tea.Msg {
|
|
return Msg{m.math.Asset, refresh{}}
|
|
},
|
|
)
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case Msg:
|
|
switch msg := msg.inner.(type) {
|
|
case refresh:
|
|
m.refreshing = true
|
|
return m, tea.Batch(
|
|
func() tea.Msg {
|
|
return m.indicator.Tick()
|
|
},
|
|
func() tea.Msg {
|
|
// TODO: log errors
|
|
_ = m.math.Refresh(context.TODO())
|
|
return Msg{m.math.Asset, m.math}
|
|
},
|
|
)
|
|
case moon.Math:
|
|
m.math = msg
|
|
refillProperties(&m)
|
|
refillProjections(&m)
|
|
return m, tea.Batch(
|
|
// schedule the next refresh
|
|
tea.Tick(time.Second*30,
|
|
func(t time.Time) tea.Msg {
|
|
return Msg{m.math.Asset, refresh{}}
|
|
}),
|
|
// wait a bit to stop the indicator, so that it's more obvious
|
|
// even when the refresh completes quickly.
|
|
tea.Tick(time.Millisecond*500,
|
|
func(t time.Time) tea.Msg {
|
|
return Msg{m.math.Asset, stopIndicator{}}
|
|
}),
|
|
)
|
|
case stopIndicator:
|
|
m.refreshing = false
|
|
return m, nil
|
|
}
|
|
case spinner.TickMsg:
|
|
if !m.refreshing {
|
|
return m, nil
|
|
}
|
|
var cmd tea.Cmd
|
|
m.indicator, cmd = m.indicator.Update(msg)
|
|
return m, cmd
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
type refresh struct{}
|
|
type stopIndicator struct{}
|
|
|
|
func refillProperties(m *Model) {
|
|
rows := []table.Row{
|
|
{"Asset", string(m.math.Asset)},
|
|
{"Price", fmt.Sprintf("$%0.2f", m.math.CurrentPrice)},
|
|
}
|
|
m.properties.SetRows(rows)
|
|
}
|
|
|
|
func refillProjections(m *Model) {
|
|
rows := []table.Row{m.math.Labels}
|
|
for i := range m.math.Columns {
|
|
rows = append(rows, m.math.Columns[i].Column())
|
|
}
|
|
rows = transpose(rows)
|
|
m.projections.SetRows(rows)
|
|
}
|
|
|
|
func transpose(slice []table.Row) []table.Row {
|
|
xl := len(slice[0])
|
|
yl := len(slice)
|
|
result := make([]table.Row, xl)
|
|
for i := range result {
|
|
result[i] = make(table.Row, yl)
|
|
}
|
|
for i := 0; i < xl; i++ {
|
|
for j := 0; j < yl; j++ {
|
|
result[i][j] = slice[j][i]
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
var s string
|
|
indicator := ""
|
|
if m.refreshing {
|
|
indicator = m.indicator.View()
|
|
}
|
|
right := lipgloss.JoinVertical(
|
|
lipgloss.Center,
|
|
baseStyle.Render(m.properties.View()),
|
|
indicator,
|
|
)
|
|
s += lipgloss.JoinHorizontal(
|
|
lipgloss.Center,
|
|
right,
|
|
baseStyle.Render(m.projections.View()),
|
|
)
|
|
return s + "\n"
|
|
}
|
|
|
|
var baseStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("240"))
|