Compare commits

..

1 Commits

Author SHA1 Message Date
Psychotoxical 492f069bf1 feat: Nord themes, Wayland compatibility, and stability fixes (v1.0.6) 2026-03-13 18:59:18 +01:00
9 changed files with 30 additions and 164 deletions
-10
View File
@@ -5,16 +5,6 @@ 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.0.7] - 2026-03-13
### Added
- **Update Notifications**: Integrated a native update check system in the sidebar that notifies you when a new version is available on GitHub.
- **Improved Settings**: Refined layout and styling for a cleaner settings experience.
### Fixed
- **UI/UX Refinements**: Polished sidebar animations and layout for better visual consistency.
- **i18n**: Added missing translations for update notifications and system status.
## [1.0.6] - 2026-03-13
### Added
+17 -10
View File
@@ -16,26 +16,26 @@ Psysonic is a beautiful desktop music player built completely from the ground up
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.
![Psysonic Screenshot](public/screenshot.png)
## ✨ Features
- 🎨 **Gorgeous UI**: 8 deeply integrated themes (Catppuccin series + Nord series) with smooth glassmorphism effects and micro-animations.
- 🎨 **Gorgeous UI**: Deeply integrated Catppuccin themes (Mocha & Latte) with smooth glassmorphism effects and micro-animations.
-**Blazing Fast**: Built with Rust & Tauri, resulting in minimal RAM usage compared to typical Electron apps.
- 🌍 **Internationalization (i18n)**: Fully translated into English and German.
- 🌍 **Internationalization (i18n)**: Fully translated into English and German (v1.0.0).
- 📻 **Live "Now Playing"**: See what other users on your server are currently listening to in real-time.
- 🎵 **Last.fm Scrobbling**: Full integration for scrobbling your tracks via the Navidrome server.
- 💾 **IndexedDB Caching**: Ultra-fast loading times with persistent IndexedDB image caching for cover art and artist images.
- 📀 **Album Downloads**: Support for downloading entire albums directly to your local machine.
- 💿 **Album & Artist Views**: Beautiful grid displays and detailed artist pages with related albums and color-coded initial avatars for fast browsing.
- 💾 **Local Caching**: Fast loading times with customizable image caching thresholds.
- 💿 **Album & Artist Views**: Beautiful grid displays and detailed artist pages with related albums.
- 🎛️ **Queue Management**: Drag & drop support, playlist saving, and loading directly built into the queue. Server-side queue synchronization is fully supported.
- 🔄 **Update Notifications**: Built-in update checker that notifies you when a new version is available on GitHub.
- 🖥️ **Cross-Platform**: Available natively for Windows, macOS, and Linux (including Wayland support).
- 🖥️ **Cross-Platform**: Available natively for Windows, macOS, and Linux.
## ● Known Limitations
## 🛡️ Security
- **Linux (drag & drop cursor feedback)**: Due to a WebKitGTK limitation, the drag cursor does not reflect the drop operation type — it may appear as a "forbidden" symbol or show no indicator at all, depending on the desktop environment. Drag and drop itself works correctly.
With the **v1.0.0 Release**, Psysonic has been hardened for public use:
- **Secured Credentials**: Industry-standard security using Tauri's native encrypted store.
- **Hardened Sandbox**: Restricted filesystem permissions to only necessary directories.
- **Native Security**: Disabled global Tauri objects to significantly reduce XSS attack vectors.
## 📥 Installation
@@ -91,3 +91,10 @@ Contributions are completely welcome! Whether it is translating the app into a n
## 📄 License
Distributed under the MIT License. See `LICENSE` for more information.
---
<p align="center">
<i>Core Components developed with <b>Claude Code</b></i><br/>
<i>UI/UX developed with <b>Gemini 2.0 Flash</b> using <b>Antigravity</b></i>
</p>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "psysonic",
"version": "1.0.7",
"version": "1.0.6",
"private": true,
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "psysonic"
version = "1.0.7"
version = "1.0.6"
description = "Psysonic Desktop Music Player"
authors = []
license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Psysonic",
"version": "1.0.7",
"version": "1.0.6",
"identifier": "dev.psysonic.app",
"build": {
"beforeDevCommand": "npm run dev",
+7 -68
View File
@@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
Disc3, Users, Music4, Radio, Settings, Heart, BarChart3, Shuffle, ListMusic,
PanelLeftClose, PanelLeft, HelpCircle, Dices, ArrowUpCircle
PanelLeftClose, PanelLeft, HelpCircle, Dices
} from 'lucide-react';
const PsysonicLogo = () => (
@@ -21,74 +21,14 @@ const navItems = [
{ icon: Heart, labelKey: 'sidebar.favorites', to: '/favorites' },
];
function isNewer(latest: string, current: string): boolean {
const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number);
const [lMaj, lMin, lPat] = parse(latest);
const [cMaj, cMin, cPat] = parse(current);
if (lMaj !== cMaj) return lMaj > cMaj;
if (lMin !== cMin) return lMin > cMin;
return lPat > cPat;
}
function UpdateToast({ isCollapsed, latestVersion }: { isCollapsed: boolean; latestVersion: string }) {
const { t } = useTranslation();
if (isCollapsed) {
return (
<div className="update-toast-icon" style={{ marginTop: 'auto' }} title={`${t('sidebar.updateAvailable')}: ${latestVersion}`}>
<ArrowUpCircle size={20} />
</div>
);
}
return (
<div className="update-toast">
<div className="update-toast-header">
<ArrowUpCircle size={14} />
<span className="update-toast-label">{t('sidebar.updateAvailable')}</span>
</div>
<div className="update-toast-version">{t('sidebar.updateReady', { version: latestVersion })}</div>
<a
className="update-toast-link"
href="https://github.com/Psychotoxical/psysonic/releases"
target="_blank"
rel="noopener noreferrer"
>
{t('sidebar.updateLink')}
</a>
</div>
);
}
export default function Sidebar({
isCollapsed = false,
toggleCollapse
}: {
export default function Sidebar({
isCollapsed = false,
toggleCollapse
}: {
isCollapsed?: boolean;
toggleCollapse?: () => void;
}) {
const { t } = useTranslation();
const [latestVersion, setLatestVersion] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const timer = setTimeout(async () => {
try {
const { getVersion } = await import('@tauri-apps/api/app');
const current = await getVersion();
const res = await fetch('https://api.github.com/repos/Psychotoxical/psysonic/releases/latest');
if (!res.ok) return;
const data = await res.json();
const tag: string = data.tag_name ?? '';
if (!cancelled && tag && isNewer(tag, current)) {
setLatestVersion(tag.startsWith('v') ? tag : `v${tag}`);
}
} catch {
// network unavailable or not running in Tauri — silently skip
}
}, 1500);
return () => { cancelled = true; clearTimeout(timer); };
}, []);
return (
<aside className={`sidebar animate-slide-in ${isCollapsed ? 'collapsed' : ''}`}>
@@ -121,11 +61,10 @@ export default function Sidebar({
))}
{!isCollapsed && <span className="nav-section-label" style={{ marginTop: 'auto' }}>{t('sidebar.system')}</span>}
{latestVersion && <UpdateToast isCollapsed={isCollapsed} latestVersion={latestVersion} />}
<NavLink
to="/statistics"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
style={isCollapsed && !latestVersion ? { marginTop: 'auto' } : undefined}
style={isCollapsed ? { marginTop: 'auto' } : undefined}
title={isCollapsed ? t('sidebar.statistics') : undefined}
>
<BarChart3 size={isCollapsed ? 22 : 18} />
+2 -8
View File
@@ -18,10 +18,7 @@ const enTranslation = {
settings: 'Settings',
help: 'Help',
expand: 'Expand Sidebar',
collapse: 'Collapse Sidebar',
updateAvailable: 'Update available',
updateReady: '{{version}} is ready',
updateLink: 'Go to release →'
collapse: 'Collapse Sidebar'
},
home: {
starred: 'Personal Favorites',
@@ -375,10 +372,7 @@ const deTranslation = {
settings: 'Einstellungen',
help: 'Hilfe',
expand: 'Sidebar einblenden',
collapse: 'Sidebar ausblenden',
updateAvailable: 'Update verfügbar',
updateReady: '{{version}} ist bereit',
updateLink: 'Zum Release →'
collapse: 'Sidebar ausblenden'
},
home: {
starred: 'Persönliche Favoriten',
+1 -1
View File
@@ -374,7 +374,7 @@ export default function Settings() {
Psysonic
</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
{t('settings.aboutVersion')} 1.0.7
{t('settings.aboutVersion')} 1.0.6
</div>
</div>
</div>
-64
View File
@@ -164,70 +164,6 @@
color: var(--text-primary);
}
/* ─── Update Toast ─── */
@keyframes update-toast-in {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.update-toast {
margin: 0 var(--space-1) var(--space-2);
padding: var(--space-3) var(--space-3);
border-radius: var(--radius-md);
background: var(--accent-dim);
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
display: flex;
flex-direction: column;
gap: 3px;
animation: update-toast-in 0.35s ease both;
}
.update-toast-header {
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--accent);
}
.update-toast-label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--accent);
}
.update-toast-version {
font-size: 12px;
color: var(--text-secondary);
padding-left: 22px;
}
.update-toast-link {
font-size: 11px;
font-weight: 600;
color: var(--accent);
text-decoration: none;
padding-left: 22px;
opacity: 0.8;
transition: opacity var(--transition-fast);
}
.update-toast-link:hover {
opacity: 1;
text-decoration: underline;
}
.update-toast-icon {
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-2) 0;
color: var(--accent);
animation: update-toast-in 0.35s ease both;
cursor: default;
}
/* ─── Main Content ─── */
.main-content {
grid-area: main;