initial commit
This commit is contained in:
commit
859c6d328e
8 changed files with 572 additions and 0 deletions
152
tui/tui.go
Normal file
152
tui/tui.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue