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"
|
"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 {
|
if height <= 0 {
|
||||||
return m.titledPanel(title, content, width, focused)
|
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)
|
inner := contentStyle.Render(content)
|
||||||
contentLines := strings.Split(inner, "\n")
|
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 {
|
if bodyH < 1 {
|
||||||
bodyH = 1
|
bodyH = 1
|
||||||
}
|
}
|
||||||
@@ -58,13 +64,21 @@ func (m Model) titledPanelH(title, content string, width, height, scrollOffset i
|
|||||||
}
|
}
|
||||||
visible := contentLines[scrollOffset:end]
|
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
|
var lines []string
|
||||||
lines = append(lines, top)
|
lines = append(lines, top)
|
||||||
for _, line := range visible {
|
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 {
|
for len(lines) < height-1-len(footerLines) {
|
||||||
lines = append(lines, bc.Render("│")+strings.Repeat(" ", innerW)+bc.Render("│"))
|
lines = append(lines, emptyLine)
|
||||||
|
}
|
||||||
|
for _, line := range footerLines {
|
||||||
|
lines = append(lines, borderLine(line))
|
||||||
}
|
}
|
||||||
lines = append(lines, bottom)
|
lines = append(lines, bottom)
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,8 @@ func (m Model) viewMonitorsLayout() string {
|
|||||||
}
|
}
|
||||||
monHeight := lipgloss.Height(monPanel)
|
monHeight := lipgloss.Height(monPanel)
|
||||||
detail := m.viewDetailInline(detailW-2, monHeight)
|
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)
|
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(" " + label.Render("No state changes") + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString("\n " + m.detailKeys() + "\n")
|
|
||||||
|
|
||||||
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
|
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")
|
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 {
|
func (m Model) fmtStatusWord(status string) string {
|
||||||
switch status {
|
switch status {
|
||||||
case "DOWN", "SSL EXP":
|
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())
|
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))
|
statParts = append(statParts, "avg "+fmtDuration(avg))
|
||||||
}
|
}
|
||||||
b.WriteString(" " + label.Render(strings.Join(statParts, " │ ")) + "\n")
|
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())
|
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user