feat: Update Notification system and UI refinements (v1.0.7)

This commit is contained in:
Psychotoxical
2026-03-13 19:31:44 +01:00
parent 85823ff4c4
commit 5528123193
8 changed files with 154 additions and 13 deletions
+10
View File
@@ -5,6 +5,16 @@ 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/), 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). 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 ## [1.0.6] - 2026-03-13
### Added ### Added
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "psysonic", "name": "psysonic",
"version": "1.0.6", "version": "1.0.7",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "psysonic" name = "psysonic"
version = "1.0.6" version = "1.0.7"
description = "Psysonic Desktop Music Player" description = "Psysonic Desktop Music Player"
authors = [] authors = []
license = "" license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Psysonic", "productName": "Psysonic",
"version": "1.0.6", "version": "1.0.7",
"identifier": "dev.psysonic.app", "identifier": "dev.psysonic.app",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",
+68 -7
View File
@@ -1,9 +1,9 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Disc3, Users, Music4, Radio, Settings, Heart, BarChart3, Shuffle, ListMusic, Disc3, Users, Music4, Radio, Settings, Heart, BarChart3, Shuffle, ListMusic,
PanelLeftClose, PanelLeft, HelpCircle, Dices PanelLeftClose, PanelLeft, HelpCircle, Dices, ArrowUpCircle
} from 'lucide-react'; } from 'lucide-react';
const PsysonicLogo = () => ( const PsysonicLogo = () => (
@@ -21,14 +21,74 @@ const navItems = [
{ icon: Heart, labelKey: 'sidebar.favorites', to: '/favorites' }, { icon: Heart, labelKey: 'sidebar.favorites', to: '/favorites' },
]; ];
export default function Sidebar({ function isNewer(latest: string, current: string): boolean {
isCollapsed = false, const parse = (v: string) => v.replace(/^v/, '').split('.').map(Number);
toggleCollapse 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
}: {
isCollapsed?: boolean; isCollapsed?: boolean;
toggleCollapse?: () => void; toggleCollapse?: () => void;
}) { }) {
const { t } = useTranslation(); 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 ( return (
<aside className={`sidebar animate-slide-in ${isCollapsed ? 'collapsed' : ''}`}> <aside className={`sidebar animate-slide-in ${isCollapsed ? 'collapsed' : ''}`}>
@@ -61,10 +121,11 @@ export default function Sidebar({
))} ))}
{!isCollapsed && <span className="nav-section-label" style={{ marginTop: 'auto' }}>{t('sidebar.system')}</span>} {!isCollapsed && <span className="nav-section-label" style={{ marginTop: 'auto' }}>{t('sidebar.system')}</span>}
{latestVersion && <UpdateToast isCollapsed={isCollapsed} latestVersion={latestVersion} />}
<NavLink <NavLink
to="/statistics" to="/statistics"
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
style={isCollapsed ? { marginTop: 'auto' } : undefined} style={isCollapsed && !latestVersion ? { marginTop: 'auto' } : undefined}
title={isCollapsed ? t('sidebar.statistics') : undefined} title={isCollapsed ? t('sidebar.statistics') : undefined}
> >
<BarChart3 size={isCollapsed ? 22 : 18} /> <BarChart3 size={isCollapsed ? 22 : 18} />
+8 -2
View File
@@ -18,7 +18,10 @@ const enTranslation = {
settings: 'Settings', settings: 'Settings',
help: 'Help', help: 'Help',
expand: 'Expand Sidebar', expand: 'Expand Sidebar',
collapse: 'Collapse Sidebar' collapse: 'Collapse Sidebar',
updateAvailable: 'Update available',
updateReady: '{{version}} is ready',
updateLink: 'Go to release →'
}, },
home: { home: {
starred: 'Personal Favorites', starred: 'Personal Favorites',
@@ -372,7 +375,10 @@ const deTranslation = {
settings: 'Einstellungen', settings: 'Einstellungen',
help: 'Hilfe', help: 'Hilfe',
expand: 'Sidebar einblenden', expand: 'Sidebar einblenden',
collapse: 'Sidebar ausblenden' collapse: 'Sidebar ausblenden',
updateAvailable: 'Update verfügbar',
updateReady: '{{version}} ist bereit',
updateLink: 'Zum Release →'
}, },
home: { home: {
starred: 'Persönliche Favoriten', starred: 'Persönliche Favoriten',
+1 -1
View File
@@ -374,7 +374,7 @@ export default function Settings() {
Psysonic Psysonic
</div> </div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}> <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
{t('settings.aboutVersion')} 1.0.6 {t('settings.aboutVersion')} 1.0.7
</div> </div>
</div> </div>
</div> </div>
+64
View File
@@ -164,6 +164,70 @@
color: var(--text-primary); 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 ─── */
.main-content { .main-content {
grid-area: main; grid-area: main;