feat(locallibrary): Artists tab — name + counts grid

Fills the last empty tab: a full-loaded grid of artists (get_artists_with_filter)
with album/track counts, inline circular cards (placeholder icon for now). All
four Local Library tabs (Albums / Tracks / Folders / Artists) now browse. The
navigate match is exhaustive over LibTab. Artist images, click-to-detail, and
name-merge dedup (Tauri's artistMergeResult) are later slices.

cc
This commit is contained in:
vicrodh committed 2026-05-29 07:44:40 -06:00
1 parent df351ec6f6
commit 6e1009afd0
4 files changed
+154 -4

No files matched your search

@@ -597,10 +597,103 @@ export component LocalLibraryView inherits Rectangle {
}
}
// -------- Other tabs — placeholder until their slices land ----
// -------- Artists tab — name + counts grid -------------------
if LocalLibraryState.active-tab == "artists": Rectangle {
if LocalLibraryState.artists-loading: VerticalLayout {
alignment: center;
HorizontalLayout {
alignment: center;
LoadingSpinner { size: 36px; }
}
}
if !LocalLibraryState.artists-loading
&& LocalLibraryState.artists.length == 0: VerticalLayout {
alignment: center;
Text {
text: @tr("No artists in your local library yet.");
color: Theme.text-muted;
font-size: Typography.body;
horizontal-alignment: center;
}
}
if !LocalLibraryState.artists-loading
&& LocalLibraryState.artists.length > 0: Rectangle {
artists-flick := Flickable {
viewport-width: self.width;
viewport-height: artists-page.preferred-height;
artists-page := VerticalLayout {
width: parent.width;
padding-left: 32px;
padding-right: 32px;
padding-top: 16px;
padding-bottom: 100px;
alignment: start;
artists-grid := Rectangle {
property <length> card-w: 160px;
property <length> card-h: 200px;
property <length> gap: 24px;
property <int> columns: Math.max(1,
Math.floor((self.width + self.gap) / (self.card-w + self.gap)));
property <int> rows: Math.ceil(LocalLibraryState.artists.length / self.columns);
height: self.rows > 0
? self.rows * self.card-h + Math.max(0, self.rows - 1) * self.gap
: 0px;
for artist[i] in LocalLibraryState.artists: VerticalLayout {
x: Math.mod(i, artists-grid.columns) * (artists-grid.card-w + artists-grid.gap);
y: Math.floor(i / artists-grid.columns) * (artists-grid.card-h + artists-grid.gap);
width: artists-grid.card-w;
spacing: 10px;
Rectangle {
width: 120px;
height: 120px;
x: (parent.width - self.width) / 2;
border-radius: 60px;
background: Theme.surface-elevated;
QbzIcon {
source: @image-url("../assets/icons/user.svg");
width: 44px;
height: 44px;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
tint: Theme.text-muted;
}
}
Text {
text: artist.name;
color: Theme.text-primary;
font-size: Typography.body;
font-weight: Typography.medium;
horizontal-alignment: center;
overflow: elide;
width: artists-grid.card-w;
}
Text {
text: artist.subtitle;
color: Theme.text-muted;
font-size: Typography.legal;
horizontal-alignment: center;
width: artists-grid.card-w;
}
}
}
}
}
ListScrollbar {
x: parent.width - self.width - 4px;
width: 14px;
height: 100%;
viewport-height: artists-flick.viewport-height;
visible-height: artists-flick.height;
viewport-y <=> artists-flick.viewport-y;
}
}
}
// -------- Other tabs — placeholder (all four now have bodies) -
if LocalLibraryState.active-tab != "albums"
&& LocalLibraryState.active-tab != "tracks"
&& LocalLibraryState.active-tab != "folders": VerticalLayout {
&& LocalLibraryState.active-tab != "folders"
&& LocalLibraryState.active-tab != "artists": VerticalLayout {
alignment: center;
spacing: 6px;
Text {
+12
View File
@@ -337,6 +337,13 @@ export enum ToastKind { success, error, info, warning, buffering }
// shows header stats + the browse tabs (Albums / Artists / Folders /
// Tracks). All management/maintenance/danger actions live under
// Settings > Local Library; the gear button routes there.
// One card in the Local Library Artists grid (name + "N albums · M tracks").
// Images + click-to-detail are a later slice.
export struct LocalArtistItem {
name: string,
subtitle: string,
}
export global LocalLibraryState {
// "albums" | "artists" | "folders" | "tracks"
in-out property <string> active-tab: "albums";
@@ -382,6 +389,11 @@ export global LocalLibraryState {
in property <[AlbumCardItem]> folders: [];
in property <bool> folders-loading: false;
in property <bool> folders-load-failed: false;
// --- Artists tab — name + counts grid (full load; images later) -----
in property <[LocalArtistItem]> artists: [];
in property <bool> artists-loading: false;
in property <bool> artists-load-failed: false;
}
export global LocalLibraryActions {