Add date separators in matches list
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user