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)
})
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 {
quality := "SD"
if st.HD {

View File

@@ -49,12 +49,18 @@ type ListColumn[T any] struct {
width int
height int
render renderer[T]
separator func(prev, curr T) (string, bool)
}
func NewListColumn[T any](title string, r renderer[T]) *ListColumn[T] {
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 {
if width <= 0 {
return ""
@@ -77,6 +83,23 @@ func truncateToWidth(text string, width int) string {
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) {
c.items = items
c.selected = 0
@@ -105,18 +128,14 @@ func (c *ListColumn[T]) CursorUp() {
if c.selected > 0 {
c.selected--
}
if c.selected < c.scroll {
c.scroll = c.selected
}
c.ensureSelectedVisible()
}
func (c *ListColumn[T]) CursorDown() {
if c.selected < len(c.items)-1 {
c.selected++
}
if c.selected >= c.scroll+c.height {
c.scroll = c.selected - c.height + 1
}
c.ensureSelectedVisible()
}
func (c *ListColumn[T]) Selected() (T, bool) {
@@ -127,6 +146,80 @@ func (c *ListColumn[T]) Selected() (T, bool) {
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 {
box := styles.Box
if focused {
@@ -144,32 +237,58 @@ func (c *ListColumn[T]) View(styles Styles, focused bool) string {
if len(c.items) == 0 {
lines = append(lines, "(no items)")
} else {
rows := c.buildRows()
c.clampScroll(len(rows))
start := c.scroll
end := start + c.height
if end > len(c.items) {
end = len(c.items)
if end > len(rows) {
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++ {
row := rows[i]
cursor := " "
lineText := c.render(c.items[i])
lineText := row.text
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 {
cursor = "▸ "
lineText = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FA8072")). // Not pink, its Salmon obviously
Bold(true).
Render(lineText)
if row.isSeparator {
lineText = buildSeparatorLine(lineText, contentWidth)
lineText = styles.Subtle.Render(lineText)
} else {
if contentWidth > 1 && lipgloss.Width(lineText) > contentWidth {
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)
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