polish(tui): responsive column hiding — 3-tier priority-based layout
Columns drop progressively as terminal narrows: - Compact (<90): #, NAME, STATUS, LATENCY - Medium (90-119): + TYPE, UPTIME, HISTORY - Wide (120+): + SSL, RETRIES Column definitions are data-driven via siteColumns slice. Row builder uses pickCols() helper so headers and cells can't drift. Closes #68
This commit was merged in pull request #95.
This commit is contained in:
+121
-41
@@ -33,27 +33,74 @@ type siteFormData struct {
|
|||||||
Regions string
|
Regions string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type colKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
colNum colKey = iota
|
||||||
|
colName
|
||||||
|
colType
|
||||||
|
colStatus
|
||||||
|
colLatency
|
||||||
|
colUptime
|
||||||
|
colHistory
|
||||||
|
colSSL
|
||||||
|
colRetries
|
||||||
|
)
|
||||||
|
|
||||||
|
type columnDef struct {
|
||||||
|
key colKey
|
||||||
|
wide string
|
||||||
|
narrow string
|
||||||
|
wideW int
|
||||||
|
narrowW int
|
||||||
|
minTerm int // minimum terminal width to show (0 = always)
|
||||||
|
}
|
||||||
|
|
||||||
|
var siteColumns = []columnDef{
|
||||||
|
{colNum, "#", "#", 4, 4, 0},
|
||||||
|
{colName, "NAME", "NAME", 0, 0, 0},
|
||||||
|
{colType, "TYPE", "TYPE", 10, 8, mediumBreakpoint},
|
||||||
|
{colStatus, "STATUS", "STATUS", 10, 10, 0},
|
||||||
|
{colLatency, "LATENCY", "LAT", 10, 7, 0},
|
||||||
|
{colUptime, "UPTIME", "UP%", 8, 8, mediumBreakpoint},
|
||||||
|
{colHistory, "HISTORY", "HISTORY", 0, 0, mediumBreakpoint},
|
||||||
|
{colSSL, "SSL", "SSL", 7, 5, wideBreakpoint},
|
||||||
|
{colRetries, "RETRIES", "RT", 9, 5, wideBreakpoint},
|
||||||
|
}
|
||||||
|
|
||||||
type tableLayout struct {
|
type tableLayout struct {
|
||||||
nameW, sparkW int
|
nameW, sparkW int
|
||||||
headers []string
|
headers []string
|
||||||
colWidths []int
|
colWidths []int
|
||||||
|
active []colKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) computeLayout() tableLayout {
|
func (m Model) computeLayout() tableLayout {
|
||||||
wide := m.isWide()
|
wide := m.isWide()
|
||||||
|
|
||||||
var fixed int
|
var active []colKey
|
||||||
var headers []string
|
var headers []string
|
||||||
var widths []int
|
var widths []int
|
||||||
|
var fixed int
|
||||||
|
|
||||||
if wide {
|
for _, c := range siteColumns {
|
||||||
headers = []string{"#", "NAME", "TYPE", "STATUS", "LATENCY", "UPTIME", "HISTORY", "SSL", "RETRIES"}
|
if c.minTerm > 0 && m.termWidth < c.minTerm {
|
||||||
widths = []int{4, 0, 10, 10, 10, 8, 0, 7, 9}
|
continue
|
||||||
fixed = 4 + 10 + 10 + 10 + 8 + 7 + 9
|
}
|
||||||
} else {
|
active = append(active, c.key)
|
||||||
headers = []string{"#", "NAME", "TYPE", "STATUS", "LAT", "UP%", "HISTORY", "SSL", "RT"}
|
if wide {
|
||||||
widths = []int{4, 0, 8, 10, 7, 8, 0, 5, 5}
|
headers = append(headers, c.wide)
|
||||||
fixed = 4 + 8 + 10 + 7 + 8 + 5 + 5
|
widths = append(widths, c.wideW)
|
||||||
|
if c.wideW > 0 {
|
||||||
|
fixed += c.wideW
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers = append(headers, c.narrow)
|
||||||
|
widths = append(widths, c.narrowW)
|
||||||
|
if c.narrowW > 0 {
|
||||||
|
fixed += c.narrowW
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
numCols := len(headers)
|
numCols := len(headers)
|
||||||
@@ -71,7 +118,23 @@ func (m Model) computeLayout() tableLayout {
|
|||||||
}
|
}
|
||||||
maxName += 4
|
maxName += 4
|
||||||
|
|
||||||
nameW := avail / 2
|
hasHistory := false
|
||||||
|
for _, k := range active {
|
||||||
|
if k == colHistory {
|
||||||
|
hasHistory = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameW, sparkW int
|
||||||
|
if hasHistory {
|
||||||
|
nameW = avail / 2
|
||||||
|
sparkW = avail - nameW
|
||||||
|
} else {
|
||||||
|
nameW = avail
|
||||||
|
sparkW = 0
|
||||||
|
}
|
||||||
|
|
||||||
if nameW > maxName {
|
if nameW > maxName {
|
||||||
nameW = maxName
|
nameW = maxName
|
||||||
}
|
}
|
||||||
@@ -81,26 +144,41 @@ func (m Model) computeLayout() tableLayout {
|
|||||||
if nameW > 35 {
|
if nameW > 35 {
|
||||||
nameW = 35
|
nameW = 35
|
||||||
}
|
}
|
||||||
|
if sparkW > 0 {
|
||||||
sparkW := avail - nameW
|
if sparkW < 15 {
|
||||||
if sparkW < 15 {
|
sparkW = 15
|
||||||
sparkW = 15
|
}
|
||||||
}
|
if sparkW > 62 {
|
||||||
if sparkW > 62 {
|
sparkW = 62
|
||||||
sparkW = 62
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
widths[1] = nameW
|
for i, k := range active {
|
||||||
widths[6] = sparkW
|
if k == colName {
|
||||||
|
widths[i] = nameW
|
||||||
|
}
|
||||||
|
if k == colHistory {
|
||||||
|
widths[i] = sparkW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tableLayout{
|
return tableLayout{
|
||||||
nameW: nameW,
|
nameW: nameW,
|
||||||
sparkW: sparkW,
|
sparkW: sparkW,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
colWidths: widths,
|
colWidths: widths,
|
||||||
|
active: active,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pickCols(active []colKey, allCells map[colKey]string) []string {
|
||||||
|
row := make([]string, len(active))
|
||||||
|
for i, k := range active {
|
||||||
|
row[i] = allCells[k]
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) viewSitesTab() string {
|
func (m Model) viewSitesTab() string {
|
||||||
|
|
||||||
if len(m.sites) == 0 {
|
if len(m.sites) == 0 {
|
||||||
@@ -137,17 +215,18 @@ func (m Model) viewSitesTab() string {
|
|||||||
if site.Type == "group" {
|
if site.Type == "group" {
|
||||||
groupRows[i-start] = true
|
groupRows[i-start] = true
|
||||||
icon := typeIcon("group", m.collapsed[site.ID])
|
icon := typeIcon("group", m.collapsed[site.ID])
|
||||||
rows = append(rows, []string{
|
cells := map[colKey]string{
|
||||||
strconv.Itoa(i + 1),
|
colNum: strconv.Itoa(i + 1),
|
||||||
m.zones.Mark(fmt.Sprintf("site-%d", i), icon+" "+limitStr(site.Name, nameW-4)),
|
colName: m.zones.Mark(fmt.Sprintf("site-%d", i), icon+" "+limitStr(site.Name, nameW-4)),
|
||||||
"group",
|
colType: "group",
|
||||||
fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
|
colStatus: fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
|
||||||
subtleStyle.Render("—"),
|
colLatency: subtleStyle.Render("—"),
|
||||||
m.groupUptime(site.ID),
|
colUptime: m.groupUptime(site.ID),
|
||||||
m.groupSparkline(site.ID, sparkWidth, rowBg),
|
colHistory: m.groupSparkline(site.ID, sparkWidth, rowBg),
|
||||||
subtleStyle.Render("-"),
|
colSSL: subtleStyle.Render("-"),
|
||||||
subtleStyle.Render("—"),
|
colRetries: subtleStyle.Render("—"),
|
||||||
})
|
}
|
||||||
|
rows = append(rows, pickCols(layout.active, cells))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,17 +263,18 @@ func (m Model) viewSitesTab() string {
|
|||||||
spark = latencySparkline(hist.Latencies, hist.Statuses, sparkWidth, rowBg)
|
spark = latencySparkline(hist.Latencies, hist.Statuses, sparkWidth, rowBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, []string{
|
cells := map[colKey]string{
|
||||||
strconv.Itoa(i + 1),
|
colNum: strconv.Itoa(i + 1),
|
||||||
m.zones.Mark(fmt.Sprintf("site-%d", i), name),
|
colName: m.zones.Mark(fmt.Sprintf("site-%d", i), name),
|
||||||
typeIcon(site.Type, false) + " " + site.Type,
|
colType: typeIcon(site.Type, false) + " " + site.Type,
|
||||||
fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
|
colStatus: fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)),
|
||||||
fmtLatency(site.Latency),
|
colLatency: fmtLatency(site.Latency),
|
||||||
fmtUptime(hist.Statuses),
|
colUptime: fmtUptime(hist.Statuses),
|
||||||
spark,
|
colHistory: spark,
|
||||||
fmtSSL(site),
|
colSSL: fmtSSL(site),
|
||||||
fmtRetries(site),
|
colRetries: fmtRetries(site),
|
||||||
})
|
}
|
||||||
|
rows = append(rows, pickCols(layout.active, cells))
|
||||||
}
|
}
|
||||||
return rows
|
return rows
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ var (
|
|||||||
|
|
||||||
type StyleOverride func(row, col int) *lipgloss.Style
|
type StyleOverride func(row, col int) *lipgloss.Style
|
||||||
|
|
||||||
const wideBreakpoint = 120
|
const (
|
||||||
|
wideBreakpoint = 120
|
||||||
|
mediumBreakpoint = 90
|
||||||
|
)
|
||||||
|
|
||||||
func (m Model) isWide() bool {
|
func (m Model) isWide() bool {
|
||||||
return m.termWidth >= wideBreakpoint
|
return m.termWidth >= wideBreakpoint
|
||||||
|
|||||||
Reference in New Issue
Block a user