diff --git a/src/lib/components/FavoritePlaylistCard.svelte b/src/lib/components/FavoritePlaylistCard.svelte
new file mode 100644
index 000000000..e8dad458c
--- /dev/null
+++ b/src/lib/components/FavoritePlaylistCard.svelte
@@ -0,0 +1,351 @@
+
+
+
e.key === 'Enter' && onclick?.()}
+>
+
+
+
+
0}
+ style="--ticker-offset: {titleOffset}; --ticker-duration: {titleDuration};"
+ bind:this={titleRef}
+ >
+ {playlist.name}
+
+
+ {playlist.owner.name}
+ -
+ {playlist.tracks_count} tracks
+
+
+
+
+{#if menuOpen}
+
+{/if}
+
+
diff --git a/src/lib/components/views/FavoritesView.svelte b/src/lib/components/views/FavoritesView.svelte
index 04b7bf45e..e1fe1386b 100644
--- a/src/lib/components/views/FavoritesView.svelte
+++ b/src/lib/components/views/FavoritesView.svelte
@@ -6,7 +6,7 @@
import TrackRow from '../TrackRow.svelte';
import QualityBadge from '../QualityBadge.svelte';
import VirtualizedTrackList from '../VirtualizedTrackList.svelte';
- import PlaylistCollage from '../PlaylistCollage.svelte';
+ import FavoritePlaylistCard from '../FavoritePlaylistCard.svelte';
import FavoritesEditModal from '../FavoritesEditModal.svelte';
import ViewTransition from '../ViewTransition.svelte';
import { type OfflineCacheStatus } from '$lib/stores/offlineCacheState';
@@ -95,6 +95,11 @@
onTrackReDownload?: (track: DisplayTrack) => void;
getTrackOfflineCacheStatus?: (trackId: number) => { status: OfflineCacheStatus; progress: number };
onPlaylistSelect?: (playlistId: number) => void;
+ onPlaylistPlay?: (playlistId: number) => void;
+ onPlaylistPlayNext?: (playlistId: number) => void;
+ onPlaylistPlayLater?: (playlistId: number) => void;
+ onPlaylistRemoveFavorite?: (playlistId: number) => void;
+ onPlaylistShareQobuz?: (playlistId: number) => void;
selectedTab?: TabType;
onTabNavigate?: (tab: TabType) => void;
activeTrackId?: number | null;
@@ -146,6 +151,11 @@
onTrackReDownload,
getTrackOfflineCacheStatus,
onPlaylistSelect,
+ onPlaylistPlay,
+ onPlaylistPlayNext,
+ onPlaylistPlayLater,
+ onPlaylistRemoveFavorite,
+ onPlaylistShareQobuz,
selectedTab,
onTabNavigate,
activeTrackId = null,
@@ -2083,13 +2093,15 @@
{:else}
{#each filteredPlaylists as playlist (playlist.id)}
-
+
onPlaylistSelect?.(playlist.id)}
+ onPlay={() => onPlaylistPlay?.(playlist.id)}
+ onPlayNext={() => onPlaylistPlayNext?.(playlist.id)}
+ onPlayLater={() => onPlaylistPlayLater?.(playlist.id)}
+ onRemoveFavorite={() => onPlaylistRemoveFavorite?.(playlist.id)}
+ onShareQobuz={() => onPlaylistShareQobuz?.(playlist.id)}
+ />
{/each}
{/if}
@@ -2937,49 +2949,9 @@
/* Playlist grid styles */
.playlist-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ grid-template-columns: repeat(auto-fill, 140px);
gap: 24px;
- }
-
- .playlist-card {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 16px;
- background-color: var(--bg-secondary);
- border: none;
- border-radius: 12px;
- cursor: pointer;
- transition: background-color 150ms ease;
- }
-
- .playlist-card:hover {
- background-color: var(--bg-tertiary);
- }
-
- .playlist-artwork {
- width: 140px;
- height: 140px;
- border-radius: 8px;
- overflow: hidden;
- margin-bottom: 12px;
- }
-
- .playlist-name {
- font-size: 14px;
- font-weight: 600;
- color: var(--text-primary);
- text-align: center;
- max-width: 140px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .playlist-meta {
- font-size: 12px;
- color: var(--text-muted);
- margin-top: 4px;
+ justify-content: start;
}
/* Artist Two-Column Sidepanel Layout */
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index a68a6a3af..1abc78043 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1184,6 +1184,18 @@
showToast('Playlist link copied to clipboard', 'success');
}
+ async function removePlaylistFavoriteById(playlistId: number) {
+ try {
+ await invoke('playlist_set_favorite', { playlistId, favorite: false });
+ showToast('Playlist removed from favorites', 'success');
+ sidebarRef?.refreshPlaylists();
+ sidebarRef?.refreshPlaylistSettings();
+ } catch (err) {
+ console.error('Failed to remove playlist favorite:', err);
+ showToast(`Failed to remove from favorites: ${err}`, 'error');
+ }
+ }
+
// Playback Functions - QobuzTrack from search results
async function handleTrackPlay(track: QobuzTrack) {
console.log('Playing track:', track);
@@ -3132,6 +3144,11 @@
onTrackReDownload={handleDisplayTrackDownload}
getTrackOfflineCacheStatus={getTrackOfflineCacheStatus}
onPlaylistSelect={selectPlaylist}
+ onPlaylistPlay={playPlaylistById}
+ onPlaylistPlayNext={queuePlaylistNextById}
+ onPlaylistPlayLater={queuePlaylistLaterById}
+ onPlaylistRemoveFavorite={removePlaylistFavoriteById}
+ onPlaylistShareQobuz={sharePlaylistQobuzLinkById}
selectedTab={getFavoritesTabFromView(activeView) ?? favoritesDefaultTab}
onTabNavigate={(tab) => navigateToFavorites(tab)}
activeTrackId={currentTrack?.id ?? null}