feat(library): album-identity mode — header dropdown, Settings row, persistence

Spec 2026-07-19-local-album-grouping-mode (A2-A5 + i18n). The Albums tab
gains a third header dropdown ('Albums by folder' / 'Albums by metadata')
and Settings > Local Library a matching 'Album grouping' row — both write
the same pref: LocalLibraryState.albums-id-mode, persisted in
locallibrary_ui.json (default 'folder': compilations and box sets stay
whole; the metadata split stays opt-in for #411's use case).

The group key changes with the mode, so switching reloads the Albums set
(reload_albums) and invalidates the Artists tab (its album cache groups
the same way — a folder-mode compilation cross-lists under every artist
in all_artists, the owner's 'appears in all artists' ask). Threaded
through spawn_albums_load, seed_counts, the Plex quality-hydration
aggregate lookup, and the artists feed; myqbz_builder pins Metadata
(artist-scoped candidates, not the Albums view).

5 msgids x 8 locales (msgfmt clean); slintcheck green.

km
This commit is contained in:
vicrodh committed 2026-07-21 14:11:04 -06:00
1 parent 77e41caa2f
commit c72e71d410
15 files changed
+250 -1

No files matched your search

@@ -4750,3 +4750,18 @@ msgstr "Begrenzt durch das Qualitätslimit für {}"
msgid "capped for this device"
msgstr "für dieses Gerät begrenzt"
msgid "Albums by folder"
msgstr "Alben nach Ordner"
msgid "Albums by metadata"
msgstr "Alben nach Metadaten"
msgid "ALBUMS VIEW"
msgstr "ALBENANSICHT"
msgid "Album grouping"
msgstr "Album-Gruppierung"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Ordner: eine Karte pro Album-Ordner — ideal für Compilations und Boxsets. Metadaten: nach Album- + Künstler-Tags aufteilen."
@@ -543,3 +543,18 @@ msgstr "Limited by the quality cap for {}"
msgid "capped for this device"
msgstr "capped for this device"
msgid "Albums by folder"
msgstr "Albums by folder"
msgid "Albums by metadata"
msgstr "Albums by metadata"
msgid "ALBUMS VIEW"
msgstr "ALBUMS VIEW"
msgid "Album grouping"
msgstr "Album grouping"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
@@ -4780,3 +4780,18 @@ msgstr "Limitado por el límite de calidad para {}"
msgid "capped for this device"
msgstr "limitado para este dispositivo"
msgid "Albums by folder"
msgstr "Álbumes por carpeta"
msgid "Albums by metadata"
msgstr "Álbumes por metadatos"
msgid "ALBUMS VIEW"
msgstr "VISTA DE ÁLBUMES"
msgid "Album grouping"
msgstr "Agrupación de álbumes"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Carpetas: una tarjeta por carpeta de álbum — ideal para compilaciones y box sets. Metadatos: separar por etiquetas de álbum + artista."
@@ -4750,3 +4750,18 @@ msgstr "Limité par le plafond de qualité pour {}"
msgid "capped for this device"
msgstr "plafonné pour cet appareil"
msgid "Albums by folder"
msgstr "Albums par dossier"
msgid "Albums by metadata"
msgstr "Albums par métadonnées"
msgid "ALBUMS VIEW"
msgstr "VUE ALBUMS"
msgid "Album grouping"
msgstr "Regroupement des albums"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Dossiers : une carte par dossier d'album — idéal pour les compilations et coffrets. Métadonnées : séparer par tags album + artiste."
@@ -4738,3 +4738,18 @@ msgstr "{}の音質上限により制限されています"
msgid "capped for this device"
msgstr "このデバイス向けに上限適用中"
msgid "Albums by folder"
msgstr "フォルダー別アルバム"
msgid "Albums by metadata"
msgstr "メタデータ別アルバム"
msgid "ALBUMS VIEW"
msgstr "アルバムビュー"
msgid "Album grouping"
msgstr "アルバムのグループ化"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "フォルダー: アルバムフォルダーごとに1枚のカード — コンピレーションやボックスセットに最適。メタデータ: アルバム+アーティストのタグで分割。"
@@ -4762,3 +4762,18 @@ msgstr "Beperkt door de kwaliteitslimiet voor {}"
msgid "capped for this device"
msgstr "begrensd voor dit apparaat"
msgid "Albums by folder"
msgstr "Albums per map"
msgid "Albums by metadata"
msgstr "Albums per metadata"
msgid "ALBUMS VIEW"
msgstr "ALBUMWEERGAVE"
msgid "Album grouping"
msgstr "Albumgroepering"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Mappen: één kaart per albummap — ideaal voor compilaties en boxsets. Metadata: splitsen op album- + artiesttags."
@@ -4750,3 +4750,18 @@ msgstr "Limitado pelo limite de qualidade para {}"
msgid "capped for this device"
msgstr "limitado para este dispositivo"
msgid "Albums by folder"
msgstr "Álbuns por pasta"
msgid "Albums by metadata"
msgstr "Álbuns por metadados"
msgid "ALBUMS VIEW"
msgstr "VISTA DE ÁLBUNS"
msgid "Album grouping"
msgstr "Agrupamento de álbuns"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Pastas: um cartão por pasta de álbum — ideal para compilações e box sets. Metadados: dividir por etiquetas de álbum + artista."
@@ -4764,3 +4764,18 @@ msgstr "Ограничено пределом качества для {}"
msgid "capped for this device"
msgstr "ограничено для этого устройства"
msgid "Albums by folder"
msgstr "Альбомы по папкам"
msgid "Albums by metadata"
msgstr "Альбомы по метаданным"
msgid "ALBUMS VIEW"
msgstr "ВИД АЛЬБОМОВ"
msgid "Album grouping"
msgstr "Группировка альбомов"
msgid "Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags."
msgstr "Папки: одна карточка на папку альбома — лучше для сборников и бокс-сетов. Метаданные: разделение по тегам альбома + исполнителя."
@@ -838,6 +838,25 @@ export component LocalLibraryView inherits Rectangle {
}
}
}
// Album identity ("Albums by") — what one album IS: a folder
// (compilations/box sets stay whole) or metadata tag pairs
// (split per album+artist, #411). Persisted; reloads the set.
VerticalLayout {
alignment: center;
QbzSelect {
menu-width: 190px;
sm: true;
options: [
@tr("Albums by folder"),
@tr("Albums by metadata"),
];
current-index: LocalLibraryState.albums-id-mode == "metadata" ? 1 : 0;
selected(i) => {
LocalLibraryActions.albums-set-id-mode(
i == 1 ? "metadata" : "folder");
}
}
}
// Quality/format/source filter (popup) + active-count badge.
VerticalLayout {
alignment: center;
@@ -9,11 +9,12 @@
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { LibraryFoldersState, LibraryManageActions, LibraryScanState, PlexSettingsState, PlexAuthActions , UiFocusState } from "../state.slint";
import { LibraryFoldersState, LibraryManageActions, LibraryScanState, PlexSettingsState, PlexAuthActions , UiFocusState, LocalLibraryState, LocalLibraryActions } from "../state.slint";
import { SettingRow } from "SettingRow.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { QbzCheckbox } from "../primitives/QbzCheckbox.slint";
import { QbzToggle } from "../primitives/QbzToggle.slint";
import { QbzSelect } from "../primitives/QbzSelect.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
import { ScrollView, LineEdit } from "std-widgets.slint";
@@ -421,6 +422,32 @@ export component LocalLibrarySettings inherits VerticalLayout {
Rectangle { height: 22px; }
// --- Albums view -------------------------------------------------------
// Same pref as the Albums-tab header dropdown — this is the discoverable
// home, the header is the quick switch (both write albums-id-mode).
GroupHeader { text: @tr("ALBUMS VIEW"); }
SettingRow {
label: @tr("Album grouping");
description: @tr("Folders: one card per album folder — best for compilations and box sets. Metadata: split by album + artist tags.");
VerticalLayout {
alignment: center;
QbzSelect {
menu-width: 190px;
sm: true;
options: [
@tr("Albums by folder"),
@tr("Albums by metadata"),
];
current-index: LocalLibraryState.albums-id-mode == "metadata" ? 1 : 0;
selected(i) => {
LocalLibraryActions.albums-set-id-mode(i == 1 ? "metadata" : "folder");
}
}
}
}
Rectangle { height: 22px; }
// --- Maintenance -----------------------------------------------------
GroupHeader { text: @tr("MAINTENANCE"); }
SettingRow {
+9
View File
@@ -1194,6 +1194,12 @@ export global LocalLibraryState {
in property <[AlbumCardItem]> albums-visible: [];
in property <[DiscoverSection]> albums-grouped: [];
in-out property <string> albums-group: "off"; // off | alpha | artist
// Album IDENTITY mode — what one "album" IS on the Albums tab:
// "folder" (one card per album-root folder; default — compilations/box
// sets stay whole) | "metadata" (split by album+artist tags, #411).
// Persisted in locallibrary_ui.json; switching re-queries the DB (unlike
// sort/group, which re-derive client-side).
in-out property <string> albums-id-mode: "folder";
in property <[AlphaJump]> albums-alpha: []; // A-Z jumps (alpha group)
in property <int> albums-shown: 0; // count after filter
in-out property <bool> albums-multi-select: false; // album checkboxes on
@@ -1318,6 +1324,9 @@ export global LocalLibraryActions {
callback albums-set-sort(string /* sort key */);
// Set group mode (off|alpha|artist) + re-derive.
callback albums-set-group(string /* mode */);
// Set the album identity mode (folder|metadata) + persist + reload the
// albums set (the group key changes — a derive is not enough).
callback albums-set-id-mode(string /* mode */);
// Set grid|list view.
callback albums-set-view(string /* mode */);
// A quality/format/source filter toggle changed -> re-derive.