From 18199a1f8a61cc1c5816c3b35451bd6831be09c3 Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Tue, 17 Mar 2026 21:19:07 +0100 Subject: [PATCH] feat: queue improvements, system browser links (v1.4.5) - Queue: year added to now-playing meta, cover enlarged to 90px + top-aligned, default width 340px - Artist pages: Last.fm/Wikipedia open in system browser with button label feedback - README: AUR badge + install section, Nord theme mention, in-app browser bullet removed Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 12 ++++++++++++ CLAUDE.md | 2 +- README.md | 10 +++++++--- package.json | 2 +- packages/aur/PKGBUILD | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/lib.rs | 9 ++++++--- src-tauri/tauri.conf.json | 2 +- src/App.tsx | 2 +- src/components/QueuePanel.tsx | 4 +++- src/i18n.ts | 2 ++ src/pages/ArtistDetail.tsx | 26 +++++++++++--------------- src/styles/layout.css | 6 +++--- 14 files changed, 51 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd10502b..45d26c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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.5] - 2026-03-17 + +### Changed + +#### Artist Pages — External Links +- Last.fm and Wikipedia buttons now open in the **system browser** instead of an in-app window. The button label temporarily changes to "Opened in browser" / "Im Browser geöffnet" for 2.5 seconds as visual confirmation. + +#### Queue Panel +- **Release year** added to the now-playing meta box, shown below the album name (when available). +- **Cover art enlarged** from 72 × 72 px to 90 × 90 px, aligned to the top of the meta block so it lines up with the song title. +- **Default width increased** from 300 px to 340 px. + ## [1.4.4] - 2026-03-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 23c86063..1196581e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -181,4 +181,4 @@ The workflow is split into three jobs: `create-release` (creates the GitHub Rele - **RandomAlbums**: No auto-refresh timer — loads once on mount, manual refresh button only. `loadingRef` guards against concurrent fetches. - **Queue shuffle**: `shuffleQueue()` in playerStore keeps current track at index 0, Fisher-Yates shuffles the rest. - **Tooltip z-index**: `.main-content` has `z-index: 1` so tooltips in the content area render above the queue panel (which has no z-index but appears later in DOM order). Multi-line tooltips: add `data-tooltip-wrap` attribute + use `\n` in the string; CSS rule `[data-tooltip-wrap]::after { white-space: pre-line; max-width: 220px }`. -- **Version**: 1.4.4 +- **Version**: 1.4.5 diff --git a/README.md b/README.md index 67524c08..c25cb536 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ Latest Release License: GPL v3 Built with Tauri + AUR

