Add mesh view to Feed
This commit is contained in:
@@ -261,5 +261,11 @@ func listIndexForTab(m Model, y int, ly viewLayout) (int, bool) {
|
||||
adj.MainInnerH = ly.ListH
|
||||
return listIndexAt(y, adj, m.cursor, m.listLen())
|
||||
}
|
||||
if m.tab == TabFeed && m.feedMeshVisible && ly.ListH > 0 {
|
||||
adj := ly
|
||||
adj.MainInnerTop = ly.ListTop
|
||||
adj.MainInnerH = ly.ListH
|
||||
return listIndexAt(y, adj, m.cursor, m.listLen())
|
||||
}
|
||||
return listIndexAt(y, ly, m.cursor, m.listLen())
|
||||
}
|
||||
|
||||
+10
-1
@@ -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, historyNote string) string {
|
||||
func renderFooter(t Theme, filtering, searching bool, filter string, follow bool, detailOpen bool, mouseEnabled bool, tab ViewTab, feedMeshVisible bool, historyNote string) string {
|
||||
var hints []string
|
||||
if searching {
|
||||
hints = []string{"type id…", "Enter lookup", "Esc cancel"}
|
||||
@@ -266,6 +266,9 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
|
||||
} else {
|
||||
hints = []string{"Tab view", "j/k nav", "Enter detail", "s search", "/ filter", "? help", "q quit"}
|
||||
}
|
||||
if tab == TabFeed {
|
||||
hints = append(hints, "v mesh")
|
||||
}
|
||||
hints = append(hints, "m mouse")
|
||||
if filtering {
|
||||
hints = []string{"type filter…", "Enter apply", "Esc cancel"}
|
||||
@@ -280,6 +283,9 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
|
||||
if mouseEnabled {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[mouse]")
|
||||
}
|
||||
if tab == TabFeed && feedMeshVisible {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[mesh]")
|
||||
}
|
||||
if historyNote != "" {
|
||||
line += " " + lipgloss.NewStyle().Foreground(t.Muted).Render("[" + historyNote + "]")
|
||||
}
|
||||
@@ -297,6 +303,9 @@ func renderHelp(t Theme) string {
|
||||
f Toggle follow mode (auto-scroll to newest)
|
||||
PgUp Load older attacks (Feed) / moderation (Incidents) / more posts (Blog)
|
||||
|
||||
Feed tab
|
||||
v Toggle peer mesh animation above the feed
|
||||
|
||||
Blog tab
|
||||
Enter Read selected briefing
|
||||
Esc Back to post list (while reading)
|
||||
|
||||
@@ -176,6 +176,16 @@ func renderFeedList(t Theme, items []viz.FeedItem, cursor int, store *viz.Store,
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func renderFeedSplitView(t Theme, meshStr string, items []viz.FeedItem, cursor int, store *viz.Store, height, width int, operator bool) string {
|
||||
_, feedH := feedPanelHeights(height)
|
||||
feedStr := renderFeedList(t, items, cursor, store, feedH, width, operator)
|
||||
var parts []string
|
||||
parts = append(parts, feedStr)
|
||||
parts = append(parts, t.MutedStyle().Render(strings.Repeat("─", min(width, 40))))
|
||||
parts = append(parts, meshStr)
|
||||
return padToLines(strings.Join(parts, "\n"), height)
|
||||
}
|
||||
|
||||
func renderIncidentRow(t Theme, inc viz.Incident, selected bool, width int) string {
|
||||
headline := viz.IncidentHeadline(inc)
|
||||
kind := string(viz.IncidentActionKindOf(inc))
|
||||
|
||||
@@ -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.historyFooterNote()))
|
||||
parts = append(parts, renderFooter(m.theme, m.filtering, m.searching, m.filterQuery, m.follow, m.detailOpen, m.mouseEnabled, m.tab, m.feedMeshVisible, m.historyFooterNote()))
|
||||
if m.filtering {
|
||||
parts = append(parts, m.filterInput.View())
|
||||
}
|
||||
@@ -86,6 +86,12 @@ func (m Model) viewLayout() viewLayout {
|
||||
ly.MeshH = meshH
|
||||
ly.ListTop = ly.MeshTop + meshH + 1
|
||||
ly.ListH = listH
|
||||
} else if m.tab == TabFeed && m.feedMeshVisible {
|
||||
meshH, feedH := feedPanelHeights(mainH)
|
||||
ly.ListTop = ly.MainInnerTop + 1
|
||||
ly.ListH = feedH
|
||||
ly.MeshTop = ly.ListTop + feedH + 1
|
||||
ly.MeshH = meshH
|
||||
}
|
||||
return ly
|
||||
}
|
||||
|
||||
+22
-2
@@ -62,6 +62,7 @@ const blogPageSize = 20
|
||||
type keyMap struct {
|
||||
Up, Down, Quit, Help, Tab, Follow, Filter, Search, Enter, Reconnect, Close, Mouse key.Binding
|
||||
PgUp, PgDn key.Binding
|
||||
MeshView key.Binding
|
||||
}
|
||||
|
||||
func defaultKeyMap() keyMap {
|
||||
@@ -80,6 +81,7 @@ func defaultKeyMap() keyMap {
|
||||
Reconnect: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "reconnect")),
|
||||
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")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +145,7 @@ type Model struct {
|
||||
|
||||
peerMesh *mesh.State
|
||||
meshTickActive bool
|
||||
feedMeshVisible bool
|
||||
}
|
||||
|
||||
func NewModel(cfg config.Config) Model {
|
||||
@@ -309,6 +312,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.follow {
|
||||
m.cursor = 0
|
||||
}
|
||||
case key.Matches(msg, m.keys.MeshView):
|
||||
if m.tab == TabFeed {
|
||||
m.feedMeshVisible = !m.feedMeshVisible
|
||||
if m.feedMeshVisible {
|
||||
m.syncMeshFromNetwork()
|
||||
if m.peerMesh != nil && m.peerMesh.HasPulses() {
|
||||
cmds = append(cmds, m.startMeshTick())
|
||||
}
|
||||
} else {
|
||||
m.meshTickActive = false
|
||||
}
|
||||
}
|
||||
case key.Matches(msg, m.keys.Filter):
|
||||
m.cancelSearch()
|
||||
m.filtering = true
|
||||
@@ -363,7 +378,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.detailOpen {
|
||||
m.refreshDetailViewport()
|
||||
}
|
||||
if m.tab == TabPeers && m.peerMesh != nil && m.peerMesh.HasPulses() {
|
||||
if m.meshShouldAnimate() && m.peerMesh != nil && m.peerMesh.HasPulses() {
|
||||
cmds = append(cmds, m.startMeshTick())
|
||||
}
|
||||
|
||||
@@ -503,7 +518,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Tick(500*time.Millisecond, func(time.Time) tea.Msg { return flashTickMsg{} })
|
||||
|
||||
case meshTickMsg:
|
||||
if m.tab == TabPeers && m.peerMesh != nil {
|
||||
if m.meshShouldAnimate() && m.peerMesh != nil {
|
||||
m.peerMesh.Tick(mesh.FrameMs)
|
||||
if m.peerMesh.HasPulses() {
|
||||
return m, m.meshTickCmd()
|
||||
@@ -774,6 +789,11 @@ func (m Model) renderMain(height, width int) string {
|
||||
listW := listContentWidth(width)
|
||||
switch m.tab {
|
||||
case TabFeed:
|
||||
if m.feedMeshVisible {
|
||||
meshH, _ := feedPanelHeights(height)
|
||||
meshStr := m.renderPeerMesh(meshH, listW)
|
||||
return renderFeedSplitView(m.theme, meshStr, m.visibleFeed(), m.cursor, m.store, height, listW, m.cfg.Operator)
|
||||
}
|
||||
return renderFeedList(m.theme, m.visibleFeed(), m.cursor, m.store, height, listW, m.cfg.Operator)
|
||||
case TabIncidents:
|
||||
return renderIncidentList(m.theme, m.visibleIncidents(), m.cursor, height, listW, m.cfg.Operator)
|
||||
|
||||
+10
-5
@@ -138,17 +138,22 @@ func (m *Model) handleMouseClick(ev tea.MouseEvent, ly viewLayout) []tea.Cmd {
|
||||
return cmds
|
||||
}
|
||||
|
||||
if m.tab == TabPeers && ly.MeshH > 0 && ev.Y >= ly.MeshTop && ev.Y < ly.MeshTop+ly.MeshH {
|
||||
if ly.MeshH > 0 && ev.Y >= ly.MeshTop && ev.Y < ly.MeshTop+ly.MeshH &&
|
||||
(m.tab == TabPeers || (m.tab == TabFeed && m.feedMeshVisible)) {
|
||||
cellX := ev.X - 2
|
||||
if cellX < 0 {
|
||||
cellX = 0
|
||||
}
|
||||
cellY := ev.Y - ly.MeshTop
|
||||
if id, ok := m.peerMesh.HitTest(cellX, cellY); ok {
|
||||
if idx := m.peerMesh.PeerIndex(id, m.peerRoster()); idx >= 0 {
|
||||
m.cursor = idx
|
||||
m.follow = false
|
||||
m.syncMeshSelection()
|
||||
if m.tab == TabPeers {
|
||||
if idx := m.peerMesh.PeerIndex(id, m.peerRoster()); idx >= 0 {
|
||||
m.cursor = idx
|
||||
m.follow = false
|
||||
m.syncMeshSelection()
|
||||
}
|
||||
} else {
|
||||
m.peerMesh.SetSelected(id)
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
|
||||
+36
-11
@@ -22,16 +22,24 @@ func meshPalette(t Theme) mesh.Palette {
|
||||
}
|
||||
|
||||
func peerPanelHeights(totalH int) (meshH, listH int) {
|
||||
meshH = totalH * 65 / 100
|
||||
if meshH < 8 {
|
||||
meshH = 8
|
||||
return splitPanelHeights(totalH, 65, 8, 3)
|
||||
}
|
||||
|
||||
func feedPanelHeights(totalH int) (meshH, feedH int) {
|
||||
return splitPanelHeights(totalH, 40, 6, 4)
|
||||
}
|
||||
|
||||
func splitPanelHeights(totalH, meshPercent, minMesh, minList int) (meshH, listH int) {
|
||||
meshH = totalH * meshPercent / 100
|
||||
if meshH < minMesh {
|
||||
meshH = minMesh
|
||||
}
|
||||
if meshH > totalH-4 {
|
||||
meshH = totalH - 4
|
||||
if meshH > totalH-minList-1 {
|
||||
meshH = totalH - minList - 1
|
||||
}
|
||||
listH = totalH - meshH - 1
|
||||
if listH < 3 {
|
||||
listH = 3
|
||||
if listH < minList {
|
||||
listH = minList
|
||||
}
|
||||
return meshH, listH
|
||||
}
|
||||
@@ -169,18 +177,35 @@ func (m *Model) startMeshTick() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m *Model) onTabChanged() tea.Cmd {
|
||||
if m.tab == TabPeers {
|
||||
switch m.tab {
|
||||
case TabPeers:
|
||||
m.syncMeshFromNetwork()
|
||||
m.syncMeshSelection()
|
||||
if m.peerMesh != nil && m.peerMesh.HasPulses() {
|
||||
return m.startMeshTick()
|
||||
case TabFeed:
|
||||
if m.feedMeshVisible {
|
||||
m.syncMeshFromNetwork()
|
||||
}
|
||||
default:
|
||||
m.meshTickActive = false
|
||||
return nil
|
||||
}
|
||||
m.meshTickActive = false
|
||||
if m.peerMesh != nil && m.peerMesh.HasPulses() && m.meshShouldAnimate() {
|
||||
return m.startMeshTick()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) meshShouldAnimate() bool {
|
||||
switch m.tab {
|
||||
case TabPeers:
|
||||
return true
|
||||
case TabFeed:
|
||||
return m.feedMeshVisible
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) meshPalette() mesh.Palette {
|
||||
return meshPalette(m.theme)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user