feat(slint): sidebar playlists list + create-playlist modal
- SidebarState/SidebarActions + crate::sidebar: the user's playlists
load into the left-nav list on shell entry; clicking a row opens
the playlist detail view, with the open playlist highlighted
(active-id). Empty state shown when there are none.
- The sidebar "+" opens a CreatePlaylistModal (name field + public
toggle); submit calls create_playlist, refreshes the sidebar, and
navigates to the new playlist.
cc
This commit is contained in:
7 files changed
+429
-9
No files matched your search
@@ -11,6 +11,7 @@ import { Sidebar } from "Sidebar.slint";
|
||||
import { HeaderBar } from "HeaderBar.slint";
|
||||
import { HeaderMenuOverlay } from "HeaderMenuOverlay.slint";
|
||||
import { PlaylistPickerModal } from "../primitives/PlaylistPickerModal.slint";
|
||||
import { CreatePlaylistModal } from "../primitives/CreatePlaylistModal.slint";
|
||||
import { PlayerBar } from "PlayerBar.slint";
|
||||
import { QueueSidebar } from "QueueSidebar.slint";
|
||||
import { LyricsSidebar } from "LyricsSidebar.slint";
|
||||
@@ -222,4 +223,7 @@ export component AppShell inherits Rectangle {
|
||||
// Global "Add to playlist" picker overlay, opened from any track
|
||||
// context menu.
|
||||
PlaylistPickerModal { }
|
||||
|
||||
// Global "Create playlist" modal, opened from the sidebar "+".
|
||||
CreatePlaylistModal { }
|
||||
}
|
||||
@@ -12,9 +12,49 @@
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { ShellState } from "../state.slint";
|
||||
import { ShellState, SidebarState, SidebarActions, SidebarPlaylistItem } from "../state.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
// One playlist row in the sidebar.
|
||||
component PlaylistRow inherits Rectangle {
|
||||
in property <SidebarPlaylistItem> item;
|
||||
in property <bool> active: false;
|
||||
callback clicked(string);
|
||||
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
background: ta.has-hover || root.active ? Theme.surface-hover : transparent;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
spacing: 8px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/list-music.svg");
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
tint: root.active ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: root.item.name;
|
||||
color: root.active ? Theme.text-primary : Theme.text-secondary;
|
||||
font-size: 13px;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
root.clicked(root.item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component Sidebar inherits Rectangle {
|
||||
// Rendered width — 0 when collapsed, otherwise the user-set width.
|
||||
width: ShellState.sidebar-collapsed ? 0px : ShellState.sidebar-width;
|
||||
@@ -44,11 +84,25 @@ export component Sidebar inherits Rectangle {
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/plus.svg");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
tint: Theme.text-muted;
|
||||
Rectangle {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
background: plus-ta.has-hover ? Theme.surface-hover : transparent;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/plus.svg");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: plus-ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
plus-ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
SidebarActions.create-playlist();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,8 +112,20 @@ export component Sidebar inherits Rectangle {
|
||||
horizontal-scrollbar-policy: ScrollBarPolicy.always-off;
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
// Real playlist rows are populated when the sidebar
|
||||
// milestone wires playlist data. No fixtures.
|
||||
alignment: start;
|
||||
for pl in SidebarState.playlists: PlaylistRow {
|
||||
item: pl;
|
||||
active: pl.id == SidebarState.active-id;
|
||||
clicked(id) => {
|
||||
SidebarActions.open-playlist(id);
|
||||
}
|
||||
}
|
||||
if SidebarState.playlists.length == 0 && !SidebarState.loading: Text {
|
||||
text: @tr("No playlists yet.");
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user