feat: redesign web interface to match aonsoku layout
Replace the early prototype UI with a darker Aonsoku-inspired shell featuring a compact top bar, library sidebar, command palette, settings overlay, dense track list, artists table, albums grid, and a bottom player bar. Add a supporting albums browse endpoint so the frontend can render the same navigation shape without faking data.
This commit is contained in:
@@ -53,6 +53,7 @@ func NewRouter(cfg config.Config, database *sql.DB, scanService *scanner.Service
|
||||
private.Get("/home", application.home)
|
||||
private.Get("/artists", application.artists)
|
||||
private.Get("/artists/{id}", application.artistByID)
|
||||
private.Get("/albums", application.albums)
|
||||
private.Get("/albums/{id}", application.albumByID)
|
||||
private.Get("/tracks", application.tracks)
|
||||
private.Get("/tracks/{id}", application.trackByID)
|
||||
@@ -152,6 +153,15 @@ func (a app) artistByID(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, item)
|
||||
}
|
||||
|
||||
func (a app) albums(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := a.library.Albums(r.Context(), 1000)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to load albums"})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
}
|
||||
|
||||
func (a app) albumByID(w http.ResponseWriter, r *http.Request) {
|
||||
item, err := a.library.AlbumByID(r.Context(), chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
|
||||
@@ -173,6 +173,34 @@ func (s *Service) RecentAlbums(ctx context.Context, limit int) ([]Album, error)
|
||||
return albums, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Service) Albums(ctx context.Context, limit int) ([]Album, error) {
|
||||
rows, err := s.db.QueryContext(
|
||||
ctx,
|
||||
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id) AS track_count, COALESCE(al.cover_art_id, '')
|
||||
FROM albums al
|
||||
JOIN artists a ON a.id = al.artist_id
|
||||
LEFT JOIN tracks t ON t.album_id = al.id
|
||||
GROUP BY al.id, al.artist_id, a.name, al.title, al.year, al.cover_art_id
|
||||
ORDER BY al.year DESC, al.title ASC
|
||||
LIMIT ?`,
|
||||
limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query all albums: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var albums []Album
|
||||
for rows.Next() {
|
||||
var album Album
|
||||
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.CoverArtID); err != nil {
|
||||
return nil, fmt.Errorf("scan all albums: %w", err)
|
||||
}
|
||||
albums = append(albums, album)
|
||||
}
|
||||
return albums, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Service) AlbumByID(ctx context.Context, id string) (AlbumDetail, error) {
|
||||
var album AlbumDetail
|
||||
|
||||
|
||||
Reference in New Issue
Block a user