Add date separators in matches list

This commit is contained in:
Salastil
2025-11-23 00:46:20 -05:00
parent 5afa624af0
commit 49ac8c9e1b
2 changed files with 150 additions and 19 deletions

View File

@@ -168,6 +168,18 @@ func New(debug bool) Model {
} }
return fmt.Sprintf("%s %s (%s)", when, title, mt.Category) return fmt.Sprintf("%s %s (%s)", when, title, mt.Category)
}) })
m.matches.SetSeparator(func(prev, curr Match) (string, bool) {
currDay := time.UnixMilli(curr.Date).Local().Format("Jan 2")
prevDay := ""
if prev.Date != 0 {
prevDay = time.UnixMilli(prev.Date).Local().Format("Jan 2")
}
if prevDay == "" || prevDay != currDay {
return currDay, true
}
return "", false
})
m.streams = NewListColumn[Stream]("Streams", func(st Stream) string { m.streams = NewListColumn[Stream]("Streams", func(st Stream) string {
quality := "SD" quality := "SD"
if st.HD { if st.HD {

View File

@@ -49,12 +49,18 @@ type ListColumn[T any] struct {
width int width int
height int height int
render renderer[T] render renderer[T]
separator func(prev, curr T) (string, bool)
} }
func NewListColumn[T any](title string, r renderer[T]) *ListColumn[T] { func NewListColumn[T any](title string, r renderer[T]) *ListColumn[T] {
return &ListColumn[T]{title: title, render: r, width: 30, height: 20} return &ListColumn[T]{title: title, render: r, width: 30, height: 20}
} }
func (c *ListColumn[T]) SetSeparator(sep func(prev, curr T) (string, bool)) {
c.separator = sep
}
func truncateToWidth(text string, width int) string { func truncateToWidth(text string, width int) string {
if width <= 0 { if width <= 0 {
return "" return ""
@@ -77,6 +83,23 @@ func truncateToWidth(text string, width int) string {
return text return text
} }
func buildSeparatorLine(label string, width int) string {
if width <= 0 {
return label
}
trimmed := strings.TrimSpace(label)
padded := fmt.Sprintf(" %s ", trimmed)
remaining := width - lipgloss.Width(padded)
if remaining <= 0 {
return truncateToWidth(padded, width)
}
left := remaining / 2
right := remaining - left
return strings.Repeat("─", left) + padded + strings.Repeat("─", right)
}
func (c *ListColumn[T]) SetItems(items []T) { func (c *ListColumn[T]) SetItems(items []T) {
c.items = items c.items = items
c.selected = 0 c.selected = 0
@@ -105,18 +128,14 @@ func (c *ListColumn[T]) CursorUp() {
if c.selected > 0 { if c.selected > 0 {
c.selected-- c.selected--
} }
if c.selected < c.scroll { c.ensureSelectedVisible()
c.scroll = c.selected
}
} }
func (c *ListColumn[T]) CursorDown() { func (c *ListColumn[T]) CursorDown() {
if c.selected < len(c.items)-1 { if c.selected < len(c.items)-1 {
c.selected++ c.selected++
} }
if c.selected >= c.scroll+c.height { c.ensureSelectedVisible()
c.scroll = c.selected - c.height + 1
}
} }
func (c *ListColumn[T]) Selected() (T, bool) { func (c *ListColumn[T]) Selected() (T, bool) {
@@ -127,6 +146,80 @@ func (c *ListColumn[T]) Selected() (T, bool) {
return c.items[c.selected], true return c.items[c.selected], true
} }
type listRow[T any] struct {
text string
isSeparator bool
itemIndex int
}
func (c *ListColumn[T]) buildRows() []listRow[T] {
rows := make([]listRow[T], 0, len(c.items))
var prev T
for i, item := range c.items {
if c.separator != nil {
if sepText, ok := c.separator(prev, item); ok {
rows = append(rows, listRow[T]{text: sepText, isSeparator: true, itemIndex: -1})
}
}
rows = append(rows, listRow[T]{text: c.render(item), itemIndex: i})
prev = item
}
return rows
}
func (c *ListColumn[T]) clampScroll(totalRows int) {
if c.height <= 0 {
c.scroll = 0
return
}
maxScroll := totalRows - c.height
if maxScroll < 0 {
maxScroll = 0
}
if c.scroll > maxScroll {
c.scroll = maxScroll
}
if c.scroll < 0 {
c.scroll = 0
}
}
func (c *ListColumn[T]) ensureSelectedVisible() {
if len(c.items) == 0 {
c.scroll = 0
return
}
rows := c.buildRows()
selRow := 0
for idx, row := range rows {
if row.isSeparator {
continue
}
if row.itemIndex == c.selected {
selRow = idx
break
}
}
if c.height <= 0 {
c.scroll = selRow
return
}
if selRow < c.scroll {
c.scroll = selRow
}
if selRow >= c.scroll+c.height {
c.scroll = selRow - c.height + 1
}
c.clampScroll(len(rows))
}
func (c *ListColumn[T]) View(styles Styles, focused bool) string { func (c *ListColumn[T]) View(styles Styles, focused bool) string {
box := styles.Box box := styles.Box
if focused { if focused {
@@ -144,32 +237,58 @@ func (c *ListColumn[T]) View(styles Styles, focused bool) string {
if len(c.items) == 0 { if len(c.items) == 0 {
lines = append(lines, "(no items)") lines = append(lines, "(no items)")
} else { } else {
rows := c.buildRows()
c.clampScroll(len(rows))
start := c.scroll start := c.scroll
end := start + c.height end := start + c.height
if end > len(c.items) { if end > len(rows) {
end = len(c.items) end = len(rows)
} }
meta = styles.Subtle.Render(fmt.Sprintf("Showing %d%d of %d", start+1, end, len(c.items)))
startItem, endItem := -1, -1
for i := start; i < end; i++ { for i := start; i < end; i++ {
row := rows[i]
cursor := " " cursor := " "
lineText := c.render(c.items[i]) lineText := row.text
contentWidth := c.width - lipgloss.Width(cursor) contentWidth := c.width - lipgloss.Width(cursor)
if contentWidth > 1 && lipgloss.Width(lineText) > contentWidth {
lineText = fmt.Sprintf("%s…", truncateToWidth(lineText, contentWidth-1))
}
if i == c.selected { if row.isSeparator {
cursor = "▸ " lineText = buildSeparatorLine(lineText, contentWidth)
lineText = lipgloss.NewStyle(). lineText = styles.Subtle.Render(lineText)
Foreground(lipgloss.Color("#FA8072")). // Not pink, its Salmon obviously } else {
Bold(true). if contentWidth > 1 && lipgloss.Width(lineText) > contentWidth {
Render(lineText) lineText = fmt.Sprintf("%s…", truncateToWidth(lineText, contentWidth-1))
}
if startItem == -1 {
startItem = row.itemIndex
}
endItem = row.itemIndex
if row.itemIndex == c.selected {
cursor = "▸ "
lineText = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FA8072")). // Not pink, its Salmon obviously
Bold(true).
Render(lineText)
}
} }
line := fmt.Sprintf("%s%s", cursor, lineText) line := fmt.Sprintf("%s%s", cursor, lineText)
lines = append(lines, line) lines = append(lines, line)
} }
if startItem == -1 {
startItem = 0
}
if endItem == -1 {
endItem = startItem
}
meta = styles.Subtle.Render(fmt.Sprintf("Showing %d%d of %d", startItem+1, endItem+1, len(c.items)))
} }
// Fill remaining lines if fewer than height // Fill remaining lines if fewer than height