diff --git a/go.mod b/go.mod index cc2688d..e493e2c 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,8 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gdamore/encoding v1.0.0 // indirect + github.com/gdamore/tcell/v2 v2.7.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -58,6 +60,7 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/tview v0.0.0-20240524063012-037df494fb76 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -72,6 +75,7 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect diff --git a/go.sum b/go.sum index 3fe82a5..ddfd9b1 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,10 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= +github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -212,8 +216,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/tview v0.0.0-20240524063012-037df494fb76 h1:iqvDlgyjmqleATtFbA7c14djmPh2n4mCYUv7JlD/ruA= +github.com/rivo/tview v0.0.0-20240524063012-037df494fb76/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -411,6 +418,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/src/components/cli/cli.go b/src/components/cli/cli.go index 9426168..8c5f52d 100644 --- a/src/components/cli/cli.go +++ b/src/components/cli/cli.go @@ -13,6 +13,7 @@ import ( "git.asdf.cafe/abs3nt/gspot/src/components/commands" "git.asdf.cafe/abs3nt/gspot/src/components/tui" + "git.asdf.cafe/abs3nt/gspot/src/components/tuitview" ) var Version = "dev" @@ -389,6 +390,17 @@ func Run(c *commands.Commander, s fx.Shutdowner) { return tui.StartTea(c, "main") }, }, + { + Name: "tview", + Usage: "Starts the TUI using tview (experimental)", + Action: func(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Present() { + return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " ")) + } + // start tview tui + return tuitview.TuitView(c) + }, + }, { Name: "seek", Usage: "Seek to a position in the song", diff --git a/src/components/tuitview/tuitview.go b/src/components/tuitview/tuitview.go new file mode 100644 index 0000000..5f96c70 --- /dev/null +++ b/src/components/tuitview/tuitview.go @@ -0,0 +1,102 @@ +package tuitview + +import ( + "sync/atomic" + + "git.asdf.cafe/abs3nt/gspot/src/components/commands" + "github.com/rivo/tview" + "github.com/zmb3/spotify/v2" +) + +var ( + tracksLoading = atomic.Bool{} + playlistsLoading = atomic.Bool{} + tracksPage = 1 + playlistsPage = 1 +) + +func TuitView(cmd *commands.Commander) error { + tracksLoading.Store(false) + playlistsLoading.Store(false) + playlistsList := tview.NewList().ShowSecondaryText(false) + playlistsList.SetBorder(true).SetTitle("Playlists") + savedTracksList := tview.NewList().ShowSecondaryText(false) + savedTracksList.SetWrapAround(false) + savedTracksList.SetBorder(true).SetTitle("Tracks") + savedTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + go cmd.PlayLikedSongs(index) + }) + flex := tview.NewFlex().AddItem(playlistsList, 0, 1, false).AddItem(savedTracksList, 0, 2, true) + playlists, err := cmd.Playlists(1) + if err != nil { + return err + } + for _, playlist := range playlists.Playlists { + playlistsList.AddItem(playlist.Name, "", 0, func() { + playlistTracksList := tview.NewList().ShowSecondaryText(false) + playlistTracksList.SetBorder(true).SetTitle(playlist.Name) + playlistTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + go cmd.PlaySongInPlaylist((*spotify.URI)(&secondaryText), &index) + }) + tracks, err := cmd.PlaylistTracks(playlist.ID, 1) + if err != nil { + return + } + for _, track := range tracks.Items { + playlistTracksList.AddItem(track.Track.Track.Name+" - "+track.Track.Track.Artists[0].Name, string(playlist.URI), 0, nil) + } + flex.Clear() + flex.AddItem(playlistsList, 0, 1, false) + flex.AddItem(playlistTracksList, 0, 2, false) + }) + } + tracks, err := cmd.TrackList(1) + if err != nil { + return err + } + for _, track := range tracks.Tracks { + savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil) + } + playlistsList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + if playlistsList.GetItemCount()%50 != 0 { + return + } + if playlistsList.GetItemCount()-index < 40 { + go func() { + if playlistsLoading.Load() { + return + } + playlistsLoading.Store(true) + defer playlistsLoading.Store(false) + playlistsPage++ + newPlaylists, _ := cmd.Playlists(playlistsPage) + for _, playlist := range newPlaylists.Playlists { + savedTracksList.AddItem(playlist.Name, "", 0, nil) + } + }() + } + }) + savedTracksList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + if savedTracksList.GetItemCount()%50 != 0 { + return + } + if savedTracksList.GetItemCount()-index < 40 { + go func() { + if tracksLoading.Load() { + return + } + tracksLoading.Store(true) + defer tracksLoading.Store(false) + tracksPage++ + tracks, _ := cmd.TrackList(tracksPage) + for _, track := range tracks.Tracks { + savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil) + } + }() + } + }) + if err := tview.NewApplication().EnableMouse(true).SetRoot(flex, true).Run(); err != nil { + return err + } + return nil +}