|
|
@@ -1,8 +1,4 @@
|
|
|
// A simple TUI application that charts latency times to specified hosts.
|
|
|
-//
|
|
|
-// i.e, change the address title to red so long as there is a single -1 in
|
|
|
-// the display buffer, and a toast notification (???) if a display buffer is
|
|
|
-// entirely composed of -1
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
@@ -17,14 +13,18 @@ import (
|
|
|
"charm.land/lipgloss/v2"
|
|
|
)
|
|
|
|
|
|
-type md struct {
|
|
|
+// the bubbletea.Model for the executable
|
|
|
+type Model struct {
|
|
|
viewport viewport.Model
|
|
|
speed time.Duration
|
|
|
p pingo.Peak
|
|
|
}
|
|
|
|
|
|
+// timingMsg is used for tracking Peak.Poll timing intervals, emitted by an
|
|
|
+// anonymous function in the update function.
|
|
|
type timingMsg time.Time
|
|
|
|
|
|
+// lipgloss styles for the tui
|
|
|
var (
|
|
|
// footer styles
|
|
|
titleStyle = lipgloss.NewStyle().
|
|
|
@@ -39,48 +39,59 @@ var (
|
|
|
Faint(true)
|
|
|
)
|
|
|
|
|
|
-func (m md) Init() tea.Cmd {
|
|
|
+// The tea.Cmd command to be called upon model initialization by bubbletea.
|
|
|
+func (m Model) Init() tea.Cmd {
|
|
|
return tea.Tick(m.speed*time.Millisecond, func(t time.Time) tea.Msg { return timingMsg(t) })
|
|
|
}
|
|
|
|
|
|
-func (m md) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
- var cmds []tea.Cmd
|
|
|
+// The core Update loop function.
|
|
|
+func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
+ // data prep
|
|
|
+ var cmds []tea.Cmd // final return
|
|
|
var cmd tea.Cmd
|
|
|
|
|
|
switch msg := msg.(type) {
|
|
|
- case tea.WindowSizeMsg:
|
|
|
+ case tea.WindowSizeMsg: // handle window resizing
|
|
|
+ // update pingo.Peak bubble
|
|
|
m.p.Width = msg.Width
|
|
|
m.p.Height = msg.Height - m.getVerticalMargin() - 1
|
|
|
+
|
|
|
+ // if the viewport has not been initialized
|
|
|
if m.viewport.Width() == 0 && m.viewport.Height() == 0 {
|
|
|
- m.viewport = viewport.New(
|
|
|
+ m.viewport = viewport.New( // init viewport
|
|
|
viewport.WithHeight(msg.Height-m.getVerticalMargin()),
|
|
|
viewport.WithWidth(msg.Width),
|
|
|
)
|
|
|
+ m.viewport.YPosition = 1 // tell viewport to render after an empty line
|
|
|
+ } else {
|
|
|
+ m.viewport.SetHeight(msg.Height - m.getVerticalMargin())
|
|
|
+ m.viewport.SetWidth(msg.Width)
|
|
|
}
|
|
|
- m.viewport.SetHeight(msg.Height - m.getVerticalMargin())
|
|
|
- m.viewport.SetWidth(msg.Width)
|
|
|
- m.viewport.YPosition = 1
|
|
|
- case tea.KeyPressMsg:
|
|
|
+
|
|
|
+ case tea.KeyPressMsg: // handle quit
|
|
|
k := msg.String()
|
|
|
if k == "ctrl+c" {
|
|
|
return m, tea.Quit
|
|
|
}
|
|
|
- case timingMsg:
|
|
|
+
|
|
|
+ case timingMsg: // handle timingMsg (timing loop)
|
|
|
cmd = tea.Tick(m.speed*time.Millisecond, func(t time.Time) tea.Msg { return timingMsg(t) })
|
|
|
cmds = append(cmds, tea.Sequence(m.p.Poll(), cmd))
|
|
|
}
|
|
|
|
|
|
+ // update viewport bubble
|
|
|
m.viewport.SetContent(m.p.View().Content)
|
|
|
m.viewport, cmd = m.viewport.Update(msg)
|
|
|
cmds = append(cmds, cmd)
|
|
|
|
|
|
+ // update Peak bubble
|
|
|
m.p, cmd = m.p.Update(msg)
|
|
|
cmds = append(cmds, cmd)
|
|
|
|
|
|
return m, tea.Batch(cmds...)
|
|
|
}
|
|
|
|
|
|
-func (m md) View() tea.View {
|
|
|
+func (m Model) View() tea.View {
|
|
|
m.viewport.SetContent(m.p.View().Content)
|
|
|
content := fmt.Sprintf("%s%s\n%s", m.header(), m.viewport.View(), m.footer())
|
|
|
|
|
|
@@ -90,16 +101,18 @@ func (m md) View() tea.View {
|
|
|
return v
|
|
|
}
|
|
|
|
|
|
-func (m md) header() string { return titleStyle.Width(m.viewport.Width()).Render("pingo v0") }
|
|
|
+func (m Model) header() string { return titleStyle.Width(m.viewport.Width()).Render("pingo v0") }
|
|
|
|
|
|
-func (m md) footer() string {
|
|
|
+func (m Model) footer() string {
|
|
|
return footerStyle.Width(m.viewport.Width()).Render("j/k: down/up\t|\tctrl+c: quit")
|
|
|
}
|
|
|
|
|
|
-func (m md) getVerticalMargin() int { return lipgloss.Height(m.header() + m.footer()) }
|
|
|
+// get lipgloss.Height value of header and footer
|
|
|
+func (m Model) getVerticalMargin() int { return lipgloss.Height(m.header() + m.footer()) }
|
|
|
|
|
|
func main() {
|
|
|
- speed := flag.Int("s", 80, "the speed with which the UI runs")
|
|
|
+ // get timing interval in milliseconds
|
|
|
+ speed := flag.Int("s", 80, "the interval in milliseconds between pings")
|
|
|
chartHeight := flag.Int("h", 0,
|
|
|
"the height of the latency chart. set to 0 to render charts full screen.")
|
|
|
flag.Parse()
|
|
|
@@ -110,7 +123,15 @@ func main() {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- p := tea.NewProgram(md{p: pingo.InitialPeakBubble(hosts, 20, 10, *chartHeight), speed: time.Duration(*speed)})
|
|
|
+ if *speed < 1 {
|
|
|
+ fmt.Println("speed must not be below 1")
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+
|
|
|
+ p := tea.NewProgram(Model{
|
|
|
+ p: pingo.InitialPeakBubble(hosts, 20, 10, *chartHeight),
|
|
|
+ speed: time.Duration(*speed),
|
|
|
+ })
|
|
|
|
|
|
if _, err := p.Run(); err != nil {
|
|
|
fmt.Printf("Alas, there's been an error: %v", err)
|