diff --git a/CHANGELOG.md b/CHANGELOG.md index 89169b41..10e6ac09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.3] - 2026-03-16 + +### Fixed + +#### Random Mix — Genre Mix +- **Second "Play All" button removed**: The genre mix section had a redundant play button below the super-genre selector. The top-right button is now context-aware — it plays the genre mix when one is active, otherwise the regular mix. +- **"Play All" disabled during genre mix loading**: The button now stays grayed out with a live progress counter (`n / 50`) until all songs are fully loaded. Clicking while the list was still building sent only the songs loaded so far. +- **Over-fetching fixed**: Genre mix previously fetched up to 100+ songs and sliced to 50 at the end. Now the matched genre list is capped at 50 (randomly sampled when more match) so the total fetch stays close to 50 with no wasted server I/O. +- **Regular mix cache-busting**: `getRandomSongs` requests now include a timestamp parameter, preventing browser/axios from returning a cached response and showing the same list on every remix. +- **Display/state mismatch on remix**: Clicking "Mischen" now clears the current list immediately, ensuring the spinner is shown and the displayed songs always match what "Play All" would send. + +#### Queue Panel +- **Hover highlight lost on right-click**: Queue items now retain their hover highlight while a context menu is open for them (`.context-active` CSS class). +- **Song count and total duration**: The queue header now shows the number of tracks and total runtime below the title (e.g. `12 tracks · 47:32`). + +#### Context Menu +- **"Favorite" option added for queue items**: Right-clicking a queue item now includes a "Favorite" option, consistent with the song context menu. + ## [1.4.2] - 2026-03-16 ### Fixed diff --git a/package.json b/package.json index ba3b43f5..7f0435e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psysonic", - "version": "1.4.2", + "version": "1.4.3", "private": true, "scripts": { "dev": "vite", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3807f910..3b44a066 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Psysonic", - "version": "1.4.2", + "version": "1.4.3", "identifier": "dev.psysonic.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/api/subsonic.ts b/src/api/subsonic.ts index 63aca17e..919525b8 100644 --- a/src/api/subsonic.ts +++ b/src/api/subsonic.ts @@ -163,7 +163,7 @@ export async function getAlbumList( } export async function getRandomSongs(size = 50, genre?: string, timeout = 15000): Promise { - const params: Record = { size }; + const params: Record = { size, _t: Date.now() }; if (genre) params.genre = genre; const data = await api<{ randomSongs: { song: SubsonicSong[] } }>('getRandomSongs.view', params, timeout); return data.randomSongs?.song ?? []; diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index 21f3b59b..888df8d4 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -215,6 +215,9 @@ export default function ContextMenu() { {t('contextMenu.openAlbum')} )} +
handleAction(() => star(song.id, 'song'))}> + {t('contextMenu.favorite')} +
handleAction(() => startRadio(song.artist, song.artist))}> {t('contextMenu.startRadio')}
diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx index 7a271553..702171b3 100644 --- a/src/components/QueuePanel.tsx +++ b/src/components/QueuePanel.tsx @@ -128,6 +128,7 @@ export default function QueuePanel() { const reorderQueue = usePlayerStore(s => s.reorderQueue); const shuffleQueue = usePlayerStore(s => s.shuffleQueue); const enqueue = usePlayerStore(s => s.enqueue); + const contextMenu = usePlayerStore(s => s.contextMenu); const [draggedIdx, setDraggedIdx] = useState(null); const [dragOverIdx, setDragOverIdx] = useState(null); @@ -254,7 +255,23 @@ export default function QueuePanel() { }} >
-

{t('queue.title')}

+
+

{t('queue.title')}

+ {queue.length > 0 && (() => { + const totalSecs = queue.reduce((acc, t) => acc + (t.duration || 0), 0); + const h = Math.floor(totalSecs / 3600); + const m = Math.floor((totalSecs % 3600) / 60); + const s = totalSecs % 60; + const dur = h > 0 + ? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` + : `${m}:${s.toString().padStart(2, '0')}`; + return ( +
+ {queue.length} {queue.length === 1 ? t('queue.trackSingular') : t('queue.trackPlural')} · {dur} +
+ ); + })()} +
- + {(() => { + const isGenreLoading = selectedSuperGenre && !genreMixComplete; + const isDisabled = loading || (selectedSuperGenre ? !genreMixComplete || genreMixSongs.length === 0 : filteredSongs.length === 0); + return ( + + ); + })()}
@@ -302,11 +326,6 @@ export default function RandomMix() { {SUPER_GENRES.find(s => s.id === selectedSuperGenre)?.label} Mix {genreMixLoading &&
} -
- -
{genreMixLoading && genreMixSongs.length === 0 ? (
diff --git a/src/styles/layout.css b/src/styles/layout.css index b1f9451f..0038de09 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -570,7 +570,7 @@ /* Prevent child elements from stealing dragenter/dragleave events */ .queue-item > * { pointer-events: none; } -.queue-item:hover { +.queue-item:hover, .queue-item.context-active { background: var(--bg-hover); color: var(--text-primary); }