feat(qbz-qt): Local Library — folder tree, windowed album grid, detail

The sidebar has had a "Local Library" row since the shell was ported and
nothing behind it: the nav entry did not even navigate. This builds the view.

QbzLocal is its own domain bridge (the phase-23 pattern, one #[cxx_qt::bridge]
per domain) over local_library_qt, which consumes qbz-library for the scanning,
the folder-vs-tag album identity and the queries rather than reimplementing any
of it. The view carries the folder tree, the album grid, album detail, the
track tabs, sorting and search, plus the scanning and empty states.

Windowed from the start, not as a later pass: real local libraries reach 16k
tracks and that is exactly the surface where the Slint port freezes. Artwork
is id-keyed and evicted with the window.

Playback is source-aware — a local track plays from disk through the player's
play_data seam, because the Qobuz tier-walk needs a client and fails on every
local advance. TrackRow gains an opt-out for its drag source: a local row's id
is a local database row, and the sidebar's drop handler forwards whatever it
receives to `playlist add-tracks` as a Qobuz catalog id, so dragging one onto a
playlist would have silently added an unrelated track.

Not included, and tracked as follow-ups: Plex union and thumbnails, multi-select
and the bulk bars, ephemeral folders, the tag editor, A-Z jump strips, the
group-by dropdown, the tree's resize handle, per-tab scroll restore, and local
favourites on rows. Album detail renders inside the view rather than as its own
routed page.

cc
This commit is contained in:
vicrodh committed 2026-07-28 13:49:15 -06:00
1 parent cd1f4486b7
commit 0799d59e01
10 files changed
+3689 -3

No files matched your search

+7 -1
View File
@@ -52,6 +52,12 @@ Rectangle {
property bool menuShowGoTo: true
property bool menuShowFavorite: true
property bool menuShowRemove: false
// Drag source arm (additive; default = the existing behaviour). The
// Local Library rows set this false: their `item.id` is a local DB row
// id, and the sidebar drop handler forwards whatever it receives to
// `playlist add-tracks` as a QOBUZ catalog id — a local row dropped on a
// playlist would silently add an unrelated catalog track.
property bool draggable: true
signal playRequested()
signal enqueueRequested(string mode)
@@ -341,7 +347,7 @@ Rectangle {
cursorShape: root.clickPlays ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: function (mouse) { root.downPos = Qt.point(mouse.x, mouse.y) }
onPositionChanged: function (mouse) {
if (!pressed) return
if (!pressed || !root.draggable) return
const g = mapToItem(null, mouse.x, mouse.y)
if (!root.dragging
&& (Math.abs(mouse.x - root.downPos.x) > 6