mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fix(settings): align AutoDJ and Hi-Res sub-options with the Normalization design (#1175)
* fix(settings): box the AutoDJ and Hi-Res sub-options like Normalization The AutoDJ overlap-cap and the Hi-Res blend-rate options sat bare in their sections instead of in the bordered sub-card the Normalization block uses for its per-mode controls. Wrap both (and the crossfade-seconds slider, for consistency within Track transitions) in the shared settings-norm-block card, and drop the redundant SettingsGroup wrapper from the Hi-Res block — the Audio tab already wraps that section in a group, so the block added a second border. * docs(changelog): audio sub-section design fix; move recent entries to the end of Fixed The #1172 and #1174 entries were prepended to the Fixed section instead of appended; move them to the end where new entries belong, and add the #1175 audio settings entry.
This commit is contained in:
+19
-13
@@ -127,19 +127,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
### Context menu "Play Now" and resize behaviour
|
|
||||||
|
|
||||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1174](https://github.com/Psychotoxical/psysonic/pull/1174)**, reported by [@peri4ko](https://github.com/peri4ko)
|
|
||||||
|
|
||||||
* On the Playlists page, right-clicking a playlist and choosing "Play Now" only opened the playlist instead of playing it. It now starts playback.
|
|
||||||
* Resizing the window while a context menu was open could leave the menu stranded and drifting off-screen. The context menu now closes when the window is resized.
|
|
||||||
|
|
||||||
### Artist header showing the plain image instead of the external background
|
|
||||||
|
|
||||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1172](https://github.com/Psychotoxical/psysonic/pull/1172)**
|
|
||||||
|
|
||||||
* On the artist page, when an artist had an external background image (from fanart.tv) but no banner, the header showed the plain Navidrome artist image instead of the background — even though the fullscreen player used the background correctly. The header now falls back banner → background → Navidrome image as intended. The background also sits a little higher so band members' heads aren't cropped on wide screens.
|
|
||||||
|
|
||||||
### Playlists header buttons clipped at narrow widths
|
### Playlists header buttons clipped at narrow widths
|
||||||
|
|
||||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1153](https://github.com/Psychotoxical/psysonic/pull/1153)**
|
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1153](https://github.com/Psychotoxical/psysonic/pull/1153)**
|
||||||
@@ -288,6 +275,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
* Strict `npm run lint` runs in CI on frontend path filters via a dedicated workflow parallel to the existing frontend test jobs.
|
* Strict `npm run lint` runs in CI on frontend path filters via a dedicated workflow parallel to the existing frontend test jobs.
|
||||||
* The `ci-ok` check waits for every applicable test and lint job on a PR (frontend and/or Rust, depending on changed paths) and blocks merge when any required job failed or did not finish in time.
|
* The `ci-ok` check waits for every applicable test and lint job on a PR (frontend and/or Rust, depending on changed paths) and blocks merge when any required job failed or did not finish in time.
|
||||||
|
|
||||||
|
### Artist header showing the plain image instead of the external background
|
||||||
|
|
||||||
|
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1172](https://github.com/Psychotoxical/psysonic/pull/1172)**
|
||||||
|
|
||||||
|
* On the artist page, when an artist had an external background image (from fanart.tv) but no banner, the header showed the plain Navidrome artist image instead of the background — even though the fullscreen player used the background correctly. The header now falls back banner → background → Navidrome image as intended. The background also sits a little higher so band members' heads aren't cropped on wide screens.
|
||||||
|
|
||||||
|
### Context menu "Play Now" and resize behaviour
|
||||||
|
|
||||||
|
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1174](https://github.com/Psychotoxical/psysonic/pull/1174)**, reported by [@peri4ko](https://github.com/peri4ko)
|
||||||
|
|
||||||
|
* On the Playlists page, right-clicking a playlist and choosing "Play Now" only opened the playlist instead of playing it. It now starts playback.
|
||||||
|
* Resizing the window while a context menu was open could leave the menu stranded and drifting off-screen. The context menu now closes when the window is resized.
|
||||||
|
|
||||||
|
### Settings — consistent design for the Audio sub-sections
|
||||||
|
|
||||||
|
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1175](https://github.com/Psychotoxical/psysonic/pull/1175)**
|
||||||
|
|
||||||
|
* The AutoDJ overlap-cap and the Native Hi-Res blend-rate options in Settings → Audio now sit in the same bordered sub-card the Normalization options use, and the Hi-Res section no longer shows a double border.
|
||||||
|
|
||||||
## [1.48.1] - 2026-06-15
|
## [1.48.1] - 2026-06-15
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
sanitizeHiResCrossfadeResampleHz,
|
sanitizeHiResCrossfadeResampleHz,
|
||||||
} from '../../../utils/audio/hiResCrossfadeResample';
|
} from '../../../utils/audio/hiResCrossfadeResample';
|
||||||
import type { TFunction } from 'i18next';
|
import type { TFunction } from 'i18next';
|
||||||
import { SettingsGroup } from '../SettingsGroup';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -29,28 +28,28 @@ export function HiResCrossfadeResampleBlock({
|
|||||||
if (!enabled) return null;
|
if (!enabled) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsGroup>
|
<div className="settings-norm-block" style={{ marginTop: '0.85rem' }}>
|
||||||
<p className="settings-row-label" style={{ marginBottom: '0.5rem' }}>
|
<div className="settings-norm-field">
|
||||||
{t('settings.hiResCrossfadeResampleTitle')}
|
<span className="settings-norm-label" style={{ minWidth: 0 }}>
|
||||||
</p>
|
{t('settings.hiResCrossfadeResampleTitle')}
|
||||||
<p className="settings-row-desc" style={{ marginBottom: '0.75rem' }}>
|
</span>
|
||||||
{t('settings.hiResCrossfadeResampleDesc')}
|
<div className="settings-norm-help">{t('settings.hiResCrossfadeResampleDesc')}</div>
|
||||||
</p>
|
<div className="settings-segmented">
|
||||||
<div className="settings-segmented" style={{ marginBottom: '0.75rem' }}>
|
{HI_RES_CROSSFADE_RESAMPLE_OPTIONS.map((hz) => (
|
||||||
{HI_RES_CROSSFADE_RESAMPLE_OPTIONS.map((hz) => (
|
<button
|
||||||
<button
|
key={hz}
|
||||||
key={hz}
|
type="button"
|
||||||
type="button"
|
className={`btn ${resampleHz === hz ? 'btn-primary' : 'btn-ghost'}`}
|
||||||
className={`btn ${resampleHz === hz ? 'btn-primary' : 'btn-ghost'}`}
|
onClick={() => onResampleHzChange(sanitizeHiResCrossfadeResampleHz(hz))}
|
||||||
onClick={() => onResampleHzChange(sanitizeHiResCrossfadeResampleHz(hz))}
|
>
|
||||||
>
|
{labelForHz(t, hz)}
|
||||||
{labelForHz(t, hz)}
|
</button>
|
||||||
</button>
|
))}
|
||||||
))}
|
</div>
|
||||||
|
<div className="settings-norm-help" role="note">
|
||||||
|
{t('settings.hiResCrossfadeResampleWarning')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="settings-row-desc" role="note" style={{ marginBottom: 0, opacity: 0.85 }}>
|
</div>
|
||||||
{t('settings.hiResCrossfadeResampleWarning')}
|
|
||||||
</p>
|
|
||||||
</SettingsGroup>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,36 +70,33 @@ export function TrackTransitionsBlock({ t }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{mode === 'crossfade' && (
|
{mode === 'crossfade' && (
|
||||||
<div style={{ paddingLeft: '1rem', marginTop: '0.7rem', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
<div className="settings-norm-block" style={{ marginTop: '0.85rem' }}>
|
||||||
<input
|
<div className="settings-norm-row">
|
||||||
type="range"
|
<input
|
||||||
min={0.1}
|
type="range"
|
||||||
max={10}
|
min={0.1}
|
||||||
step={0.1}
|
max={10}
|
||||||
value={auth.crossfadeSecs}
|
step={0.1}
|
||||||
disabled={hostControlled}
|
value={auth.crossfadeSecs}
|
||||||
onChange={e => auth.setCrossfadeSecs(parseFloat(e.target.value))}
|
disabled={hostControlled}
|
||||||
style={{ flex: 1, minWidth: 80, maxWidth: 200 }}
|
onChange={e => auth.setCrossfadeSecs(parseFloat(e.target.value))}
|
||||||
id="crossfade-secs-slider"
|
id="crossfade-secs-slider"
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 13, color: 'var(--text-secondary)', minWidth: 36 }}>
|
<span className="settings-norm-value">
|
||||||
{t('settings.crossfadeSecs', { n: auth.crossfadeSecs.toFixed(1) })}
|
{t('settings.crossfadeSecs', { n: auth.crossfadeSecs.toFixed(1) })}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mode === 'autodj' && (
|
{mode === 'autodj' && (
|
||||||
<>
|
<div className="settings-norm-block" style={{ marginTop: '0.85rem' }}>
|
||||||
<div style={{ paddingLeft: '1rem', fontSize: 12, color: 'var(--text-muted)', marginTop: '0.7rem' }}>
|
<div className="settings-norm-help">{t('settings.autoDjDesc')}</div>
|
||||||
{t('settings.autoDjDesc')}
|
<div className="settings-norm-field">
|
||||||
</div>
|
<span className="settings-norm-label" style={{ minWidth: 0 }}>
|
||||||
<div style={{ paddingLeft: '1rem', marginTop: '0.9rem' }}>
|
|
||||||
<p className="settings-row-label" style={{ marginBottom: '0.45rem' }}>
|
|
||||||
{t('settings.autodjOverlapCapTitle')}
|
{t('settings.autodjOverlapCapTitle')}
|
||||||
</p>
|
</span>
|
||||||
<p className="settings-row-desc" style={{ marginBottom: '0.6rem' }}>
|
<div className="settings-norm-help">{t('settings.autodjOverlapCapDesc')}</div>
|
||||||
{t('settings.autodjOverlapCapDesc')}
|
<div className="settings-segmented">
|
||||||
</p>
|
|
||||||
<div className="settings-segmented" style={{ marginBottom: auth.autodjOverlapCapMode === 'limit' ? '0.65rem' : 0 }}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn ${auth.autodjOverlapCapMode === 'auto' ? 'btn-primary' : 'btn-ghost'}`}
|
className={`btn ${auth.autodjOverlapCapMode === 'auto' ? 'btn-primary' : 'btn-ghost'}`}
|
||||||
@@ -118,7 +115,7 @@ export function TrackTransitionsBlock({ t }: Props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{auth.autodjOverlapCapMode === 'limit' && (
|
{auth.autodjOverlapCapMode === 'limit' && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
<div className="settings-norm-row">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={AUTODJ_OVERLAP_CAP_MIN_SEC}
|
min={AUTODJ_OVERLAP_CAP_MIN_SEC}
|
||||||
@@ -127,25 +124,22 @@ export function TrackTransitionsBlock({ t }: Props) {
|
|||||||
value={auth.autodjOverlapCapSec}
|
value={auth.autodjOverlapCapSec}
|
||||||
disabled={hostControlled}
|
disabled={hostControlled}
|
||||||
onChange={e => auth.setAutodjOverlapCapSec(parseInt(e.target.value, 10))}
|
onChange={e => auth.setAutodjOverlapCapSec(parseInt(e.target.value, 10))}
|
||||||
style={{ flex: 1, minWidth: 80, maxWidth: 200 }}
|
|
||||||
id="autodj-overlap-cap-slider"
|
id="autodj-overlap-cap-slider"
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 13, color: 'var(--text-secondary)', minWidth: 48 }}>
|
<span className="settings-norm-value">
|
||||||
{t('settings.autodjOverlapCapSecs', { n: auth.autodjOverlapCapSec })}
|
{t('settings.autodjOverlapCapSecs', { n: auth.autodjOverlapCapSec })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ paddingLeft: '1rem', marginTop: '0.7rem' }}>
|
<SettingsToggle
|
||||||
<SettingsToggle
|
label={t('settings.autodjSmoothSkip')}
|
||||||
label={t('settings.autodjSmoothSkip')}
|
desc={t('settings.autodjSmoothSkipDesc')}
|
||||||
desc={t('settings.autodjSmoothSkipDesc')}
|
checked={auth.autodjSmoothSkip}
|
||||||
checked={auth.autodjSmoothSkip}
|
disabled={hostControlled}
|
||||||
disabled={hostControlled}
|
onChange={auth.setAutodjSmoothSkip}
|
||||||
onChange={auth.setAutodjSmoothSkip}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</SettingsGroup>
|
</SettingsGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user