ytgo/tui/tui.go
2024-12-09 21:59:16 +01:00

152 lines
3.2 KiB
Go

package tui
import (
"fmt"
"log"
"strings"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"ytgo/scraper"
"ytgo/player"
)
type videoItem struct {
Video scraper.Video
}
// Implement list.Item for videoItem
func (v videoItem) Title() string { return v.Video.Title }
func (v videoItem) Description() string { return fmt.Sprintf("%s | %s | %s | %s", v.Video.Channel, v.Video.Duration, v.Video.UploadDate, v.Video.Views) }
func (v videoItem) FilterValue() string { return v.Video.Title }
type model struct {
searchBar textinput.Model
results list.Model
videos []scraper.Video
showSearch bool
err error
width int
height int
}
// Initial model setup
func initialModel() model {
searchBar := textinput.New()
searchBar.Placeholder = "Search YouTube"
searchBar.Focus()
delegate := list.NewDefaultDelegate()
results := list.New([]list.Item{}, delegate, 50, 20)
results.Title = "Search Results"
return model{
searchBar: searchBar,
results: results,
showSearch: true, // Start in search mode
}
}
// Init initializes the Bubble Tea program
func (m model) Init() tea.Cmd {
return textinput.Blink
}
// Update handles updates to the model based on user input
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
case "/":
m.showSearch = true
m.searchBar.Focus()
return m, nil
case "esc":
if m.showSearch {
m.showSearch = false
m.searchBar.Blur()
}
return m, nil
case "enter":
if m.showSearch {
query := m.searchBar.Value()
if query == "" {
return m, nil // Ignore empty queries
}
// Fetch video results
videos, err := scraper.FetchVideos(query)
if err != nil {
m.err = err
log.Printf("Error fetching videos: %v", err)
return m, nil
}
// Populate the results list
items := make([]list.Item, len(videos))
for i, v := range videos {
items[i] = videoItem{Video: v}
}
m.videos = videos
m.results.SetItems(items)
m.showSearch = false
m.searchBar.Blur()
} else {
// Handle video selection
if i := m.results.Index(); i != -1 {
selectedVideo := m.videos[i]
cmd := player.PlayVideo(selectedVideo.URL)
return m, tea.ExecProcess(cmd, nil)
}
}
return m, nil
}
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.results.SetSize(msg.Width, msg.Height-3) // Leave space for search bar
}
var cmd tea.Cmd
if m.showSearch {
m.searchBar, cmd = m.searchBar.Update(msg)
} else {
m.results, cmd = m.results.Update(msg)
}
return m, cmd
}
// View renders the TUI
func (m model) View() string {
var s strings.Builder
// Always show search bar at the top
searchText := "Press '/' to search"
if m.showSearch {
searchText = m.searchBar.View()
}
s.WriteString(searchText + "\n\n")
// Show results
s.WriteString(m.results.View())
// Show error if any
if m.err != nil {
s.WriteString(fmt.Sprintf("\nError: %v", m.err))
}
return s.String()
}
// Run starts the Bubble Tea program
func Run() error {
p := tea.NewProgram(initialModel())
return p.Start()
}