feat(tui): pin detail panel footer keys at bottom of sidebar
Footer keys (period selector, edit/history/SLA shortcuts, Esc) are now pinned at the bottom of the detail panel, always visible regardless of scroll position. Scrollable content fills the space between the header and the pinned footer.
This commit is contained in:
+19
-5
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
func (m Model) titledPanelH(title, content string, width, height, scrollOffset int, focused bool) string {
|
||||
func (m Model) titledPanelH(title, content, footer string, width, height, scrollOffset int, focused bool) string {
|
||||
if height <= 0 {
|
||||
return m.titledPanel(title, content, width, focused)
|
||||
}
|
||||
@@ -40,7 +40,13 @@ func (m Model) titledPanelH(title, content string, width, height, scrollOffset i
|
||||
inner := contentStyle.Render(content)
|
||||
contentLines := strings.Split(inner, "\n")
|
||||
|
||||
bodyH := height - 2
|
||||
var footerLines []string
|
||||
if footer != "" {
|
||||
footerRendered := contentStyle.Render(footer)
|
||||
footerLines = strings.Split(footerRendered, "\n")
|
||||
}
|
||||
|
||||
bodyH := height - 2 - len(footerLines)
|
||||
if bodyH < 1 {
|
||||
bodyH = 1
|
||||
}
|
||||
@@ -58,13 +64,21 @@ func (m Model) titledPanelH(title, content string, width, height, scrollOffset i
|
||||
}
|
||||
visible := contentLines[scrollOffset:end]
|
||||
|
||||
borderLine := func(line string) string {
|
||||
return bc.Render("│") + line + strings.Repeat(" ", max(0, innerW-lipgloss.Width(line))) + bc.Render("│")
|
||||
}
|
||||
emptyLine := borderLine(strings.Repeat(" ", innerW))
|
||||
|
||||
var lines []string
|
||||
lines = append(lines, top)
|
||||
for _, line := range visible {
|
||||
lines = append(lines, bc.Render("│")+line+strings.Repeat(" ", max(0, innerW-lipgloss.Width(line)))+bc.Render("│"))
|
||||
lines = append(lines, borderLine(line))
|
||||
}
|
||||
for len(lines) < height-1 {
|
||||
lines = append(lines, bc.Render("│")+strings.Repeat(" ", innerW)+bc.Render("│"))
|
||||
for len(lines) < height-1-len(footerLines) {
|
||||
lines = append(lines, emptyLine)
|
||||
}
|
||||
for _, line := range footerLines {
|
||||
lines = append(lines, borderLine(line))
|
||||
}
|
||||
lines = append(lines, bottom)
|
||||
|
||||
|
||||
@@ -192,7 +192,8 @@ func (m Model) viewMonitorsLayout() string {
|
||||
}
|
||||
monHeight := lipgloss.Height(monPanel)
|
||||
detail := m.viewDetailInline(detailW-2, monHeight)
|
||||
detailPanel := m.zones.Mark("panel-detail", m.titledPanelH(title, detail, detailW, monHeight, m.detailScrollOffset, m.focusedPanel == panelDetail))
|
||||
footer := m.detailFooter(detailW - 2)
|
||||
detailPanel := m.zones.Mark("panel-detail", m.titledPanelH(title, detail, footer, detailW, monHeight, m.detailScrollOffset, m.focusedPanel == panelDetail))
|
||||
topParts = append(topParts, detailPanel)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,8 +143,6 @@ func (m Model) viewDetailSidebar(site models.Site, hist monitor.SiteHistory, wid
|
||||
b.WriteString(" " + label.Render("No state changes") + "\n")
|
||||
}
|
||||
|
||||
b.WriteString("\n " + m.detailKeys() + "\n")
|
||||
|
||||
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
|
||||
}
|
||||
|
||||
@@ -216,6 +214,33 @@ func (m Model) detailKeys() string {
|
||||
return m.st.subtleStyle.Render("[e] Edit [h] History [s] SLA [Esc] Back")
|
||||
}
|
||||
|
||||
func (m Model) detailFooter(width int) string {
|
||||
label := m.st.subtleStyle
|
||||
var lines []string
|
||||
|
||||
switch m.detailMode {
|
||||
case detailSLA:
|
||||
var keys []string
|
||||
for i, p := range slaPeriods {
|
||||
k := fmt.Sprintf("[%s] %s", p.key, p.label)
|
||||
if i == m.slaPeriodIdx {
|
||||
keys = append(keys, m.st.titleStyle.Render(k))
|
||||
} else {
|
||||
keys = append(keys, label.Render(k))
|
||||
}
|
||||
}
|
||||
lines = append(lines, " "+strings.Join(keys, " "))
|
||||
lines = append(lines, " "+label.Render("[Esc] Back"))
|
||||
case detailHistory:
|
||||
lines = append(lines, " "+label.Render("[Esc] Back"))
|
||||
default:
|
||||
lines = append(lines, " "+m.detailKeys())
|
||||
}
|
||||
|
||||
content := strings.Join(lines, "\n")
|
||||
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(content)
|
||||
}
|
||||
|
||||
func (m Model) fmtStatusWord(status string) string {
|
||||
switch status {
|
||||
case "DOWN", "SSL EXP":
|
||||
@@ -292,20 +317,6 @@ func (m Model) viewSLASidebar(width, _ int) string {
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
var keys []string
|
||||
for i, p := range slaPeriods {
|
||||
k := fmt.Sprintf("[%s] %s", p.key, p.label)
|
||||
if i == m.slaPeriodIdx {
|
||||
keys = append(keys, m.st.titleStyle.Render(k))
|
||||
} else {
|
||||
keys = append(keys, label.Render(k))
|
||||
}
|
||||
}
|
||||
b.WriteString(" " + strings.Join(keys, " ") + "\n")
|
||||
b.WriteString(" " + label.Render("[Esc] Back") + "\n")
|
||||
|
||||
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
|
||||
}
|
||||
|
||||
@@ -368,7 +379,6 @@ func (m Model) viewHistorySidebar(width, _ int) string {
|
||||
statParts = append(statParts, "avg "+fmtDuration(avg))
|
||||
}
|
||||
b.WriteString(" " + label.Render(strings.Join(statParts, " │ ")) + "\n")
|
||||
b.WriteString(" " + label.Render("[Esc] Back") + "\n")
|
||||
|
||||
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user