From 97fcd10c41ef8b79f6b90e395b7cd837a29973a5 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 10 Dec 2024 01:04:31 +0100 Subject: [PATCH] feat(song): finish view --- config/development.yaml | 4 +- .../20241209222600_song_created_at.sql | 13 +++ db/queries/song.sql | 8 +- db/queries/tap.sql | 2 +- internal/pkg/db/dto/event.go | 3 +- internal/pkg/db/sqlc/song.sql.go | 8 +- internal/pkg/db/sqlc/tap.sql.go | 4 +- internal/pkg/event/event.go | 41 ++++---- internal/pkg/song/api.go | 2 +- internal/pkg/song/song.go | 1 + tui/components/stopwatch/stopwatch.go | 14 +-- tui/screen/cammie/cammie.go | 2 +- tui/screen/song/song.go | 4 +- tui/view/song/song.go | 93 ++++++++++++------- tui/view/song/style.go | 21 ++++- tui/view/song/util.go | 24 ++--- tui/view/song/view.go | 93 ++++++++++--------- tui/view/tap/tap.go | 2 +- tui/view/tap/view.go | 2 +- tui/view/util.go | 20 ++++ 20 files changed, 222 insertions(+), 139 deletions(-) create mode 100644 db/migrations/20241209222600_song_created_at.sql diff --git a/config/development.yaml b/config/development.yaml index b4999e3..5cb3c2e 100644 --- a/config/development.yaml +++ b/config/development.yaml @@ -1,5 +1,5 @@ server: - host: "localhost" + host: "0.0.0.0" port: 3000 db: @@ -102,7 +102,7 @@ tui: song: interval_current_s: 5 interval_history_s: 5 - interval_top_s: 3600 + interval_top_s: 300 tap: interval_s: 60 diff --git a/db/migrations/20241209222600_song_created_at.sql b/db/migrations/20241209222600_song_created_at.sql new file mode 100644 index 0000000..8c9e3a9 --- /dev/null +++ b/db/migrations/20241209222600_song_created_at.sql @@ -0,0 +1,13 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE song_history +ALTER COLUMN created_at +SET DEFAULT (NOW() - INTERVAL '1 second'); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE song_history +ALTER COLUMN created_at +SET DEFAULT CURRENT_TIMESTAMP; +-- +goose StatementEnd diff --git a/db/queries/song.sql b/db/queries/song.sql index 7e03b50..c81f777 100644 --- a/db/queries/song.sql +++ b/db/queries/song.sql @@ -75,7 +75,7 @@ SELECT s.title FROM song_history sh JOIN song s ON sh.song_id = s.id ORDER BY created_at DESC -LIMIT 5; +LIMIT 10; -- name: GetTopSongs :many SELECT s.id AS song_id, s.title, COUNT(sh.id) AS play_count @@ -83,7 +83,7 @@ FROM song_history sh JOIN song s ON sh.song_id = s.id GROUP BY s.id, s.title ORDER BY play_count DESC -LIMIT 5; +LIMIT 10; -- name: GetTopArtists :many SELECT sa.id AS artist_id, sa.name AS artist_name, COUNT(sh.id) AS total_plays @@ -93,7 +93,7 @@ JOIN song_artist_song sas ON s.id = sas.song_id JOIN song_artist sa ON sas.artist_id = sa.id GROUP BY sa.id, sa.name ORDER BY total_plays DESC -LIMIT 5; +LIMIT 10; -- name: GetTopGenres :many SELECT g.genre AS genre_name, COUNT(sh.id) AS total_plays @@ -105,4 +105,4 @@ JOIN song_artist_genre sag ON sa.id = sag.artist_id JOIN song_genre g ON sag.genre_id = g.id GROUP BY g.genre ORDER BY total_plays DESC -LIMIT 5; +LIMIT 10; diff --git a/db/queries/tap.sql b/db/queries/tap.sql index 88394e7..d2a4929 100644 --- a/db/queries/tap.sql +++ b/db/queries/tap.sql @@ -50,7 +50,7 @@ FROM tap GROUP BY category; -- name: GetOrderCountByCategorySinceOrderID :many -SELECT category, COUNT(*), CAST(MAX(order_created_at) AS INTEGER) AS latest_order_created_at +SELECT category, COUNT(*), MAX(order_created_at)::TIMESTAMP AS latest_order_created_at FROM tap WHERE order_id >= $1 GROUP BY category; diff --git a/internal/pkg/db/dto/event.go b/internal/pkg/db/dto/event.go index 5fb2614..dabf0c5 100644 --- a/internal/pkg/db/dto/event.go +++ b/internal/pkg/db/dto/event.go @@ -1,6 +1,7 @@ package dto import ( + "bytes" "time" "github.com/jackc/pgx/v5/pgtype" @@ -31,7 +32,7 @@ func EventDTO(e sqlc.Event) *Event { // Equal compares 2 events func (e *Event) Equal(e2 Event) bool { - return e.Name == e2.Name && e.Date.Equal(e2.Date) && e.AcademicYear == e2.AcademicYear && e.Location == e2.Location + return e.Name == e2.Name && e.Date.Equal(e2.Date) && e.AcademicYear == e2.AcademicYear && e.Location == e2.Location && bytes.Equal(e.Poster, e2.Poster) } // CreateParams converts a Event DTO to a sqlc CreateEventParams object diff --git a/internal/pkg/db/sqlc/song.sql.go b/internal/pkg/db/sqlc/song.sql.go index fa718f5..91d0493 100644 --- a/internal/pkg/db/sqlc/song.sql.go +++ b/internal/pkg/db/sqlc/song.sql.go @@ -304,7 +304,7 @@ SELECT s.title FROM song_history sh JOIN song s ON sh.song_id = s.id ORDER BY created_at DESC -LIMIT 5 +LIMIT 10 ` func (q *Queries) GetSongHistory(ctx context.Context) ([]string, error) { @@ -335,7 +335,7 @@ JOIN song_artist_song sas ON s.id = sas.song_id JOIN song_artist sa ON sas.artist_id = sa.id GROUP BY sa.id, sa.name ORDER BY total_plays DESC -LIMIT 5 +LIMIT 10 ` type GetTopArtistsRow struct { @@ -374,7 +374,7 @@ JOIN song_artist_genre sag ON sa.id = sag.artist_id JOIN song_genre g ON sag.genre_id = g.id GROUP BY g.genre ORDER BY total_plays DESC -LIMIT 5 +LIMIT 10 ` type GetTopGenresRow struct { @@ -408,7 +408,7 @@ FROM song_history sh JOIN song s ON sh.song_id = s.id GROUP BY s.id, s.title ORDER BY play_count DESC -LIMIT 5 +LIMIT 10 ` type GetTopSongsRow struct { diff --git a/internal/pkg/db/sqlc/tap.sql.go b/internal/pkg/db/sqlc/tap.sql.go index 8a5c7e1..56ac2eb 100644 --- a/internal/pkg/db/sqlc/tap.sql.go +++ b/internal/pkg/db/sqlc/tap.sql.go @@ -143,7 +143,7 @@ func (q *Queries) GetOrderCount(ctx context.Context) ([]GetOrderCountRow, error) } const getOrderCountByCategorySinceOrderID = `-- name: GetOrderCountByCategorySinceOrderID :many -SELECT category, COUNT(*), CAST(MAX(order_created_at) AS INTEGER) AS latest_order_created_at +SELECT category, COUNT(*), MAX(order_created_at)::TIMESTAMP AS latest_order_created_at FROM tap WHERE order_id >= $1 GROUP BY category @@ -152,7 +152,7 @@ GROUP BY category type GetOrderCountByCategorySinceOrderIDRow struct { Category string Count int64 - LatestOrderCreatedAt int32 + LatestOrderCreatedAt pgtype.Timestamp } func (q *Queries) GetOrderCountByCategorySinceOrderID(ctx context.Context, orderID int32) ([]GetOrderCountByCategorySinceOrderIDRow, error) { diff --git a/internal/pkg/event/event.go b/internal/pkg/event/event.go index 3b92975..9ccbb5f 100644 --- a/internal/pkg/event/event.go +++ b/internal/pkg/event/event.go @@ -9,6 +9,7 @@ import ( "github.com/zeusWPI/scc/internal/pkg/db" "github.com/zeusWPI/scc/internal/pkg/db/dto" + "github.com/zeusWPI/scc/internal/pkg/db/sqlc" "github.com/zeusWPI/scc/pkg/config" ) @@ -38,15 +39,35 @@ func (e *Event) Update() error { return nil } - eventsDB, err := e.db.Queries.GetEventByAcademicYear(context.Background(), events[0].AcademicYear) + eventsDBSQL, err := e.db.Queries.GetEventByAcademicYear(context.Background(), events[0].AcademicYear) if err != nil { return err } + eventsDB := make([]*dto.Event, 0, len(eventsDBSQL)) + + var wg sync.WaitGroup + var errs []error + for _, event := range eventsDBSQL { + wg.Add(1) + + go func(event sqlc.Event) { + defer wg.Done() + + ev := dto.EventDTO(event) + eventsDB = append(eventsDB, ev) + err := e.getPoster(ev) + if err != nil { + errs = append(errs, err) + } + }(event) + } + wg.Wait() + // Check if there are any new events equal := true for _, event := range eventsDB { - found := slices.ContainsFunc(events, func(ev dto.Event) bool { return ev.Equal(*dto.EventDTO(event)) }) + found := slices.ContainsFunc(events, func(ev dto.Event) bool { return ev.Equal(*event) }) if !found { equal = false break @@ -67,22 +88,6 @@ func (e *Event) Update() error { if err != nil { return err } - var errs []error - - var wg sync.WaitGroup - for _, event := range events { - wg.Add(1) - - go func(event *dto.Event) { - defer wg.Done() - - err := e.getPoster(event) - if err != nil { - errs = append(errs, err) - } - }(&event) - } - wg.Wait() for _, event := range events { err = e.getPoster(&event) diff --git a/internal/pkg/song/api.go b/internal/pkg/song/api.go index 5438a7d..1f651a7 100644 --- a/internal/pkg/song/api.go +++ b/internal/pkg/song/api.go @@ -68,7 +68,7 @@ type artistResponse struct { } func (s *Song) getArtist(artist *dto.SongArtist) error { - zap.S().Info("Song: Getting artists info for ", artist.ID) + zap.S().Info("Song: Getting artists info for ", artist.SpotifyID) req := fiber.Get(fmt.Sprintf("%s/%s/%s", s.api, "artists", artist.SpotifyID)). Set("Authorization", fmt.Sprintf("Bearer %s", s.AccessToken)) diff --git a/internal/pkg/song/song.go b/internal/pkg/song/song.go index 18dab1b..5967d51 100644 --- a/internal/pkg/song/song.go +++ b/internal/pkg/song/song.go @@ -116,6 +116,7 @@ func (s *Song) Track(track *dto.Song) error { if (a != sqlc.SongArtist{}) { // Artist already exists // Add it as an artist for this track + track.Artists[i].ID = a.ID if _, err := s.db.Queries.CreateSongArtistSong(context.Background(), *track.CreateSongArtistSongParams(i)); err != nil { errs = append(errs, err) } diff --git a/tui/components/stopwatch/stopwatch.go b/tui/components/stopwatch/stopwatch.go index a96a681..6bf702b 100644 --- a/tui/components/stopwatch/stopwatch.go +++ b/tui/components/stopwatch/stopwatch.go @@ -13,13 +13,13 @@ import ( var lastID int64 -func nextID() int { - return int(atomic.AddInt64(&lastID, 1)) +func nextID() int64 { + return atomic.AddInt64(&lastID, 1) } // TickMsg is a message that is sent on every stopwatch tick type TickMsg struct { - id int + id int64 } // StartStopMsg is a message that controls if the stopwatch is running or not @@ -34,12 +34,12 @@ type ResetMsg struct { // Model for the stopwatch component type Model struct { - id int + id int64 duration time.Duration running bool } -// New created a new stopwatch with a given interval +// New creates a new stopwatch with a given interval func New() Model { return Model{ id: nextID(), @@ -48,7 +48,7 @@ func New() Model { } } -// Init initiates the stopwatch component +// Init initializes the stopwatch component func (m Model) Init() tea.Cmd { return nil } @@ -121,7 +121,7 @@ func (m Model) View() string { return fmt.Sprintf("%02d:%02d", min, sec) } -func tick(id int) tea.Cmd { +func tick(id int64) tea.Cmd { return tea.Tick(time.Second, func(_ time.Time) tea.Msg { return TickMsg{id: id} }) diff --git a/tui/screen/cammie/cammie.go b/tui/screen/cammie/cammie.go index 29187db..bbb1ee3 100644 --- a/tui/screen/cammie/cammie.go +++ b/tui/screen/cammie/cammie.go @@ -59,7 +59,7 @@ func (c *Cammie) Update(msg tea.Msg) (screen.Screen, tea.Cmd) { c.width = msg.Width c.height = msg.Height - sMsg = sMsg.Width(c.width/2 - sMsg.GetHorizontalFrameSize() - sMsg.GetHorizontalPadding()).Height(c.height - sMsg.GetVerticalFrameSize() - sMsg.GetVerticalPadding()) + sMsg = sMsg.Width(c.width/2 - view.GetOuterWidth(sMsg)).Height(c.height - sMsg.GetVerticalFrameSize() - sMsg.GetVerticalPadding()) sTop = sTop.Width(c.width/2 - sTop.GetHorizontalFrameSize()).Height(c.height/2 - sTop.GetVerticalFrameSize()) sBottom = sBottom.Width(c.width/2 - sBottom.GetHorizontalFrameSize()).Height(c.height/2 - sBottom.GetVerticalFrameSize()) diff --git a/tui/screen/song/song.go b/tui/screen/song/song.go index a7c8b50..57c42ac 100644 --- a/tui/screen/song/song.go +++ b/tui/screen/song/song.go @@ -19,7 +19,7 @@ type Song struct { // New creates a new song screen func New(db *db.DB) screen.Screen { - return &Song{db: db, song: song.NewModel(db), width: 0, height: 0} + return &Song{db: db, song: song.New(db), width: 0, height: 0} } // Init initializes the song screen @@ -34,7 +34,7 @@ func (s *Song) Update(msg tea.Msg) (screen.Screen, tea.Cmd) { s.width = msg.Width s.height = msg.Height - sSong = sSong.Width(s.width - sSong.GetHorizontalFrameSize() - sSong.GetHorizontalPadding()).Height(s.height - sSong.GetVerticalFrameSize() - sSong.GetVerticalPadding()) + sSong = sSong.Width(s.width - view.GetOuterWidth(sSong)).Height(s.height - sSong.GetVerticalFrameSize() - sSong.GetVerticalPadding()) return s, s.GetSizeMsg } diff --git a/tui/view/song/song.go b/tui/view/song/song.go index c4ab914..038c4e0 100644 --- a/tui/view/song/song.go +++ b/tui/view/song/song.go @@ -5,26 +5,26 @@ import ( "context" "time" - "github.com/charmbracelet/bubbles/progress" tea "github.com/charmbracelet/bubbletea" "github.com/jackc/pgx/v5" "github.com/zeusWPI/scc/internal/pkg/db" "github.com/zeusWPI/scc/internal/pkg/db/dto" "github.com/zeusWPI/scc/internal/pkg/lyrics" "github.com/zeusWPI/scc/pkg/config" + "github.com/zeusWPI/scc/tui/components/progress" "github.com/zeusWPI/scc/tui/components/stopwatch" "github.com/zeusWPI/scc/tui/view" ) var ( previousAmount = 5 // Amount of passed lyrics to show - upcomingAmount = 10 // Amount of upcoming lyrics to show + upcomingAmount = 12 // Amount of upcoming lyrics to show ) type playing struct { song *dto.Song - progress progress.Model stopwatch stopwatch.Model + progress progress.Model lyrics lyrics.Lyrics previous []string // Lyrics already sang current string // Current lyric @@ -36,9 +36,9 @@ type Model struct { db *db.DB current playing history []string - topSongs []topStat - topGenres []topStat - topArtists []topStat + topSongs topStat + topGenres topStat + topArtists topStat width int height int } @@ -48,13 +48,14 @@ type Model struct { type Msg struct{} type msgPlaying struct { - current playing + song *dto.Song + lyrics lyrics.Lyrics } type msgTop struct { - topSongs []topStat - topGenres []topStat - topArtists []topStat + topSongs []topStatEntry + topGenres []topStatEntry + topArtists []topStatEntry } type msgHistory struct { @@ -62,7 +63,7 @@ type msgHistory struct { } type msgLyrics struct { - song dto.Song + song *dto.Song previous []string current string upcoming []string @@ -71,19 +72,24 @@ type msgLyrics struct { } type topStat struct { + title string + entries []topStatEntry +} + +type topStatEntry struct { name string amount int } -// NewModel initializes a new song model -func NewModel(db *db.DB) view.View { +// New initializes a new song model +func New(db *db.DB) view.View { return &Model{ db: db, - current: playing{stopwatch: stopwatch.New(), progress: progress.New()}, + current: playing{stopwatch: stopwatch.New(), progress: progress.New(sStatusProgressFainted, sStatusProgressGlow)}, history: make([]string, 0, 5), - topSongs: make([]topStat, 0, 5), - topGenres: make([]topStat, 0, 5), - topArtists: make([]topStat, 0, 5), + topSongs: topStat{title: "Top Tracks", entries: make([]topStatEntry, 0, 5)}, + topGenres: topStat{title: "Top Genres", entries: make([]topStatEntry, 0, 5)}, + topArtists: topStat{title: "Top Artists", entries: make([]topStatEntry, 0, 5)}, width: 0, height: 0, } @@ -91,7 +97,10 @@ func NewModel(db *db.DB) view.View { // Init starts the song view func (m *Model) Init() tea.Cmd { - return m.current.stopwatch.Init() + return tea.Batch( + m.current.stopwatch.Init(), + m.current.progress.Init(), + ) } // Name returns the name of the view @@ -107,11 +116,19 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { if ok { m.width = entry.Width m.height = entry.Height + + sStatusSong = sStatusSong.Width(m.width - view.GetOuterWidth(sStatusSong)) + sStatusProgress = sStatusProgress.Width(m.width - view.GetOuterWidth(sStatusProgress)) + sLyric = sLyric.Width(m.width - view.GetOuterWidth(sLyric)) + sStatAll = sStatAll.Width(m.width - view.GetOuterWidth(sStatAll)) + sAll = sAll.Height(m.height - view.GetOuterHeight(sAll)).Width(m.width - view.GetOuterWidth(sAll)) } return m, nil + case msgPlaying: - m.current = msg.current + m.current.song = msg.song + m.current.lyrics = msg.lyrics // New song, start the commands to update the lyrics lyric, ok := m.current.lyrics.Next() if !ok { @@ -132,8 +149,13 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { startTime = startTime.Add(lyric.Duration) } + m.current.previous = lyricsToString(m.current.lyrics.Previous(previousAmount)) m.current.upcoming = lyricsToString(m.current.lyrics.Upcoming(upcomingAmount)) - return m, tea.Batch(updateLyrics(m.current, startTime), m.current.stopwatch.Start(time.Since(m.current.song.CreatedAt))) + return m, tea.Batch( + updateLyrics(m.current, startTime), + m.current.stopwatch.Start(time.Since(m.current.song.CreatedAt)), + m.current.progress.Start(view.GetWidth(sStatusProgress), time.Since(m.current.song.CreatedAt), time.Duration(m.current.song.DurationMS)*time.Millisecond), + ) case msgHistory: m.history = msg.history @@ -142,13 +164,13 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { case msgTop: if msg.topSongs != nil { - m.topSongs = msg.topSongs + m.topSongs.entries = msg.topSongs } if msg.topGenres != nil { - m.topGenres = msg.topGenres + m.topGenres.entries = msg.topGenres } if msg.topArtists != nil { - m.topArtists = msg.topArtists + m.topArtists.entries = msg.topArtists } return m, nil @@ -173,16 +195,17 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { // Start the cmd to update the lyrics return m, updateLyrics(m.current, msg.startNext) - - case progress.FrameMsg: - progressModel, cmd := m.current.progress.Update(msg) - m.current.progress = progressModel.(progress.Model) - - return m, cmd } + // Maybe a stopwatch message? var cmd tea.Cmd m.current.stopwatch, cmd = m.current.stopwatch.Update(msg) + if cmd != nil { + return m, cmd + } + + // Maybe a progress bar message? + m.current.progress, cmd = m.current.progress.Update(msg) return m, cmd } @@ -248,7 +271,7 @@ func updateCurrentSong(view view.View) (tea.Msg, error) { song := dto.SongDTOHistory(songs) - return msgPlaying{current: playing{song: song, lyrics: lyrics.New(song)}}, nil + return msgPlaying{song: song, lyrics: lyrics.New(song)}, nil } func updateHistory(view view.View) (tea.Msg, error) { @@ -272,7 +295,7 @@ func updateTopStats(view view.View) (tea.Msg, error) { return nil, err } - if !equalTopSongs(m.topSongs, songs) { + if !equalTopSongs(m.topSongs.entries, songs) { msg.topSongs = topStatSqlcSong(songs) change = true } @@ -282,7 +305,7 @@ func updateTopStats(view view.View) (tea.Msg, error) { return nil, err } - if !equalTopGenres(m.topGenres, genres) { + if !equalTopGenres(m.topGenres.entries, genres) { msg.topGenres = topStatSqlcGenre(genres) change = true } @@ -292,7 +315,7 @@ func updateTopStats(view view.View) (tea.Msg, error) { return nil, err } - if !equalTopArtists(m.topArtists, artists) { + if !equalTopArtists(m.topArtists.entries, artists) { msg.topArtists = topStatSqlcArtist(artists) change = true } @@ -316,7 +339,7 @@ func updateLyrics(song playing, start time.Time) tea.Cmd { lyric, ok := song.lyrics.Next() if !ok { // Song finished - return msgLyrics{song: *song.song, done: true} + return msgLyrics{song: song.song, done: true} } previous := song.lyrics.Previous(previousAmount) @@ -325,7 +348,7 @@ func updateLyrics(song playing, start time.Time) tea.Cmd { end := start.Add(lyric.Duration) return msgLyrics{ - song: *song.song, + song: song.song, previous: lyricsToString(previous), current: lyric.Text, upcoming: lyricsToString(upcoming), diff --git a/tui/view/song/style.go b/tui/view/song/style.go index 783dbf0..81fb366 100644 --- a/tui/view/song/style.go +++ b/tui/view/song/style.go @@ -6,6 +6,7 @@ import "github.com/charmbracelet/lipgloss" var ( cZeus = lipgloss.Color("#FF7F00") cSpotify = lipgloss.Color("#1DB954") + cBorder = lipgloss.Color("#383838") ) // Base style @@ -13,11 +14,12 @@ var base = lipgloss.NewStyle() // Styles for the stats var ( - wStatTotal = 30 + wStatTotal = 40 wStatEnum = 3 wStatAmount = 4 wStatBody = wStatTotal - wStatEnum - wStatAmount + sStatAll = base.Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder()).BorderTop(true).BorderForeground(cBorder).PaddingTop(3) sStat = base.Width(wStatTotal).MarginRight(3).MarginBottom(2) sStatTitle = base.Foreground(cZeus).Width(wStatTotal).Align(lipgloss.Center). BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).BorderForeground(cSpotify) @@ -28,15 +30,24 @@ var ( // Styles for the status var ( - sStatusSong = base - sStatusStopwatch = base.Faint(true) - sStatusProgress = base + sStatus = base.PaddingTop(1) + sStatusSong = base.Padding(0, 1).Align(lipgloss.Center) + sStatusStopwatch = base.Faint(true) + sStatusProgress = base.Padding(0, 2).PaddingBottom(3).Align(lipgloss.Left) + sStatusProgressFainted = base.Foreground(cZeus).Faint(true) + sStatusProgressGlow = base.Foreground(cZeus) ) // Styles for the lyrics var ( - sLyricBase = base.Width(50).Align(lipgloss.Center) + sLyricBase = base.Width(50).Align(lipgloss.Center).Bold(true) + sLyric = sLyricBase.AlignVertical(lipgloss.Center) sLyricPrevious = sLyricBase.Foreground(cZeus).Faint(true) sLyricCurrent = sLyricBase.Foreground(cZeus) sLyricUpcoming = sLyricBase.Foreground(cSpotify) ) + +// Style for everything +var ( + sAll = base.Align(lipgloss.Center).AlignVertical(lipgloss.Center) +) diff --git a/tui/view/song/util.go b/tui/view/song/util.go index 182cf89..8fb7b90 100644 --- a/tui/view/song/util.go +++ b/tui/view/song/util.go @@ -5,7 +5,7 @@ import ( "github.com/zeusWPI/scc/internal/pkg/lyrics" ) -func equalTopSongs(s1 []topStat, s2 []sqlc.GetTopSongsRow) bool { +func equalTopSongs(s1 []topStatEntry, s2 []sqlc.GetTopSongsRow) bool { if len(s1) != len(s2) { return false } @@ -19,15 +19,15 @@ func equalTopSongs(s1 []topStat, s2 []sqlc.GetTopSongsRow) bool { return true } -func topStatSqlcSong(songs []sqlc.GetTopSongsRow) []topStat { - topstats := make([]topStat, 0, len(songs)) +func topStatSqlcSong(songs []sqlc.GetTopSongsRow) []topStatEntry { + topstats := make([]topStatEntry, 0, len(songs)) for _, s := range songs { - topstats = append(topstats, topStat{name: s.Title, amount: int(s.PlayCount)}) + topstats = append(topstats, topStatEntry{name: s.Title, amount: int(s.PlayCount)}) } return topstats } -func equalTopGenres(s1 []topStat, s2 []sqlc.GetTopGenresRow) bool { +func equalTopGenres(s1 []topStatEntry, s2 []sqlc.GetTopGenresRow) bool { if len(s1) != len(s2) { return false } @@ -41,15 +41,15 @@ func equalTopGenres(s1 []topStat, s2 []sqlc.GetTopGenresRow) bool { return true } -func topStatSqlcGenre(songs []sqlc.GetTopGenresRow) []topStat { - topstats := make([]topStat, 0, len(songs)) +func topStatSqlcGenre(songs []sqlc.GetTopGenresRow) []topStatEntry { + topstats := make([]topStatEntry, 0, len(songs)) for _, s := range songs { - topstats = append(topstats, topStat{name: s.GenreName, amount: int(s.TotalPlays)}) + topstats = append(topstats, topStatEntry{name: s.GenreName, amount: int(s.TotalPlays)}) } return topstats } -func equalTopArtists(s1 []topStat, s2 []sqlc.GetTopArtistsRow) bool { +func equalTopArtists(s1 []topStatEntry, s2 []sqlc.GetTopArtistsRow) bool { if len(s1) != len(s2) { return false } @@ -63,10 +63,10 @@ func equalTopArtists(s1 []topStat, s2 []sqlc.GetTopArtistsRow) bool { return true } -func topStatSqlcArtist(songs []sqlc.GetTopArtistsRow) []topStat { - topstats := make([]topStat, 0, len(songs)) +func topStatSqlcArtist(songs []sqlc.GetTopArtistsRow) []topStatEntry { + topstats := make([]topStatEntry, 0, len(songs)) for _, s := range songs { - topstats = append(topstats, topStat{name: s.ArtistName, amount: int(s.TotalPlays)}) + topstats = append(topstats, topStatEntry{name: s.ArtistName, amount: int(s.TotalPlays)}) } return topstats } diff --git a/tui/view/song/view.go b/tui/view/song/view.go index 8c1949c..97bf487 100644 --- a/tui/view/song/view.go +++ b/tui/view/song/view.go @@ -10,11 +10,17 @@ import ( func (m *Model) viewPlaying() string { status := m.viewPlayingStatus() + status = sStatus.Render(status) + + stats := m.viewPlayingStats() + stats = sStatAll.Render(stats) + lyrics := m.viewPlayingLyrics() + lyrics = sLyric.Height(sAll.GetHeight() - lipgloss.Height(status) - lipgloss.Height(stats)).Render(lyrics) - view := lipgloss.JoinVertical(lipgloss.Left, status, lyrics) + view := lipgloss.JoinVertical(lipgloss.Left, status, lyrics, stats) - return view + return sAll.Render(view) } func (m *Model) viewPlayingStatus() string { @@ -33,14 +39,11 @@ func (m *Model) viewPlayingStatus() string { artist = artist[:len(artist)-3] } - song := sStatusSong.Width(m.width - lipgloss.Width(stopwatch)).Render(fmt.Sprintf("%s | %s", m.current.song.Title, artist)) + song := sStatusSong.Width(sStatusSong.GetWidth() - lipgloss.Width(stopwatch)).Render(fmt.Sprintf("%s | %s", m.current.song.Title, artist)) // Progress bar - // zap.S().Info(m.current.lyrics.Progress()) - // progress := sStatusProgress.Width(m.width).Render(m.current.progress.ViewAs(m.current.lyrics.Progress())) - // zap.S().Info(progress) - - progress := sStatusProgress.Width(m.width).Render(strings.Repeat("▄", int(m.current.lyrics.Progress()*float64(m.width)))) + progress := m.current.progress.View() + progress = sStatusProgress.Render(progress) view := lipgloss.JoinHorizontal(lipgloss.Top, song, stopwatch) view = lipgloss.JoinVertical(lipgloss.Left, view, progress) @@ -67,7 +70,18 @@ func (m *Model) viewPlayingLyrics() string { } upcoming := sLyricUpcoming.Render(upcomingB.String()) - return base.MarginLeft(5).Render(lipgloss.JoinVertical(lipgloss.Left, previous, current, upcoming)) + return sLyric.Render(lipgloss.JoinVertical(lipgloss.Left, previous, current, upcoming)) +} + +func (m *Model) viewPlayingStats() string { + columns := make([]string, 0, 4) + + columns = append(columns, m.viewRecent()) + columns = append(columns, m.viewTopStat(m.topSongs)) + columns = append(columns, m.viewTopStat(m.topArtists)) + columns = append(columns, m.viewTopStat(m.topGenres)) + + return lipgloss.JoinHorizontal(lipgloss.Top, columns...) } func (m *Model) viewNotPlaying() string { @@ -76,7 +90,22 @@ func (m *Model) viewNotPlaying() string { rows = append(rows, make([]string, 0, 2)) } - // Recently played + rows[0] = append(rows[0], m.viewRecent()) + rows[0] = append(rows[0], m.viewTopStat(m.topSongs)) + rows[1] = append(rows[1], m.viewTopStat(m.topArtists)) + rows[1] = append(rows[1], m.viewTopStat(m.topGenres)) + + renderedRows := make([]string, 0, 2) + for _, row := range rows { + renderedRows = append(renderedRows, lipgloss.JoinHorizontal(lipgloss.Top, row...)) + } + + view := lipgloss.JoinVertical(lipgloss.Left, renderedRows...) + + return sAll.Render(view) +} + +func (m *Model) viewRecent() string { items := make([]string, 0, len(m.history)) for i, track := range m.history { number := sStatEnum.Render(fmt.Sprintf("%d.", i+1)) @@ -85,41 +114,21 @@ func (m *Model) viewNotPlaying() string { } l := lipgloss.JoinVertical(lipgloss.Left, items...) title := sStatTitle.Render("Recently Played") - rows[0] = append(rows[0], sStat.Render(lipgloss.JoinVertical(lipgloss.Left, title, l))) - - // All other stats - topStats := [][]topStat{m.topSongs, m.topArtists, m.topGenres} - for i, topStat := range topStats { - items := make([]string, 0, len(topStat)) - for i, stat := range topStat { - number := sStatEnum.Render(fmt.Sprintf("%d.", i+1)) - body := sStatBody.Render(stat.name) - amount := sStatAmount.Render(fmt.Sprintf("%d", stat.amount)) - items = append(items, lipgloss.JoinHorizontal(lipgloss.Top, number, body, amount)) - } - l := lipgloss.JoinVertical(lipgloss.Left, items...) - - var row int - if i == 0 { - title = sStatTitle.Render("Top Tracks") - row = 0 - } else if i == 1 { - title = sStatTitle.Render("Top Artists") - row = 1 - } else { - title = sStatTitle.Render("Top Genres") - row = 1 - } - rows[row] = append(rows[row], sStat.Render(lipgloss.JoinVertical(lipgloss.Left, title, l))) - } + return sStat.Render(lipgloss.JoinVertical(lipgloss.Left, title, l)) +} - renderedRows := make([]string, 0, 2) - for _, row := range rows { - renderedRows = append(renderedRows, lipgloss.JoinHorizontal(lipgloss.Top, row...)) +func (m *Model) viewTopStat(topStat topStat) string { + items := make([]string, 0, len(topStat.entries)) + for i, stat := range topStat.entries { + number := sStatEnum.Render(fmt.Sprintf("%d.", i+1)) + body := sStatBody.Render(stat.name) + amount := sStatAmount.Render(fmt.Sprintf("%d", stat.amount)) + items = append(items, lipgloss.JoinHorizontal(lipgloss.Top, number, body, amount)) } + l := lipgloss.JoinVertical(lipgloss.Left, items...) - view := lipgloss.JoinVertical(lipgloss.Left, renderedRows...) + title := sStatTitle.Render(topStat.title) - return view + return sStat.Render(lipgloss.JoinVertical(lipgloss.Left, title, l)) } diff --git a/tui/view/tap/tap.go b/tui/view/tap/tap.go index 3f51a39..499b030 100644 --- a/tui/view/tap/tap.go +++ b/tui/view/tap/tap.go @@ -155,7 +155,7 @@ func updateOrders(view view.View) (tea.Msg, error) { counts[category(order.Category)] = tapItem{ category: category(order.Category), amount: int(order.Count), - last: time.Unix(int64(order.LatestOrderCreatedAt), 0), + last: order.LatestOrderCreatedAt.Time, } } diff --git a/tui/view/tap/view.go b/tui/view/tap/view.go index 4c4a850..65433ae 100644 --- a/tui/view/tap/view.go +++ b/tui/view/tap/view.go @@ -39,7 +39,7 @@ func (m *Model) viewStats() string { for _, item := range m.items { amount := sStatsAmount.Render(strconv.Itoa(item.amount)) category := sStatsCategory.Inherit(categoryToStyle[item.category]).Render(string(item.category)) - last := sStatsLast.Render(item.last.Format("02/01 15:04")) + last := sStatsLast.Render(item.last.Format("15:04 02/01")) text := lipgloss.JoinHorizontal(lipgloss.Top, amount, category, last) rows = append(rows, text) diff --git a/tui/view/util.go b/tui/view/util.go index 332808a..d3fb57e 100644 --- a/tui/view/util.go +++ b/tui/view/util.go @@ -42,3 +42,23 @@ func ImagetoString(width int, img image.Image) string { return str.String() } + +// GetOuterWidth returns the outer border size of a lipgloss Style +func GetOuterWidth(style lipgloss.Style) int { + return style.GetHorizontalFrameSize() + style.GetHorizontalPadding() +} + +// GetWidth returns the inner width of a lipgloss Style +func GetWidth(style lipgloss.Style) int { + return style.GetWidth() - GetOuterWidth(style) +} + +// GetOuterHeight returns the outer border size of a lipgloss Style +func GetOuterHeight(style lipgloss.Style) int { + return style.GetVerticalFrameSize() + style.GetVerticalPadding() +} + +// GetHeight returns the inner width of a lipgloss Style +func GetHeight(style lipgloss.Style) int { + return style.GetHeight() - GetOuterHeight(style) +}