fix(myqbz): context menus — z-index, Remove/queue wiring, sidebar + album->collection

- Detail row ⋯ menu (and the hero overflow + filter popups) were PopupWindows
  with no explicit height, so they collapsed and the rows below painted over them.
  Gave each an explicit height (+ close-on-click) like every other app menu, so
  they paint above the rows.
- Row menu Remove now actually removes: it was a no-op ("remove" wasn't a RowMode)
  -> wired to remove the item (repo::remove_item by position, reload). Add-to-queue
  was also silently broken (emitted "play-later", unparsed) -> now "add-to-queue".
- Sidebar playlist right-click menu: added "Add to Mixtape/Collection" (opens the
  picker with the playlist payload).
- Album -> Collection: an album can now be added to ANY collection kind incl.
  artist_collection (removed the ArtistCollection exclusion in load_rows; Tauri
  allows adding an album to an artist discography).

Offline in the row menu is net-new (Tauri's collection row menu has none; it would
be a batch resolve-then-cache) and is NOT added here. Backend crates untouched.
cargo check -p qbz-slint green.

cc
This commit is contained in:
vicrodh committed 2026-06-09 15:17:03 -06:00
1 parent 47fdfe6622
commit 8b0df8325d
6 files changed
+125 -5

No files matched your search

+66
View File
@@ -2989,6 +2989,31 @@ fn wire_myqbz_detail(
});
}
// --- Per-row REMOVE (single item) -----------------------------------
// Routes ONE position through the audited bulk remover (remove-highest-
// first compaction + clear-selection + toast + reload) with a 1-element
// vec, so single-row remove reuses the exact same code path as the bulk
// "remove-selected" action — no duplicated removal logic.
{
let weak = window.as_weak();
let handle = tokio_rt.handle().clone();
let image_cache = image_cache.clone();
window.global::<Act>().on_remove_item(move |position| {
let Some(w) = weak.upgrade() else { return };
let id = w.global::<MyQbzDetailState>().get_id().to_string();
if id.is_empty() {
return;
}
myqbz_edit::remove_selected(
weak.clone(),
handle.clone(),
image_cache.clone(),
id,
vec![position],
);
});
}
// --- Expanded view-mode: inline tracks under every album/playlist (§8) -
// Fired when the expanded view-mode becomes active; fetches each
// expandable item's tracks (skipping already-cached rows).
@@ -7852,6 +7877,47 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
es.set_open(true);
});
}
{
// Add to Mixtape/Collection (sidebar playlist context menu) — build a
// 1-item playlist payload from the cached SidebarEntry row + the cached
// track count, then open the global AddToMixtapeModal. Because the
// item_type is "playlist", `open_add_to_mixtape` computes restrict=true
// → the picker lists mixtapes only and hides the "+ Collections" chip (a
// playlist can't live in a Collection). Mirrors the PlaylistManager path.
let weak = window.as_weak();
let handle = tokio_rt.handle().clone();
window
.global::<SidebarActions>()
.on_add_to_mixtape(move |id| {
use slint::Model;
let Some(w) = weak.upgrade() else { return };
let model = w.global::<SidebarState>().get_entries();
let Some(row) = (0..model.row_count())
.filter_map(|i| model.row_data(i))
.find(|e| e.kind == "playlist" && e.id == id)
else {
return;
};
let artwork = row.url1.to_string();
let item = myqbz_add::AddItem {
item_type: "playlist".into(),
source: "qobuz".into(),
source_item_id: id.to_string(),
title: row.name.to_string(),
subtitle: None,
artwork_url: (!artwork.is_empty()).then_some(artwork),
year: None,
// SidebarEntry doesn't carry the count; pull it from the
// sidebar cache by id (None if unknown — it's optional).
track_count: id
.parse::<u64>()
.ok()
.and_then(sidebar::playlist_track_count)
.map(|n| n as i32),
};
open_add_to_mixtape(weak.clone(), handle.clone(), vec![item]);
});
}
{
// Edit folder (sidebar context menu) — open the folder editor.
let weak = window.as_weak();
+4 -3
View File
@@ -159,9 +159,10 @@ pub fn load_rows(restrict_to_mixtape: bool, items: &[AddItem]) -> Vec<LoadedRow>
if restrict_to_mixtape {
cols.retain(|c| c.kind == CollectionKind::Mixtape);
} else {
// Albums can land in a Mixtape or a Collection, but NEVER an
// auto-generated ArtistCollection.
cols.retain(|c| c.kind != CollectionKind::ArtistCollection);
// An album can be added to ANY collection kind — Mixtape,
// Collection, OR Artist Collection. Tauri allows adding an album
// to an artist_collection (the user can augment a built
// discography), so no kind restriction here.
}
// Sort by last_played_at ?? updated_at DESC.
+11
View File
@@ -203,6 +203,17 @@ pub fn playlist_name_desc(id: u64) -> Option<(String, String)> {
NAME_DESC.lock().ok().and_then(|nd| nd.get(&id).cloned())
}
/// Total track count for `id`, from the last loaded playlist cache. Used by the
/// sidebar "Add to Mixtape/Collection" context action to populate the AddItem
/// `track_count` (the SidebarEntry struct doesn't carry it). Returns None when
/// the playlist is unknown.
pub fn playlist_track_count(id: u64) -> Option<u32> {
CACHE
.lock()
.ok()
.and_then(|c| c.playlists.iter().find(|p| p.id == id).map(|p| p.tracks_count))
}
/// Store the freshly-loaded data and render it.
pub fn apply(window: &AppWindow, data: SidebarData) {
if let Ok(mut cache) = CACHE.lock() {