Files
qbz/crates/qbz-slint/ui/primitives/TrackContextMenu.slint
T
vicrodh 70fe3effb7 feat(slint): go-to album/artist on local track menus; playlist header buttons homologated
Local-surface track menus gain Go to Album / Go to Artist (owner
improvement - Tauri omits them on local rows), routed by source: local
rows navigate via their album_group_key and artist name to the local
album view and LocalLibrary Artists; plex rows resolve the content-hash
plex album key (local_queue_track parity); qobuz_download rows recover
their real Qobuz album/artist ids through get_track and open the Qobuz
pages, falling back to the local destinations when the resolve fails so
the click always lands. Entries hide when keys are absent (ephemeral,
file:/broken:/unresolved rows). Playlist-detail sidecar rows opt in with
the same routing, and the global go-to arms divert snapshot local/plex
rows before the catalog parse (a local row id would have been mis-typed
as a Qobuz catalog id).

Playlist header drops its local CircleAction copy (transparent + 1px
border ring) for the shared user-verified primitive (solid primary disc,
translucent fills, dim-on-disabled) - all seven actions restyled,
callbacks untouched.

cc
2026-06-10 13:26:34 -06:00

186 lines
8.1 KiB
Plaintext

// Track-row "more" context menu — flat, ported 1:1 from the Tauri
// TrackMenu component. Items that have no Slint wiring yet emit a
// media-action with an action string the runtime currently ignores
// (no-op until backend wiring lands).
import { ContextMenu } from "ContextMenu.slint";
import { ContextMenuItem } from "ContextMenuItem.slint";
export component TrackContextMenu inherits PopupWindow {
in property <string> track-id;
// Per-source applicability (the surrounding row sets these from the track's
// source). `qobuz-actions` = the Qobuz-backed block (radio, share,
// make-offline, go-to album/artist, track info) — on for Qobuz + offline,
// off for local / Plex / ephemeral. `favorite-action` / `mixtape-action`
// gate the favorite and Add-to-Mixtape entries independently of the block
// (library surfaces show them on rows the Qobuz block is dead for — spec
// §3.1/§3.2); they default to the block so Qobuz surfaces are unchanged.
// `playlist-action` = Add to playlist — on for Qobuz + offline + local +
// Plex (local playlists store each source's own ref), off for ephemeral.
// `remove-from-playlist-action` = the playlist-detail per-row removal
// (every row source — the arm splits by id namespace); the surface
// gates it on ownership (spec §3.1).
// `track-info-action` lets a surface HIDE the Track Info entry while
// its target feature is missing there (spec §3.3) — defaults on so
// other surfaces are untouched.
// `go-album-action` / `go-artist-action` — the Go to album / Go to
// artist pair, gated independently of the block so library surfaces
// can show them on local/plex/qobuz_download rows too (owner
// improvement over Tauri, which omits both on local rows; the Rust
// arms route by row source). Default to the block so Qobuz surfaces
// are unchanged.
// The transient actions (play / next / queue) always show.
in property <bool> qobuz-actions: true;
in property <bool> favorite-action: root.qobuz-actions;
in property <bool> mixtape-action: root.qobuz-actions;
in property <bool> playlist-action: true;
in property <bool> remove-from-playlist-action: false;
in property <bool> track-info-action: true;
in property <bool> go-album-action: root.qobuz-actions;
in property <bool> go-artist-action: root.qobuz-actions;
// The row's offline-cache status (TrackItem.cache-status): when cached
// (3) the single "Make available offline" entry swaps for the
// Refresh / Remove pair — Tauri's ready-state submenu, rendered flat
// (cosmetic-flat per spec §3.5).
in property <int> cache-status: 0;
callback media-action(string, string, string);
width: 224px;
// 3 transient + optional favorite/mixtape/add-to-playlist/remove +
// optional go-to pair + the Qobuz block (3 fixed + 1-or-2 offline
// entries + optional track info).
height: (3
+ (root.favorite-action ? 1 : 0)
+ (root.mixtape-action ? 1 : 0)
+ (root.playlist-action ? 1 : 0)
+ (root.remove-from-playlist-action ? 1 : 0)
+ (root.go-album-action ? 1 : 0)
+ (root.go-artist-action ? 1 : 0)
+ (root.qobuz-actions
? 3 + (root.cache-status == 3 ? 2 : 1) + (root.track-info-action ? 1 : 0)
: 0)) * 30px;
ContextMenu {
ContextMenuItem {
icon: @image-url("../assets/icons/play.svg");
label: @tr("Play now");
clicked => {
root.media-action("track", root.track-id, "play");
}
}
ContextMenuItem {
icon: @image-url("../assets/icons/list-plus.svg");
label: @tr("Play next");
clicked => {
root.media-action("track", root.track-id, "play-next");
}
}
ContextMenuItem {
icon: @image-url("../assets/icons/list-end.svg");
label: @tr("Add to queue");
clicked => {
root.media-action("track", root.track-id, "queue");
}
}
if root.qobuz-actions: ContextMenuItem {
icon: @image-url("../assets/icons/radio.svg");
label: @tr("Create radio");
clicked => {
root.media-action("track", root.track-id, "create-radio");
}
}
if root.favorite-action: ContextMenuItem {
icon: @image-url("../assets/icons/heart.svg");
label: @tr("Add to library");
clicked => {
root.media-action("track", root.track-id, "favorite");
}
}
if root.mixtape-action: ContextMenuItem {
icon: @image-url("../assets/icons/cassette-tape.svg");
label: @tr("Add to mixtape");
clicked => {
root.media-action("track", root.track-id, "add-to-mixtape");
}
}
if root.playlist-action: ContextMenuItem {
icon: @image-url("../assets/icons/list-music.svg");
label: @tr("Add to playlist");
clicked => {
root.media-action("track", root.track-id, "add-to-playlist");
}
}
// Remove from playlist (danger) — playlist detail only, after Add
// to playlist (Tauri group order, spec §1.3 #8).
if root.remove-from-playlist-action: ContextMenuItem {
icon: @image-url("../assets/icons/trash-2.svg");
label: @tr("Remove from playlist");
danger: true;
clicked => {
root.media-action("track", root.track-id, "remove-from-playlist");
}
}
if root.qobuz-actions: ContextMenuItem {
icon: @image-url("../assets/icons/link.svg");
label: @tr("Share Qobuz link");
clicked => {
root.media-action("track", root.track-id, "share-qobuz");
}
}
if root.qobuz-actions: ContextMenuItem {
icon: @image-url("../assets/icons/link.svg");
label: @tr("Share Song.link");
clicked => {
root.media-action("track", root.track-id, "share-songlink");
}
}
// Offline-cache block — cached-state-aware (Tauri's ready-state
// submenu, flat per spec §3.5): not cached -> Make available
// offline; cached -> Refresh + Remove (icons 1:1 with Tauri's
// RefreshCw / Trash2).
if root.qobuz-actions && root.cache-status != 3: ContextMenuItem {
icon: @image-url("../assets/icons/cloud-download.svg");
label: @tr("Make available offline");
clicked => {
root.media-action("track", root.track-id, "download");
}
}
if root.qobuz-actions && root.cache-status == 3: ContextMenuItem {
icon: @image-url("../assets/icons/refresh-cw.svg");
label: @tr("Refresh offline copy");
clicked => {
root.media-action("track", root.track-id, "recache");
}
}
if root.qobuz-actions && root.cache-status == 3: ContextMenuItem {
icon: @image-url("../assets/icons/trash-2.svg");
label: @tr("Remove offline copy");
danger: true;
clicked => {
root.media-action("track", root.track-id, "uncache");
}
}
if root.go-album-action: ContextMenuItem {
icon: @image-url("../assets/icons/disc-3.svg");
label: @tr("Go to album");
clicked => {
root.media-action("track", root.track-id, "go-to-album");
}
}
if root.go-artist-action: ContextMenuItem {
icon: @image-url("../assets/icons/user.svg");
label: @tr("Go to artist");
clicked => {
root.media-action("track", root.track-id, "go-to-artist");
}
}
if root.qobuz-actions && root.track-info-action: ContextMenuItem {
icon: @image-url("../assets/icons/info.svg");
label: @tr("Track info");
clicked => {
root.media-action("track", root.track-id, "track-info");
}
}
}
}