feat(slint): sidebar context menus, in-app toast, SearchView + Playlist Manager polish
Sidebar: - Fix "Recursion detected" panic from focusing the playlist search input in init (defer via a single-shot Timer). - Right-click context menus: playlist (Edit, Hide from sidebar, Move to folder with folder search + Move to root) and folder (Edit, Hide from sidebar), built on ContextMenu/ContextMenuItem; remove the unreachable hover "..." button. - Filter hidden playlists/folders out of the list; add folders::set_folder_hidden. - Folder list in the move-to-folder menu scrolls (cap 5 rows); close-on-click-outside so the folder-search input no longer dismisses the menu. - Sort-menu Refresh now uses a dedicated refresh-cw icon. Toast: in-app ToastKind/ToastState + Toast overlay mounted at the app root, crate::toast (show/hide, per-kind auto-hide via a reusable Timer, cross-thread *_weak helpers); surface play album/artist load failures. SearchView: title inline with NavButtons + searchType filters floated right; tab strip on its own row; Most-popular hero for a track uses the new SearchTrackHero, sized to the artist card. Playlist Manager: list view renders compact PmListRow rows instead of cards; grid/folder card fonts tightened to match Tauri; folder card centered with hover-reveal edit; search input narrowed to 70%. cc
This commit is contained in:
18 files changed
+1143
-282
No files matched your search
@@ -29,7 +29,8 @@ import { PmFolderIcon } from "../primitives/PmFolderIcon.slint";
|
||||
// Search field — leading magnifier, input, trailing X to clear.
|
||||
component PmSearch inherits Rectangle {
|
||||
height: 34px;
|
||||
width: 240px;
|
||||
// 70% of the former 240px width (task: shrink the PM search input).
|
||||
width: 168px;
|
||||
border-radius: 6px;
|
||||
background: Theme.surface-elevated;
|
||||
border-width: 1px;
|
||||
@@ -267,6 +268,9 @@ component PmFolderCard inherits Rectangle {
|
||||
in property <PmFolderItem> folder;
|
||||
callback open();
|
||||
callback edit();
|
||||
// Matches Tauri's `.folder-card`: 160 wide, 16px padding, centered
|
||||
// icon/name/count column. Hover reveals the edit pencil (opacity 0->1)
|
||||
// and lifts the surface — same affordance as Tauri.
|
||||
width: 160px;
|
||||
height: 128px;
|
||||
border-radius: 10px;
|
||||
@@ -275,43 +279,56 @@ component PmFolderCard inherits Rectangle {
|
||||
|
||||
VerticalLayout {
|
||||
padding: 16px;
|
||||
spacing: 10px;
|
||||
PmFolderIcon {
|
||||
folder: root.folder;
|
||||
size: 64px;
|
||||
spacing: 8px;
|
||||
alignment: center;
|
||||
// Centered icon.
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
PmFolderIcon {
|
||||
folder: root.folder;
|
||||
size: 64px;
|
||||
}
|
||||
}
|
||||
// Centered single-line name (13px, elided — matches `.folder-name`).
|
||||
Text {
|
||||
text: root.folder.name;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
font-size: 13px;
|
||||
font-weight: Typography.medium;
|
||||
horizontal-alignment: center;
|
||||
overflow: elide;
|
||||
height: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
// Centered count (12px — matches `.folder-count`).
|
||||
Text {
|
||||
text: root.folder.count == 1
|
||||
? @tr("1 playlist")
|
||||
: @tr("{} playlists", root.folder.count);
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.legal;
|
||||
font-size: 12px;
|
||||
horizontal-alignment: center;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
// Hover edit pencil (top-right).
|
||||
// Hover edit pencil (top-right). Hidden until the card (or the button)
|
||||
// is hovered, exactly like Tauri's `.folder-card:hover .folder-edit-btn`.
|
||||
edit-btn := Rectangle {
|
||||
x: parent.width - self.width - 8px;
|
||||
y: 8px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
opacity: (ta.has-hover || edit-ta.has-hover) ? 1.0 : 0.0;
|
||||
background: edit-ta.has-hover ? Theme.surface-card : #00000066;
|
||||
animate opacity { duration: 150ms; }
|
||||
background: edit-ta.has-hover ? Theme.surface-card : Theme.surface-elevated;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/pen-line.svg");
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: Theme.text-primary;
|
||||
tint: edit-ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
edit-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
@@ -344,9 +361,10 @@ component PmGridCard inherits Rectangle {
|
||||
callback move-down();
|
||||
|
||||
width: 160px;
|
||||
// pad(10) + [reorder 22 + gap 8] + collage 140 + gap 8 + name 36 +
|
||||
// gap 8 + footer 24 + pad(10).
|
||||
height: root.can-reorder ? 266px : 236px;
|
||||
// pad(10) + [reorder 22 + gap 8] + collage 140 + gap 8 + name 18 +
|
||||
// gap 8 + footer 22 + pad(10). Tauri's name is a single-line 13px
|
||||
// title (the .grid-item .name); we elide instead of 2-line clamp.
|
||||
height: root.can-reorder ? 250px : 220px;
|
||||
border-radius: 8px;
|
||||
background: ta.has-hover ? Theme.surface-hover : Theme.surface-card;
|
||||
opacity: root.item.is-hidden ? 0.6 : 1.0;
|
||||
@@ -400,24 +418,26 @@ component PmGridCard inherits Rectangle {
|
||||
Text {
|
||||
text: root.item.name;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
font-size: 13px;
|
||||
font-weight: Typography.medium;
|
||||
wrap: word-wrap;
|
||||
overflow: elide;
|
||||
width: 140px;
|
||||
height: 36px;
|
||||
height: 18px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Footer: meta + actions.
|
||||
HorizontalLayout {
|
||||
width: 140px;
|
||||
height: 22px;
|
||||
alignment: space-between;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
Text {
|
||||
text: root.item.tracks-line;
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.legal;
|
||||
font-size: 11px;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
@@ -460,8 +480,8 @@ component PmGridCard inherits Rectangle {
|
||||
// card-body click opens the playlist while the footer buttons keep
|
||||
// their own clicks. Sits below the reorder header when present.
|
||||
width: parent.width;
|
||||
// pad(10) + collage(140) + gap(8) + name(36) = 194.
|
||||
height: 194px;
|
||||
// pad(10) + collage(140) + gap(8) + name(18) = 176.
|
||||
height: 176px;
|
||||
y: root.can-reorder ? 30px : 0px;
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
@@ -487,11 +507,11 @@ component PmListRow inherits Rectangle {
|
||||
callback move-down();
|
||||
callback move-to-folder(string /* folder id, "" = root */);
|
||||
|
||||
// Matches Tauri's `.list-item`: a solid surface row (bg-secondary),
|
||||
// hover lifts to bg-tertiary. Compact: 8px/12px padding, 48px collage.
|
||||
height: 64px;
|
||||
border-radius: 8px;
|
||||
background: ta.has-hover
|
||||
? Theme.surface-hover
|
||||
: (Math.mod(root.index, 2) == 1 ? #ffffff07 : transparent);
|
||||
border-radius: 6px;
|
||||
background: ta.has-hover ? Theme.surface-hover : Theme.surface-card;
|
||||
opacity: root.item.is-hidden ? 0.6 : 1.0;
|
||||
|
||||
HorizontalLayout {
|
||||
@@ -528,57 +548,69 @@ component PmListRow inherits Rectangle {
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
spacing: 2px;
|
||||
// Name — single-line 14px elided (matches `.list-item .name`).
|
||||
Text {
|
||||
text: root.item.name;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
font-size: 14px;
|
||||
font-weight: Typography.medium;
|
||||
overflow: elide;
|
||||
}
|
||||
// Meta line — 12px muted (matches `.list-item .meta`).
|
||||
HorizontalLayout {
|
||||
spacing: 6px;
|
||||
alignment: start;
|
||||
Text {
|
||||
text: root.item.tracks-line;
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.legal;
|
||||
font-size: 12px;
|
||||
}
|
||||
if root.item.local-line != "": Text {
|
||||
text: root.item.local-line;
|
||||
color: #22c55e;
|
||||
font-size: Typography.legal;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Play-count badge.
|
||||
// Play-count badge — rounded chip (matches `.play-count-badge`).
|
||||
if root.item.play-count > 0: VerticalLayout {
|
||||
alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: 4px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/chart-no-axes-column.svg");
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
tint: Theme.text-muted;
|
||||
Rectangle {
|
||||
width: pc-row.preferred-width + 16px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
background: Theme.surface-elevated;
|
||||
pc-row := HorizontalLayout {
|
||||
x: 8px;
|
||||
width: self.preferred-width;
|
||||
height: parent.height;
|
||||
spacing: 4px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/chart-no-axes-column.svg");
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
tint: Theme.text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: root.item.play-count;
|
||||
color: Theme.text-muted;
|
||||
font-size: Typography.legal;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: root.item.play-count;
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Actions (hover).
|
||||
// Actions — always visible (matches Tauri's `.list-item` buttons).
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: 0px;
|
||||
opacity: ta.has-hover ? 1.0 : 0.35;
|
||||
PmActionButton {
|
||||
icon: root.item.is-favorite
|
||||
? @image-url("../assets/icons/heart-filled.svg")
|
||||
@@ -846,7 +878,7 @@ export component PlaylistManagerView inherits Rectangle {
|
||||
property <length> card-w: 160px;
|
||||
// Card height depends on whether reorder controls show (matches
|
||||
// PmGridCard's height).
|
||||
property <length> card-h: PlaylistManagerState.can-reorder ? 266px : 236px;
|
||||
property <length> card-h: PlaylistManagerState.can-reorder ? 250px : 220px;
|
||||
|
||||
// The view-mode cycle button shows the NEXT mode's icon (Tauri).
|
||||
property <image> next-view-icon:
|
||||
|
||||
Reference in new issue
Block a user