This commit is contained in:
@@ -252,7 +252,7 @@ func writeWrappedField(b *strings.Builder, label, value string, width, labelWidt
|
||||
}
|
||||
}
|
||||
|
||||
func renderFooter(t Theme, filtering, searching bool, filter string, follow bool, detailOpen bool, mouseEnabled bool, tab ViewTab, feedMeshVisible bool, historyNote string) string {
|
||||
func renderFooter(t Theme, filtering, searching bool, filter string, follow bool, detailOpen bool, mouseEnabled bool, tab ViewTab, feedMeshVisible, soundEnabled bool, historyNote string) string {
|
||||
var hints []string
|
||||
if searching {
|
||||
hints = []string{"type id…", "Enter lookup", "Esc cancel"}
|
||||
@@ -269,6 +269,7 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
|
||||
if tab == TabFeed {
|
||||
hints = append(hints, "v mesh")
|
||||
}
|
||||
hints = append(hints, "e sound")
|
||||
hints = append(hints, "m mouse")
|
||||
if filtering {
|
||||
hints = []string{"type filter…", "Enter apply", "Esc cancel"}
|
||||
@@ -286,6 +287,9 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
|
||||
if tab == TabFeed && feedMeshVisible {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[mesh]")
|
||||
}
|
||||
if soundEnabled {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[sound]")
|
||||
}
|
||||
if historyNote != "" {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Muted).Render("[" + historyNote + "]")
|
||||
}
|
||||
@@ -304,7 +308,8 @@ func renderHelp(t Theme) string {
|
||||
PgUp Load older attacks (Feed) / moderation (Incidents) / more posts (Blog)
|
||||
|
||||
Feed tab
|
||||
v Toggle peer mesh animation above the feed
|
||||
v Toggle peer mesh animation below the feed
|
||||
e Toggle event sounds (attack, block, peer, moderation)
|
||||
|
||||
Blog tab
|
||||
Enter Read selected briefing
|
||||
@@ -346,6 +351,7 @@ func renderHelp(t Theme) string {
|
||||
r Force SSE reconnect
|
||||
|
||||
General
|
||||
e Toggle synthesized event sounds (matches web viz)
|
||||
? Toggle this help
|
||||
q, Ctrl+C Quit
|
||||
`
|
||||
|
||||
@@ -48,7 +48,7 @@ func (m Model) chromeBottom() string {
|
||||
if m.statusErr != "" {
|
||||
parts = append(parts, renderStatusErr(m.theme, m.statusErr))
|
||||
}
|
||||
parts = append(parts, renderFooter(m.theme, m.filtering, m.searching, m.filterQuery, m.follow, m.detailOpen, m.mouseEnabled, m.tab, m.feedMeshVisible, m.historyFooterNote()))
|
||||
parts = append(parts, renderFooter(m.theme, m.filtering, m.searching, m.filterQuery, m.follow, m.detailOpen, m.mouseEnabled, m.tab, m.feedMeshVisible, m.sound.Enabled(), m.historyFooterNote()))
|
||||
if m.filtering {
|
||||
parts = append(parts, m.filterInput.View())
|
||||
}
|
||||
|
||||
+30
-6
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/honeypeer/cli-viz/internal/config"
|
||||
"github.com/honeypeer/cli-viz/internal/mesh"
|
||||
"github.com/honeypeer/cli-viz/internal/sound"
|
||||
"github.com/honeypeer/cli-viz/internal/transport"
|
||||
"github.com/honeypeer/cli-viz/internal/viz"
|
||||
)
|
||||
@@ -63,6 +64,7 @@ type keyMap struct {
|
||||
Up, Down, Quit, Help, Tab, Follow, Filter, Search, Enter, Reconnect, Close, Mouse key.Binding
|
||||
PgUp, PgDn key.Binding
|
||||
MeshView key.Binding
|
||||
Sound key.Binding
|
||||
}
|
||||
|
||||
func defaultKeyMap() keyMap {
|
||||
@@ -82,6 +84,7 @@ func defaultKeyMap() keyMap {
|
||||
PgUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "history")),
|
||||
PgDn: key.NewBinding(key.WithKeys("pgdn"), key.WithHelp("pgdn", "down")),
|
||||
MeshView: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "mesh")),
|
||||
Sound: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "sound")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +149,7 @@ type Model struct {
|
||||
peerMesh *mesh.State
|
||||
meshTickActive bool
|
||||
feedMeshVisible bool
|
||||
sound *sound.Engine
|
||||
}
|
||||
|
||||
func NewModel(cfg config.Config) Model {
|
||||
@@ -179,6 +183,7 @@ func NewModel(cfg config.Config) Model {
|
||||
pm := mesh.NewState()
|
||||
pm.SetReducedMotion(cfg.NoColor || envTruthy("HP_VIZ_REDUCED_MOTION"))
|
||||
m.peerMesh = pm
|
||||
m.sound = sound.New()
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -261,9 +266,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
case key.Matches(msg, m.keys.Mouse):
|
||||
return m.toggleMouse()
|
||||
case key.Matches(msg, m.keys.Sound):
|
||||
m.sound.Toggle()
|
||||
return m, nil
|
||||
case key.Matches(msg, m.keys.Quit):
|
||||
m.sse.Stop()
|
||||
return m, tea.Quit
|
||||
return m, m.quit()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -284,9 +291,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
case key.Matches(msg, m.keys.Mouse):
|
||||
return m.toggleMouse()
|
||||
case key.Matches(msg, m.keys.Sound):
|
||||
m.sound.Toggle()
|
||||
return m, nil
|
||||
case key.Matches(msg, m.keys.Quit):
|
||||
m.sse.Stop()
|
||||
return m, tea.Quit
|
||||
return m, m.quit()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -294,8 +303,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case key.Matches(msg, m.keys.Mouse):
|
||||
return m.toggleMouse()
|
||||
case key.Matches(msg, m.keys.Quit):
|
||||
m.sse.Stop()
|
||||
return m, tea.Quit
|
||||
return m, m.quit()
|
||||
case key.Matches(msg, m.keys.Help):
|
||||
m.showHelp = !m.showHelp
|
||||
case key.Matches(msg, m.keys.Tab):
|
||||
@@ -324,6 +332,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.meshTickActive = false
|
||||
}
|
||||
}
|
||||
case key.Matches(msg, m.keys.Sound):
|
||||
m.sound.Toggle()
|
||||
case key.Matches(msg, m.keys.Filter):
|
||||
m.cancelSearch()
|
||||
m.filtering = true
|
||||
@@ -577,6 +587,7 @@ func (m *Model) handleSSE(evt transport.Event) {
|
||||
fmt.Print("\a")
|
||||
}
|
||||
m.peerMesh.TriggerAttack(a.PeerID, a.Service, m.meshPalette())
|
||||
m.sound.PlayAttack()
|
||||
case "peer":
|
||||
p := data.(viz.PeerEvent)
|
||||
m.store.Push(viz.FeedFromPeer(p))
|
||||
@@ -589,6 +600,11 @@ func (m *Model) handleSSE(evt transport.Event) {
|
||||
net := viz.ApplyPeerEvent(m.network, p)
|
||||
m.network = net
|
||||
m.syncMeshFromNetwork()
|
||||
if p.Status == "online" {
|
||||
m.sound.PlayPeerOnline()
|
||||
} else if p.Status == "offline" {
|
||||
m.sound.PlayPeerOffline()
|
||||
}
|
||||
case "block":
|
||||
b := data.(viz.BlockEvent)
|
||||
m.store.Push(viz.FeedFromBlock(b))
|
||||
@@ -596,6 +612,7 @@ func (m *Model) handleSSE(evt transport.Event) {
|
||||
m.cursor = 0
|
||||
}
|
||||
m.peerMesh.TriggerBlock(m.meshPalette())
|
||||
m.sound.PlayBlock()
|
||||
case "moderation":
|
||||
mod := data.(viz.ModerationEvent)
|
||||
item := viz.FeedFromModeration(mod)
|
||||
@@ -609,6 +626,7 @@ func (m *Model) handleSSE(evt transport.Event) {
|
||||
if mod.TierAfter == "block" && mod.TierBefore != "block" {
|
||||
m.peerMesh.TriggerBlock(m.meshPalette())
|
||||
}
|
||||
m.sound.PlayModeration()
|
||||
case "stats":
|
||||
s := data.(viz.Stats)
|
||||
m.stats = &s
|
||||
@@ -685,6 +703,12 @@ func (m *Model) openDetail() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) quit() tea.Cmd {
|
||||
m.sse.Stop()
|
||||
m.sound.Close()
|
||||
return tea.Quit
|
||||
}
|
||||
|
||||
func (m Model) toggleMouse() (Model, tea.Cmd) {
|
||||
if m.mouseEnabled {
|
||||
m.mouseEnabled = false
|
||||
|
||||
Reference in New Issue
Block a user