--- -Psysonic is a beautiful desktop music player built completely from the ground up for the modern era. Utilizing **Tauri v2** and **React**, it offers a native-feeling, lightweight, and incredibly fast experience with a stunning UI inspired by the [Catppuccin](https://github.com/catppuccin/catppuccin) aesthetic. +Psysonic is a beautiful desktop music player built completely from the ground up for the modern era. Utilizing **Tauri v2** and **React**, it offers a native-feeling, lightweight, and incredibly fast experience with a stunning UI inspired by the [Catppuccin](https://github.com/catppuccin/catppuccin) and [Nord](https://www.nordtheme.com/) aesthetics. Designed specifically for users hosting their own music via Navidrome or other Subsonic API servers, Psysonic aims to be the best way to interact with your personal library. @@ -33,7 +34,6 @@ Designed specifically for users hosting their own music via Navidrome or other S - 🌊 **MilkDrop Visualizer**: Full-screen Butterchurn/MilkDrop visualizer in the Ambient Stage with hundreds of presets and smooth transitions. - 🎛️ **Queue Management**: Drag & drop reordering, shuffle, playlist saving/loading, and server-side queue synchronization. - 🎼 **Random Mix**: Generate a random playlist from your entire library. Filter by keyword or pick a Super Genre (Metal, Rock, Electronic, Jazz…) for a focused mix with progressive loading. -- 🔗 **In-App Browser**: Last.fm and Wikipedia links on artist pages open in a dedicated native window — no need to leave the app. - 🔄 **Update Notifications**: Built-in update checker (on startup + every 10 minutes) that notifies you when a new version is available on GitHub. - 🖥️ **Cross-Platform**: Available natively for Windows, macOS, and Linux (including Wayland support). @@ -47,7 +47,11 @@ Navigate to the [Releases](https://github.com/Psychotoxical/psysonic/releases) p - **Windows**: `.exe` or `.msi` - **macOS**: `.dmg` (Universal or Apple Silicon) -- **Linux**: `.AppImage` or `.deb` +- **Linux (Ubuntu/Debian)**: `.deb` from GitHub Releases +- **Linux (Fedora/RHEL)**: `.rpm` from GitHub Releases +- **Linux (Arch/CachyOS)**: AUR — `yay -S psysonic` or `paru -S psysonic` + +> The AUR package builds from source using your system's own WebKitGTK — no bundled libs, no EGL/Mesa compatibility issues. ## 🚀 Getting Started diff --git a/package.json b/package.json index d4c4964e..cecf719c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psysonic", - "version": "1.4.4", + "version": "1.4.5", "private": true, "scripts": { "dev": "vite", diff --git a/packages/aur/PKGBUILD b/packages/aur/PKGBUILD index 2e55d027..24e80d35 100644 --- a/packages/aur/PKGBUILD +++ b/packages/aur/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: stelle pkgname=psysonic -pkgver=1.4.3 +pkgver=1.4.5 pkgrel=1 pkgdesc="Desktop music player for Subsonic API-compatible servers (Navidrome, Gonic, etc.)" arch=('x86_64') diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9af2ef94..adad6d9a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3118,7 +3118,7 @@ dependencies = [ [[package]] name = "psysonic" -version = "1.4.2" +version = "1.4.5" dependencies = [ "reqwest 0.12.28", "rodio", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 131b1e77..b70b908a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psysonic" -version = "1.4.4" +version = "1.4.5" description = "Psysonic Desktop Music Player" authors = [] license = "" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f2ef8230..f455c1bb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19,6 +19,7 @@ fn exit_app(app_handle: tauri::AppHandle) { app_handle.exit(0); } + pub fn run() { let (audio_engine, _audio_thread) = audio::create_engine(); @@ -107,9 +108,11 @@ pub fn run() { }) .on_window_event(|window, event| { if let tauri::WindowEvent::CloseRequested { .. } = event { - // Emit event so JS can decide: hide to tray or allow close. - // JS handles prevent_close via onCloseRequested() in App.tsx. - let _ = window.emit("window:close-requested", ()); + // Only intercept close for the main window (hide to tray). + // Browser popup windows (browser_*) close normally. + if window.label() == "main" { + let _ = window.emit("window:close-requested", ()); + } } }) .invoke_handler(tauri::generate_handler![ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c1689e2a..21b3f47e 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.4", + "version": "1.4.5", "identifier": "dev.psysonic.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/App.tsx b/src/App.tsx index 47dd7797..7d17d36b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -73,7 +73,7 @@ function AppShell() { const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => { return localStorage.getItem('psysonic_sidebar_collapsed') === 'true'; }); - const [queueWidth, setQueueWidth] = useState(300); + const [queueWidth, setQueueWidth] = useState(340); const [isDraggingQueue, setIsDraggingQueue] = useState(false); useEffect(() => { diff --git a/src/components/QueuePanel.tsx b/src/components/QueuePanel.tsx index 702171b3..429488a9 100644 --- a/src/components/QueuePanel.tsx +++ b/src/components/QueuePanel.tsx @@ -316,7 +316,9 @@ export default function QueuePanel() { style={{ cursor: currentTrack.albumId ? 'pointer' : 'default' }} onClick={() => currentTrack.albumId && navigate(`/album/${currentTrack.albumId}`)} >{currentTrack.album} - + {currentTrack.year && ( +
{currentTrack.year}
+ )}
{currentTrack.bitRate && currentTrack.suffix ? ( diff --git a/src/i18n.ts b/src/i18n.ts index 84d121c1..f981d083 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -120,6 +120,7 @@ const enTranslation = { favorite: 'Favorite', albumCount_one: '{{count}} Album', albumCount_other: '{{count}} Albums', + openedInBrowser: 'Opened in browser', }, favorites: { title: 'Favorites', @@ -539,6 +540,7 @@ const deTranslation = { favorite: 'Als Favorit', albumCount_one: '{{count}} Album', albumCount_other: '{{count}} Alben', + openedInBrowser: 'Im Browser geöffnet', }, favorites: { title: 'Favoriten', diff --git a/src/pages/ArtistDetail.tsx b/src/pages/ArtistDetail.tsx index ac686d20..d609b360 100644 --- a/src/pages/ArtistDetail.tsx +++ b/src/pages/ArtistDetail.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { getArtist, getArtistInfo, getTopSongs, getSimilarSongs2, SubsonicArtist, SubsonicAlbum, SubsonicSong, SubsonicArtistInfo, buildCoverArtUrl, coverArtCacheKey, star, unstar } from '../api/subsonic'; import AlbumCard from '../components/AlbumCard'; import CachedImage from '../components/CachedImage'; import { ArrowLeft, Users, ExternalLink, Star, Play, Shuffle, Radio } from 'lucide-react'; -import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; +import { open } from '@tauri-apps/plugin-shell'; import { usePlayerStore } from '../store/playerStore'; import { useTranslation } from 'react-i18next'; @@ -50,6 +50,7 @@ export default function ArtistDetail() { const [loading, setLoading] = useState(true); const [radioLoading, setRadioLoading] = useState(false); const [isStarred, setIsStarred] = useState(false); + const [openedLink, setOpenedLink] = useState(null); const playTrack = usePlayerStore(state => state.playTrack); const enqueue = usePlayerStore(state => state.enqueue); @@ -77,15 +78,10 @@ export default function ArtistDetail() { }); }, [id]); - const openLink = (url: string, title: string) => { - const label = `browser_${Date.now()}`; - new WebviewWindow(label, { - url, - title, - width: 1100, - height: 780, - center: true, - }); + const openLink = (url: string, key: string) => { + open(url); + setOpenedLink(key); + setTimeout(() => setOpenedLink(null), 2500); }; const toggleStar = async () => { @@ -192,14 +188,14 @@ export default function ArtistDetail() { {(info?.lastFmUrl || artist.name) && (
{info?.lastFmUrl && ( - )} -
)} diff --git a/src/styles/layout.css b/src/styles/layout.css index 0038de09..933f8fae 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -478,15 +478,15 @@ padding: var(--space-3) var(--space-4); display: flex; flex-direction: row; - align-items: center; + align-items: flex-start; gap: var(--space-3); flex-shrink: 0; border-bottom: 1px solid var(--border-subtle); } .queue-current-cover { - width: 72px; - height: 72px; + width: 90px; + height: 90px; flex-shrink: 0; border-radius: var(--radius-md); overflow: hidden;