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:
2026-04-02 22:53:13 +03:00
parent 2f7034fae2
commit 2e7283baad
16 changed files with 1201 additions and 242 deletions

View File

@@ -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 {

View File

@@ -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