feat(myqbz): collapsible My QBZ sidebar with per-element hide and toolbar
Opt-in collapsible My QBZ tree in the sidebar (Settings > Navigation): a branding-titled parent over Mixtapes and Collections sections, each header a link to its grid with a chevron that only collapses. Header text and icon on the left, a Playlists-parity toolbar floating right (search, new, sort A-Z/Z-A, refresh, collapse) with the chevron aligned to the playlist carets. The tree feed rides QbzShell (QbzMyQbz is not reliably registered in the shell context) and is published through publish_sidebar, so it lands reliably at startup. Per-element visibility reuses the mixtape hidden flag (repo::set_hidden): a row's right-click hides it from the sidebar, and the detail hero menu toggles "Hide from sidebar" / "Show in sidebar". Collapse state for the playlist tree and all three My QBZ sections is persisted. The collapsible tree and the flat My QBZ entry are mutually exclusive — the classic row is suppressed whenever the tree is on. Settings gains a Navigation section (Navigation + Search moved out of Appearance). Detail view also gains a discreet, column-aligned sub-header over the inline track list. Eight locales updated. cc
This commit is contained in:
22 files changed
+1375
-110
No files matched your search
@@ -192,6 +192,13 @@ Rectangle {
|
||||
|
||||
readonly property bool loading: doc.loading === true
|
||||
readonly property bool found: doc.found === true
|
||||
// The collapsible My QBZ opt-in gates the sidebar hide/show toggle in the
|
||||
// hero overflow — the concept is meaningless while the flat entry is used.
|
||||
// GUARDED parse, the NavFlyout.qml:65-70 precedent.
|
||||
readonly property bool collapsibleMyqbzOn: {
|
||||
try { return JSON.parse(QbzBridge.settingsJson).collapsibleMyqbz === true }
|
||||
catch (e) { return false }
|
||||
}
|
||||
readonly property string kind: doc.kind || ""
|
||||
readonly property int itemCount: doc.itemCount || 0
|
||||
readonly property string viewMode: doc.viewMode || "list"
|
||||
@@ -816,6 +823,45 @@ Rectangle {
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
// Sub-header for the expanded track list.
|
||||
// The item's OWN columns (Item/Type/Source/
|
||||
// Quality/Tracks/Year) name the collection
|
||||
// rows, not these child tracks — so a small,
|
||||
// muted, column-aligned label band sits over
|
||||
// the inline tracks instead. Reuses the
|
||||
// shared TrackListHeader with the SAME arms
|
||||
// the inline TrackRow sets (menu only), so
|
||||
// "#", "Title", "Duration", "Quality" land on
|
||||
// their columns by construction. Shown only
|
||||
// once the tracks have loaded (never over the
|
||||
// spinner or the empty state).
|
||||
Item {
|
||||
visible: !rowCell.expandLoading
|
||||
&& rowCell.tracksLoaded
|
||||
&& rowCell.inlineTracks.length > 0
|
||||
width: parent.width
|
||||
height: visible ? 22 : 0
|
||||
TrackListHeader {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
showArtwork: false
|
||||
showSource: false
|
||||
showAlbum: false
|
||||
showFavorite: false
|
||||
showDownload: false
|
||||
showMenu: true
|
||||
showReorder: false
|
||||
}
|
||||
// Discreet hairline under the labels.
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: theme.surfaceElevated
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: rowCell.inlineTracks
|
||||
delegate: TrackRow {
|
||||
@@ -1265,6 +1311,15 @@ Rectangle {
|
||||
m.push({ "label": root.kind === "mixtape"
|
||||
? t("Convert to Collection", r) : t("Convert to Mixtape", r),
|
||||
"icon": "cassette-tape", "action": "convert" })
|
||||
// Sidebar visibility — only meaningful with the collapsible tree on.
|
||||
// The label reflects the CURRENT state (the mechanism playlists carry).
|
||||
if (root.collapsibleMyqbzOn) {
|
||||
m.push({ "sep": true })
|
||||
m.push({ "label": root.doc.hidden === true
|
||||
? t("Show in sidebar", r) : t("Hide from sidebar", r),
|
||||
"icon": root.doc.hidden === true ? "eye" : "eye-off",
|
||||
"action": "toggle-hidden" })
|
||||
}
|
||||
m.push({ "sep": true })
|
||||
m.push({ "label": t("Delete", r), "icon": "trash-2", "action": "delete", "danger": true })
|
||||
return m
|
||||
@@ -1277,6 +1332,8 @@ Rectangle {
|
||||
else if (a === "clear-cover") QbzMyQbz.removeCover()
|
||||
else if (a === "play-mode") QbzMyQbz.togglePlayMode()
|
||||
else if (a === "convert") QbzMyQbz.convertKind()
|
||||
else if (a === "toggle-hidden")
|
||||
QbzMyQbz.setCollectionHidden(root.doc.id, root.doc.hidden !== true)
|
||||
else if (a === "delete") QbzMyQbz.openDelete()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user