Agreeable goat
This commit is contained in:
84
ui/model.go
84
ui/model.go
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// View modes
|
||||
@@ -24,6 +25,7 @@ const (
|
||||
// Messages
|
||||
type tickMsg time.Time
|
||||
type alarmFiredMsg scheduler.AlarmEvent
|
||||
type alarmsLoadedMsg []db.Alarm
|
||||
|
||||
// Model is the main bubbletea model.
|
||||
type Model struct {
|
||||
@@ -61,14 +63,14 @@ func NewModel(store *db.Store, sched *scheduler.Scheduler, pl *player.Player) Mo
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return tea.Batch(
|
||||
tickEverySecond(),
|
||||
tickEveryHalfSecond(),
|
||||
listenForAlarms(m.scheduler),
|
||||
m.loadAlarms,
|
||||
loadAlarmsCmd(m.store),
|
||||
)
|
||||
}
|
||||
|
||||
func tickEverySecond() tea.Cmd {
|
||||
return tea.Every(time.Second, func(t time.Time) tea.Msg {
|
||||
func tickEveryHalfSecond() tea.Cmd {
|
||||
return tea.Every(500*time.Millisecond, func(t time.Time) tea.Msg {
|
||||
return tickMsg(t)
|
||||
})
|
||||
}
|
||||
@@ -80,13 +82,14 @@ func listenForAlarms(sched *scheduler.Scheduler) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) loadAlarms() tea.Msg {
|
||||
alarms, err := m.store.ListAlarms()
|
||||
if err != nil {
|
||||
return nil
|
||||
func loadAlarmsCmd(store *db.Store) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
alarms, err := store.ListAlarms()
|
||||
if err != nil {
|
||||
return alarmsLoadedMsg(nil)
|
||||
}
|
||||
return alarmsLoadedMsg(alarms)
|
||||
}
|
||||
m.alarms = alarms
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
@@ -96,12 +99,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
|
||||
case alarmsLoadedMsg:
|
||||
if msg != nil {
|
||||
m.alarms = []db.Alarm(msg)
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tickMsg:
|
||||
m.now = time.Time(msg)
|
||||
if m.firingAlarm != nil {
|
||||
m.firingBlink = !m.firingBlink
|
||||
}
|
||||
return m, tickEverySecond()
|
||||
return m, tickEveryHalfSecond()
|
||||
|
||||
case alarmFiredMsg:
|
||||
alarm := msg.Alarm
|
||||
@@ -284,37 +293,64 @@ func (m Model) View() string {
|
||||
help := m.renderHelp()
|
||||
sections = append(sections, "", lipgloss.PlaceHorizontal(m.width, lipgloss.Center, help))
|
||||
|
||||
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Top,
|
||||
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center,
|
||||
strings.Join(sections, "\n"),
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
alarmColPrefix = 6 // "▸ ● " = 4 display cols + 2 padding
|
||||
alarmColTrigger = 18 // enough for cron like "30 7 * * 1-5"
|
||||
alarmColName = 22
|
||||
)
|
||||
|
||||
// padRight pads s to exactly width display columns using runewidth.
|
||||
func padRight(s string, width int) string {
|
||||
w := runewidth.StringWidth(s)
|
||||
if w >= width {
|
||||
return runewidth.Truncate(s, width, "…")
|
||||
}
|
||||
return s + strings.Repeat(" ", width-w)
|
||||
}
|
||||
|
||||
func (m Model) renderAlarmList() string {
|
||||
if len(m.alarms) == 0 {
|
||||
return StatusStyle.Render("No alarms. Press 'a' to add one.")
|
||||
}
|
||||
|
||||
title := TitleStyle.Render("Alarms")
|
||||
|
||||
// Header — prefix padded to same display width as row prefixes
|
||||
header := padRight("", alarmColPrefix) +
|
||||
padRight("Time/Trigger", alarmColTrigger) +
|
||||
padRight("Name", alarmColName) +
|
||||
"Description"
|
||||
|
||||
var lines []string
|
||||
lines = append(lines, title, "")
|
||||
lines = append(lines, DividerStyle.Render(header))
|
||||
|
||||
for i, a := range m.alarms {
|
||||
cursor := " "
|
||||
var prefix string
|
||||
if i == m.cursor {
|
||||
cursor = "▸ "
|
||||
prefix = "▸ "
|
||||
} else {
|
||||
prefix = " "
|
||||
}
|
||||
|
||||
status := "●"
|
||||
var style lipgloss.Style
|
||||
if a.Enabled {
|
||||
prefix += "● "
|
||||
} else {
|
||||
prefix += "○ "
|
||||
}
|
||||
|
||||
var style lipgloss.Style
|
||||
if i == m.cursor {
|
||||
style = AlarmSelectedStyle
|
||||
} else if a.Enabled {
|
||||
style = AlarmEnabledStyle
|
||||
} else {
|
||||
style = AlarmDisabledStyle
|
||||
status = "○"
|
||||
}
|
||||
|
||||
if i == m.cursor {
|
||||
style = AlarmSelectedStyle
|
||||
}
|
||||
|
||||
trigger := a.Time
|
||||
@@ -322,7 +358,11 @@ func (m Model) renderAlarmList() string {
|
||||
trigger = a.Trigger
|
||||
}
|
||||
|
||||
line := fmt.Sprintf("%s%s %s %-20s %s", cursor, status, trigger, a.Name, a.Description)
|
||||
line := padRight(prefix, alarmColPrefix) +
|
||||
padRight(trigger, alarmColTrigger) +
|
||||
padRight(a.Name, alarmColName) +
|
||||
a.Description
|
||||
|
||||
lines = append(lines, style.Render(line))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user