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:
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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user