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
This commit is contained in:
vicrodh committed 2026-06-10 13:26:34 -06:00
1 parent 065e6be7f4
commit 70fe3effb7
4 files changed
+219 -49

No files matched your search

+11 -44
View File
@@ -8,6 +8,7 @@ import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { PlaylistState, PlaylistActions, DragState, NavState, OfflineState, BulkAction } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { CircleAction } from "../primitives/CircleAction.slint";
import { TrackRow } from "../primitives/TrackRow.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
import { ListScrollbar } from "../primitives/ListScrollbar.slint";
@@ -16,50 +17,6 @@ import { MultiSelectBar } from "../primitives/MultiSelectBar.slint";
import { NavButtons } from "../shell/NavButtons.slint";
import { ListView } from "std-widgets.slint";
// Header action button — homologated 1:1 with AlbumPageView's
// CircleAction: a ghost circle (translucent fill + faint ring, never a
// solid accent fill) so the playlist transport row matches the album
// one. Adds `active` (accent-tinted glyph for toggled state, e.g.
// favorite / select mode) and `enabled` (dimmed + non-interactive),
// which the album header doesn't need but the playlist one does.
component CircleAction inherits Rectangle {
in property <image> icon;
in property <bool> primary: false;
in property <bool> active: false;
in property <length> diameter: 40px;
in property <bool> enabled: true;
callback clicked();
width: root.diameter;
height: root.diameter;
border-radius: root.diameter / 2;
opacity: root.enabled ? 1.0 : 0.4;
// Ghost circle — translucent fill with a faint ring (matches the
// Tauri transport controls + AlbumPageView).
background: ta.has-hover ? Theme.surface-hover : transparent;
border-width: 1px;
border-color: ta.has-hover ? Theme.text-disabled : Theme.border-subtle;
QbzIcon {
source: root.icon;
width: root.primary ? 19px : 17px;
height: root.primary ? 19px : 17px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: root.active
? Theme.accent
: (ta.has-hover ? Theme.text-primary : Theme.text-secondary);
}
ta := TouchArea {
mouse-cursor: root.enabled ? pointer : default;
clicked => {
if (root.enabled) {
root.clicked();
}
}
}
}
// One row in the Sort menu. The active field shows its direction arrow
// (asc/desc); re-selecting it toggles direction.
component SortOption inherits Rectangle {
@@ -718,6 +675,16 @@ export component PlaylistView inherits Rectangle {
// of rendering a no-op entry. Other surfaces keep
// the default until their own audit.
track-info-action: false;
// Go to Album / Go to Artist on the local/plex
// sidecar rows too (Qobuz rows already carry the
// pair via the Qobuz block) — owner improvement
// over Tauri. Only RESOLVED rows (numeric snapshot
// ids) qualify: the "file:"/"broken:"/unresolved-
// "plex:" fallbacks have no navigable album/artist
// keys, so their entries stay hidden (no dead
// clicks).
local-goto-actions: (track.source == "local" || track.source == "plex")
&& track.id.is-float();
multi-select-mode: PlaylistState.multi-select-mode;
media-action(kind, id, action) => {
if (action == "play") {
@@ -23,6 +23,12 @@ export component TrackContextMenu inherits PopupWindow {
// `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;
@@ -30,6 +36,8 @@ export component TrackContextMenu inherits PopupWindow {
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
@@ -38,15 +46,18 @@ export component TrackContextMenu inherits PopupWindow {
callback media-action(string, string, string);
width: 224px;
// 3 transient + optional favorite/mixtape/add-to-playlist/remove + the
// Qobuz block (5 fixed + 1-or-2 offline entries + optional track info).
// 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
? 5 + (root.cache-status == 3 ? 2 : 1) + (root.track-info-action ? 1 : 0)
? 3 + (root.cache-status == 3 ? 2 : 1) + (root.track-info-action ? 1 : 0)
: 0)) * 30px;
ContextMenu {
@@ -149,14 +160,14 @@ export component TrackContextMenu inherits PopupWindow {
root.media-action("track", root.track-id, "uncache");
}
}
if root.qobuz-actions: ContextMenuItem {
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.qobuz-actions: ContextMenuItem {
if root.go-artist-action: ContextMenuItem {
icon: @image-url("../assets/icons/user.svg");
label: @tr("Go to artist");
clicked => {
@@ -63,6 +63,16 @@ export component TrackRow inherits Rectangle {
// Track Info menu entry visibility — a surface whose Track Info is
// dead hides it (spec §3.3); default on keeps other surfaces unchanged.
in property <bool> track-info-action: true;
// Go to Album / Go to Artist on LOCAL rows (owner improvement over
// Tauri, which omits both there): defaults ON for every persistent
// row of a force-local-menu surface (LocalLibrary Tracks/folder
// detail, local album detail — their Rust arms route by row source);
// mixed surfaces (playlist detail) opt rows in explicitly, because
// only RESOLVED local/plex sidecar rows have navigable keys (the
// "file:"/"broken:"/unresolved fallbacks must keep the entries
// hidden — no dead clicks).
in property <bool> local-goto-actions: root.force-local-menu
&& root.track.source != "ephemeral";
// Custom-order reorder controls (up/down chevrons) at the leading
// edge — used by the playlist custom sort.
in property <bool> show-reorder: false;
@@ -631,6 +641,12 @@ export component TrackRow inherits Rectangle {
playlist-action: root.track.source != "ephemeral";
remove-from-playlist-action: root.remove-from-playlist-action;
track-info-action: root.track-info-action;
// Go to album / artist: the Qobuz block's pair, ALSO enabled on
// local-surface rows (local-goto-actions doc above) — the Rust
// arms route by source (local album view / LocalLibrary artist
// for local+plex, the real Qobuz pages for qobuz_download).
go-album-action: self.qobuz-actions || root.local-goto-actions;
go-artist-action: self.qobuz-actions || root.local-goto-actions;
// Cached-state-aware offline entry (Refresh/Remove when cached) —
// straight from the row model.
cache-status: root.track.cache-status;