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:
@@ -6,21 +6,21 @@ export type User = {
|
||||
isAdmin: boolean
|
||||
}
|
||||
|
||||
export type HomePayload = {
|
||||
recentAlbums: Array<{
|
||||
id: string
|
||||
artistId: string
|
||||
artistName: string
|
||||
title: string
|
||||
year: number
|
||||
trackCount: number
|
||||
coverArtId: string
|
||||
}>
|
||||
artists: Array<{
|
||||
id: string
|
||||
name: string
|
||||
albumCount: number
|
||||
}>
|
||||
export type Artist = {
|
||||
id: string
|
||||
name: string
|
||||
albumCount: number
|
||||
coverArtId: string
|
||||
}
|
||||
|
||||
export type Album = {
|
||||
id: string
|
||||
artistId: string
|
||||
artistName: string
|
||||
title: string
|
||||
year: number
|
||||
trackCount: number
|
||||
coverArtId: string
|
||||
}
|
||||
|
||||
export type Track = {
|
||||
@@ -32,6 +32,36 @@ export type Track = {
|
||||
albumTitle: string
|
||||
trackNumber: number
|
||||
durationSeconds: number
|
||||
coverArtId?: string
|
||||
}
|
||||
|
||||
export type ArtistDetail = Artist & {
|
||||
albums: Album[]
|
||||
}
|
||||
|
||||
export type AlbumDetail = Album & {
|
||||
tracks: Track[]
|
||||
}
|
||||
|
||||
export type SearchResults = {
|
||||
artists: Artist[]
|
||||
albums: Album[]
|
||||
tracks: Track[]
|
||||
}
|
||||
|
||||
export type ScanStatus = {
|
||||
scanning: boolean
|
||||
startedAt?: string
|
||||
finishedAt?: string
|
||||
lastError?: string
|
||||
artists: number
|
||||
albums: number
|
||||
tracks: number
|
||||
}
|
||||
|
||||
export type HomePayload = {
|
||||
recentAlbums: Album[]
|
||||
artists: Artist[]
|
||||
}
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:4040'
|
||||
@@ -65,11 +95,48 @@ export async function fetchHome() {
|
||||
return request<HomePayload>('/api/home')
|
||||
}
|
||||
|
||||
export async function fetchArtists() {
|
||||
return request<{ items: Artist[] }>('/api/artists')
|
||||
}
|
||||
|
||||
export async function fetchArtist(id: string) {
|
||||
return request<ArtistDetail>(`/api/artists/${id}`)
|
||||
}
|
||||
|
||||
export async function fetchAlbums() {
|
||||
return request<{ items: Album[] }>('/api/albums')
|
||||
}
|
||||
|
||||
export async function fetchAlbum(id: string) {
|
||||
return request<AlbumDetail>(`/api/albums/${id}`)
|
||||
}
|
||||
|
||||
export async function fetchTracks() {
|
||||
return request<{ items: Track[] }>('/api/tracks')
|
||||
}
|
||||
|
||||
export async function fetchTrack(id: string) {
|
||||
return request<Track>(`/api/tracks/${id}`)
|
||||
}
|
||||
|
||||
export async function searchLibrary(query: string) {
|
||||
return request<SearchResults>(`/api/search?q=${encodeURIComponent(query)}`)
|
||||
}
|
||||
|
||||
export async function fetchScanStatus() {
|
||||
return request<ScanStatus>('/api/admin/scan-status')
|
||||
}
|
||||
|
||||
export async function triggerScan() {
|
||||
return request<ScanStatus>('/api/admin/scan', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function coverArtUrl(id: string) {
|
||||
const token = useSessionStore.getState().token
|
||||
return `${API_BASE}/api/cover-art/${id}${token ? `?token=${encodeURIComponent(token)}` : ''}`
|
||||
}
|
||||
|
||||
export function streamUrl(id: string) {
|
||||
const token = useSessionStore.getState().token
|
||||
return `${API_BASE}/api/stream/${id}${token ? `?token=${encodeURIComponent(token)}` : ''}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user