mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(settings): remove redundant Animations 3-state setting (#495)
* refactor(settings): remove redundant Animations 3-state setting under Seekbar Style The `animationMode` setting (Full / Reduced / Static) duplicated work the perf-flag system and OS-level reduced-motion preference already covered: - `perfFlags.disableMarqueeScroll` already kills marquee scrolling on demand, replacing what `static` mode used to gate. - The `data-perf-disable-animations` html-level switch already strips every `*` animation, replacing what `static` mode used to do globally. - `@media (prefers-reduced-motion: reduce)` honours the OS setting for every user that asked for it via system preferences. - The 30 fps cap that `reduced` mode applied to the seekbar wave was better served by per-feature perf toggles cucadmuh added later. Removed: - `AnimationMode` type, `animationMode` field + setter from auth store. - Settings UI block (3 buttons + hint text) under Appearance > Seekbar Style. - `animationMode === 'static'` short-circuit in WaveformSeek's rAF effect; `isReduced` skip-every-other-frame logic; `static`-checks in `drawNow` / `needsDirectDraw`. - `animationMode !== 'static'` guard and `data-anim-mode` attribute in MarqueeText. - `[data-anim-mode="static"]` and `[data-anim-mode="reduced"]` rules in layout.css. - Seven i18n keys (animationMode + 6 variants) across all eight locales. Migration: the persist layer strips `animationMode` (and the legacy `reducedAnimations` boolean predecessor) so anyone who had `'reduced'` or `'static'` selected silently lands on the former `'full'` path on first launch after upgrade. No user-facing prompt — the missing setting just stops existing. cucadmuh's PR #472 (FPS overlay), #476 (preview-freeze main seekbar, sleep-recovery hooks, card-hover removal) and #486 (interpolation anchor reset on resume) are all preserved untouched — they live in separate effects / files and were not driven by `animationMode`. * docs(changelog): add Removed section for animationMode setting (PR #495) * docs(changelog): refine animationMode removal rationale (drop prefers-reduced-motion overstatement)
This commit is contained in:
committed by
GitHub
parent
ba73649360
commit
ddb1f29af9
@@ -149,6 +149,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
* Added eight new dark themes covering the colour families people most commonly ask for: **Obsidian Black**, **Carbon Grey**, **Volcanic Dark**, **Forest Green**, **Violet Haze**, **Copper Oxide**, **Sakura Night**, **Obsidian Gold**.
|
||||
* Light polish on the existing **AMOLED Black Pure** surface variables so card surfaces no longer collapse onto a pure-black background that read as a single flat slab.
|
||||
|
||||
## Removed
|
||||
|
||||
### Settings — Animations 3-state setting under Seekbar Style
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#495](https://github.com/Psychotoxical/psysonic/pull/495)**
|
||||
|
||||
* The **Animations** 3-state setting (Full / Reduced / Static) under **Settings > Appearance > Seekbar Style** is gone. The newer perf-flag system and per-feature performance work cover the expensive animation paths more directly: marquee scrolling can be disabled via `perfFlags.disableMarqueeScroll` (Sidebar toggle), global CSS animations via the html-level `data-perf-disable-animations` switch, and seekbar performance is handled by the newer per-feature toggles.
|
||||
* Anyone who had `'reduced'` or `'static'` selected silently lands on the normal animation path on first launch after upgrade — the persist layer strips the obsolete field, no user-facing prompt.
|
||||
|
||||
## Fixed
|
||||
|
||||
### Hot cache, HTTP streaming replay, and queue source indicator
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePerfProbeFlags } from '../utils/perfFlags';
|
||||
|
||||
interface Props {
|
||||
@@ -13,7 +12,6 @@ export default function MarqueeText({ text, className, style, onClick }: Props)
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
const [scrollAmount, setScrollAmount] = useState(0);
|
||||
const animationMode = useAuthStore(s => s.animationMode);
|
||||
const perfFlags = usePerfProbeFlags();
|
||||
|
||||
const measure = useCallback(() => {
|
||||
@@ -34,9 +32,7 @@ export default function MarqueeText({ text, className, style, onClick }: Props)
|
||||
return () => ro.disconnect();
|
||||
}, [text, measure]);
|
||||
|
||||
// In `static` animation mode the marquee never scrolls — overflowing text
|
||||
// is truncated with an ellipsis (handled by CSS via data-anim-mode).
|
||||
const shouldScroll = scrollAmount > 0 && animationMode !== 'static' && !perfFlags.disableMarqueeScroll;
|
||||
const shouldScroll = scrollAmount > 0 && !perfFlags.disableMarqueeScroll;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -44,7 +40,6 @@ export default function MarqueeText({ text, className, style, onClick }: Props)
|
||||
className={`marquee-wrap${className ? ` ${className}` : ''}`}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
data-anim-mode={animationMode}
|
||||
>
|
||||
<span
|
||||
ref={textRef}
|
||||
|
||||
@@ -967,14 +967,11 @@ export default function WaveformSeek({ trackId }: Props) {
|
||||
const waveformBins = usePlayerStore(s => s.waveformBins);
|
||||
const duration = usePlayerStore(s => s.currentTrack?.duration ?? 0);
|
||||
const seekbarStyle = useAuthStore(s => s.seekbarStyle);
|
||||
const animationMode = useAuthStore(s => s.animationMode);
|
||||
|
||||
// Ref so the subscription callback (closed over at mount) can read the
|
||||
// current style without stale-closure issues.
|
||||
const styleRef = useRef(seekbarStyle);
|
||||
styleRef.current = seekbarStyle;
|
||||
const animationModeRef = useRef(animationMode);
|
||||
animationModeRef.current = animationMode;
|
||||
|
||||
useEffect(() => {
|
||||
if (!trackId) {
|
||||
@@ -1096,10 +1093,8 @@ export default function WaveformSeek({ trackId }: Props) {
|
||||
visualProgressRef.current = visualTargetProgressRef.current;
|
||||
}
|
||||
// Static styles always redraw on progress; animated styles let the rAF
|
||||
// loop drive paints. In `static` animation mode we skip the rAF loop
|
||||
// entirely, so animated styles also need to repaint here on every tick.
|
||||
const drawNow =
|
||||
!ANIMATED_STYLES.has(styleRef.current) || animationModeRef.current === 'static';
|
||||
// loop drive paints.
|
||||
const drawNow = !ANIMATED_STYLES.has(styleRef.current);
|
||||
if (drawNow) {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
@@ -1137,28 +1132,14 @@ export default function WaveformSeek({ trackId }: Props) {
|
||||
duration,
|
||||
]);
|
||||
|
||||
// rAF loop — animated styles only, and only in `full`/`reduced` mode.
|
||||
// In `static` mode the loop is skipped entirely; the imperative progress
|
||||
// subscription (~2 Hz audio heartbeat) handles repaints, so the seekbar
|
||||
// updates a couple of times per second with no wave animation.
|
||||
// rAF loop — animated styles only.
|
||||
useEffect(() => {
|
||||
if (!ANIMATED_STYLES.has(seekbarStyle)) return;
|
||||
if (animationMode === 'static') {
|
||||
// Repaint once on entry so the canvas reflects current progress
|
||||
// without any wave morph and stays put until the next heartbeat.
|
||||
const canvas = canvasRef.current;
|
||||
if (canvas) {
|
||||
animStateRef.current = makeAnimState();
|
||||
drawSeekbar(canvas, seekbarStyle, heightsRef.current, progressRef.current, bufferedRef.current, animStateRef.current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
animStateRef.current = makeAnimState();
|
||||
let rafId: number | null = null;
|
||||
let pollId: number | null = null;
|
||||
let skip = false;
|
||||
const stop = () => {
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId);
|
||||
@@ -1177,22 +1158,13 @@ export default function WaveformSeek({ trackId }: Props) {
|
||||
}, 400);
|
||||
return;
|
||||
}
|
||||
// 30 fps cap in `reduced` mode: skip every other rAF, advance
|
||||
// animation time by a doubled delta so wave speed stays the same.
|
||||
const isReduced = animationModeRef.current === 'reduced';
|
||||
if (isReduced && skip) {
|
||||
skip = false;
|
||||
rafId = requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
skip = isReduced;
|
||||
animStateRef.current.time += isReduced ? 0.032 : 0.016;
|
||||
animStateRef.current.time += 0.016;
|
||||
drawSeekbar(canvas, seekbarStyle, heightsRef.current, progressRef.current, bufferedRef.current, animStateRef.current);
|
||||
rafId = requestAnimationFrame(tick);
|
||||
};
|
||||
tick();
|
||||
return () => stop();
|
||||
}, [seekbarStyle, animationMode]);
|
||||
}, [seekbarStyle]);
|
||||
|
||||
// Smoothly advance progress between sparse transport ticks.
|
||||
// Preview pauses main sink in Rust while UI `isPlaying` may still be true.
|
||||
@@ -1267,8 +1239,7 @@ export default function WaveformSeek({ trackId }: Props) {
|
||||
: currentVisual + delta * smoothing;
|
||||
visualProgressRef.current = nextVisualProgress;
|
||||
progressRef.current = nextVisualProgress;
|
||||
const needsDirectDraw =
|
||||
!ANIMATED_STYLES.has(styleRef.current) || animationModeRef.current === 'static';
|
||||
const needsDirectDraw = !ANIMATED_STYLES.has(styleRef.current);
|
||||
if (needsDirectDraw && now - lastPaintAt >= INTERPOLATION_PAINT_MIN_MS) {
|
||||
const canvas = canvasRef.current;
|
||||
if (canvas) {
|
||||
|
||||
@@ -979,13 +979,6 @@ export const deTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: 'Aktive Zeile oben mit sanfter Animation.',
|
||||
seekbarStyle: 'Seekbar-Stil',
|
||||
seekbarStyleDesc: 'Aussehen der Wiedergabe-Seekbar auswählen',
|
||||
animationMode: 'Animationen',
|
||||
animationModeDesc: 'Visuelle Glätte gegen geringere GPU-/CPU-Last auf langsamerer Hardware tauschen.',
|
||||
animationModeFull: 'Voll',
|
||||
animationModeReduced: 'Reduziert',
|
||||
animationModeStatic: 'Statisch',
|
||||
animationModeReducedHint: '30 fps Limit auf der Seekbar-Welle; Titel im Player läuft mit halber Geschwindigkeit.',
|
||||
animationModeStaticHint: 'Keine Wellenanimation; Titel im Player wird abgeschnitten statt gescrollt.',
|
||||
seekbarTruewave: 'Echte Wellenform',
|
||||
seekbarPseudowave: 'Pseudo-Wellenform',
|
||||
seekbarLinedot: 'Linie & Punkt',
|
||||
|
||||
@@ -985,13 +985,6 @@ export const enTranslation = {
|
||||
fsPortraitDim: 'Photo dimming',
|
||||
seekbarStyle: 'Seekbar Style',
|
||||
seekbarStyleDesc: 'Choose the look of the player seek bar',
|
||||
animationMode: 'Animations',
|
||||
animationModeDesc: 'Trade visual smoothness for lower GPU/CPU usage on slower hardware.',
|
||||
animationModeFull: 'Full',
|
||||
animationModeReduced: 'Reduced',
|
||||
animationModeStatic: 'Static',
|
||||
animationModeReducedHint: '30 fps cap on the seekbar wave; player title scrolls at half speed.',
|
||||
animationModeStaticHint: 'No wave animation; player title is truncated instead of scrolling.',
|
||||
seekbarTruewave: 'Truewave',
|
||||
seekbarPseudowave: 'Pseudowave',
|
||||
seekbarLinedot: 'Line & Dot',
|
||||
|
||||
@@ -972,13 +972,6 @@ export const esTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: 'La línea activa se ancla arriba con animación suave.',
|
||||
seekbarStyle: 'Estilo de Barra de Progreso',
|
||||
seekbarStyleDesc: 'Elige la apariencia de la barra de progreso del reproductor',
|
||||
animationMode: 'Animaciones',
|
||||
animationModeDesc: 'Cambia fluidez visual por menor uso de GPU/CPU en hardware más lento.',
|
||||
animationModeFull: 'Completas',
|
||||
animationModeReduced: 'Reducidas',
|
||||
animationModeStatic: 'Estáticas',
|
||||
animationModeReducedHint: 'Límite de 30 fps en la onda de la barra; el título del reproductor se desplaza a la mitad de velocidad.',
|
||||
animationModeStaticHint: 'Sin animación de onda; el título del reproductor se trunca en lugar de desplazarse.',
|
||||
seekbarTruewave: 'Forma de Onda Real',
|
||||
seekbarPseudowave: 'Forma de Onda Pseudo',
|
||||
seekbarLinedot: 'Línea y Punto',
|
||||
|
||||
@@ -967,13 +967,6 @@ export const frTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: "La ligne active reste en haut avec une animation fluide.",
|
||||
seekbarStyle: 'Style de la barre de lecture',
|
||||
seekbarStyleDesc: 'Choisir l\'apparence de la barre de progression',
|
||||
animationMode: 'Animations',
|
||||
animationModeDesc: 'Échanger la fluidité visuelle contre une utilisation GPU/CPU réduite sur du matériel plus lent.',
|
||||
animationModeFull: 'Complètes',
|
||||
animationModeReduced: 'Réduites',
|
||||
animationModeStatic: 'Statiques',
|
||||
animationModeReducedHint: 'Plafond de 30 fps sur l\'onde de la barre ; le titre du lecteur défile à moitié vitesse.',
|
||||
animationModeStaticHint: 'Pas d\'animation d\'onde ; le titre du lecteur est tronqué au lieu de défiler.',
|
||||
seekbarTruewave: 'Forme d\'onde réelle',
|
||||
seekbarPseudowave: 'Forme d\'onde pseudo',
|
||||
seekbarLinedot: 'Ligne & point',
|
||||
|
||||
@@ -966,13 +966,6 @@ export const nbTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: 'Aktiv linje forankres øverst med jevn animasjon.',
|
||||
seekbarStyle: 'Søkefelt-stil',
|
||||
seekbarStyleDesc: 'Velg utseendet på avspillingssøkefeltet',
|
||||
animationMode: 'Animasjoner',
|
||||
animationModeDesc: 'Bytt visuell flyt mot lavere GPU-/CPU-bruk på tregere maskinvare.',
|
||||
animationModeFull: 'Full',
|
||||
animationModeReduced: 'Redusert',
|
||||
animationModeStatic: 'Statisk',
|
||||
animationModeReducedHint: '30 fps grense på søkefelt-bølgen; spillertittelen ruller med halv hastighet.',
|
||||
animationModeStaticHint: 'Ingen bølgeanimasjon; spillertittelen forkortes i stedet for å rulle.',
|
||||
seekbarTruewave: 'Ekte bølgeform',
|
||||
seekbarPseudowave: 'Pseudo-bølgeform',
|
||||
seekbarLinedot: 'Linje & punkt',
|
||||
|
||||
@@ -966,13 +966,6 @@ export const nlTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: 'Actieve regel verankerd bovenaan met vloeiende animatie.',
|
||||
seekbarStyle: 'Zoekbalkstijl',
|
||||
seekbarStyleDesc: 'Kies het uiterlijk van de afspeelbalk',
|
||||
animationMode: 'Animaties',
|
||||
animationModeDesc: 'Wissel visuele soepelheid in voor minder GPU-/CPU-gebruik op tragere hardware.',
|
||||
animationModeFull: 'Volledig',
|
||||
animationModeReduced: 'Gereduceerd',
|
||||
animationModeStatic: 'Statisch',
|
||||
animationModeReducedHint: '30 fps limiet op de zoekbalk-golf; spelertitel scrollt op halve snelheid.',
|
||||
animationModeStaticHint: 'Geen golfanimatie; spelertitel wordt afgekapt in plaats van scrollen.',
|
||||
seekbarTruewave: 'Echte golfvorm',
|
||||
seekbarPseudowave: 'Pseudo-golfvorm',
|
||||
seekbarLinedot: 'Lijn & punt',
|
||||
|
||||
@@ -1020,13 +1020,6 @@ export const ruTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: 'Активная строка фиксируется сверху с плавной анимацией.',
|
||||
seekbarStyle: 'Стиль прогресс-бара',
|
||||
seekbarStyleDesc: 'Выбор внешнего вида полосы воспроизведения',
|
||||
animationMode: 'Анимации',
|
||||
animationModeDesc: 'Обменивайте визуальную плавность на меньшую нагрузку на GPU/CPU на слабом железе.',
|
||||
animationModeFull: 'Полные',
|
||||
animationModeReduced: 'Сниженные',
|
||||
animationModeStatic: 'Статичные',
|
||||
animationModeReducedHint: 'Ограничение 30 fps для волны прогресс-бара; заголовок плеера прокручивается с половинной скоростью.',
|
||||
animationModeStaticHint: 'Без анимации волны; заголовок плеера обрезается вместо прокрутки.',
|
||||
seekbarTruewave: 'Реальная форма волны',
|
||||
seekbarPseudowave: 'Псевдо форма волны',
|
||||
seekbarLinedot: 'Линия и точка',
|
||||
|
||||
@@ -961,13 +961,6 @@ export const zhTranslation = {
|
||||
sidebarLyricsStyleAppleDesc: '活动行固定在顶部,带有平滑弹簧动画。',
|
||||
seekbarStyle: '进度条样式',
|
||||
seekbarStyleDesc: '选择播放进度条的外观',
|
||||
animationMode: '动画',
|
||||
animationModeDesc: '在较慢的硬件上以视觉流畅度换取更低的 GPU/CPU 占用。',
|
||||
animationModeFull: '完整',
|
||||
animationModeReduced: '减少',
|
||||
animationModeStatic: '静态',
|
||||
animationModeReducedHint: '进度条波形限制为 30 fps;播放器标题以半速滚动。',
|
||||
animationModeStaticHint: '无波形动画;播放器标题被截断而不滚动。',
|
||||
seekbarTruewave: '真实波形',
|
||||
seekbarPseudowave: '伪波形',
|
||||
seekbarLinedot: '线条与点',
|
||||
|
||||
@@ -3922,43 +3922,6 @@ export default function Settings() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="settings-norm-block" style={{ marginTop: '1rem', paddingTop: '1rem', borderTop: '1px solid var(--border)' }}>
|
||||
<div className="settings-norm-field">
|
||||
<div className="settings-norm-row">
|
||||
<span className="settings-norm-label">{t('settings.animationMode')}</span>
|
||||
<div style={{ display: 'flex', gap: '0.4rem', flexWrap: 'wrap' }}>
|
||||
<button
|
||||
className={`btn ${auth.animationMode === 'full' ? 'btn-primary' : 'btn-ghost'}`}
|
||||
style={{ fontSize: 12, padding: '4px 14px' }}
|
||||
onClick={() => auth.setAnimationMode('full')}
|
||||
>
|
||||
{t('settings.animationModeFull')}
|
||||
</button>
|
||||
<button
|
||||
className={`btn ${auth.animationMode === 'reduced' ? 'btn-primary' : 'btn-ghost'}`}
|
||||
style={{ fontSize: 12, padding: '4px 14px' }}
|
||||
onClick={() => auth.setAnimationMode('reduced')}
|
||||
>
|
||||
{t('settings.animationModeReduced')}
|
||||
</button>
|
||||
<button
|
||||
className={`btn ${auth.animationMode === 'static' ? 'btn-primary' : 'btn-ghost'}`}
|
||||
style={{ fontSize: 12, padding: '4px 14px' }}
|
||||
onClick={() => auth.setAnimationMode('static')}
|
||||
>
|
||||
{t('settings.animationModeStatic')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-norm-help">
|
||||
{auth.animationMode === 'reduced'
|
||||
? t('settings.animationModeReducedHint')
|
||||
: auth.animationMode === 'static'
|
||||
? t('settings.animationModeStaticHint')
|
||||
: t('settings.animationModeDesc')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSubSection>
|
||||
|
||||
|
||||
+8
-19
@@ -25,7 +25,6 @@ export interface ServerProfile {
|
||||
export type SeekbarStyle = 'truewave' | 'pseudowave' | 'linedot' | 'bar' | 'thick' | 'segmented' | 'neon' | 'pulsewave' | 'particletrail' | 'liquidfill' | 'retrotape';
|
||||
export type LoggingMode = 'off' | 'normal' | 'debug';
|
||||
export type NormalizationEngine = 'off' | 'replaygain' | 'loudness';
|
||||
export type AnimationMode = 'full' | 'reduced' | 'static';
|
||||
export type DiscordCoverSource = 'none' | 'apple' | 'server';
|
||||
|
||||
/** Integrated-loudness target presets (Settings + analysis). */
|
||||
@@ -179,12 +178,6 @@ interface AuthState {
|
||||
lastSeenChangelogVersion: string;
|
||||
|
||||
seekbarStyle: SeekbarStyle;
|
||||
/** Animation budget across the UI:
|
||||
* - `full` = native frame rate
|
||||
* - `reduced`= 30 fps cap on animated seekbar; marquee at half frame rate
|
||||
* - `static` = ~1 fps progress (no rAF interpolation); marquee disabled (truncate)
|
||||
*/
|
||||
animationMode: AnimationMode;
|
||||
/** Persisted UI toggle: is the Now Playing section in queue panel collapsed */
|
||||
queueNowPlayingCollapsed: boolean;
|
||||
|
||||
@@ -337,7 +330,6 @@ interface AuthState {
|
||||
setShowChangelogOnUpdate: (v: boolean) => void;
|
||||
setLastSeenChangelogVersion: (v: string) => void;
|
||||
setSeekbarStyle: (v: SeekbarStyle) => void;
|
||||
setAnimationMode: (v: AnimationMode) => void;
|
||||
setQueueNowPlayingCollapsed: (v: boolean) => void;
|
||||
setEnableHiRes: (v: boolean) => void;
|
||||
setAudioOutputDevice: (v: string | null) => void;
|
||||
@@ -475,7 +467,6 @@ export const useAuthStore = create<AuthState>()(
|
||||
showChangelogOnUpdate: true,
|
||||
lastSeenChangelogVersion: '',
|
||||
seekbarStyle: 'truewave',
|
||||
animationMode: 'full',
|
||||
queueNowPlayingCollapsed: false,
|
||||
enableHiRes: false,
|
||||
audioOutputDevice: null,
|
||||
@@ -640,7 +631,6 @@ export const useAuthStore = create<AuthState>()(
|
||||
setLastSeenChangelogVersion: (v) => set({ lastSeenChangelogVersion: v }),
|
||||
|
||||
setSeekbarStyle: (v) => set({ seekbarStyle: v }),
|
||||
setAnimationMode: (v) => set({ animationMode: v }),
|
||||
setQueueNowPlayingCollapsed: (v: boolean) => set({ queueNowPlayingCollapsed: v }),
|
||||
setEnableHiRes: (v) => set({ enableHiRes: v }),
|
||||
setAudioOutputDevice: (v) => set({ audioOutputDevice: v }),
|
||||
@@ -843,14 +833,14 @@ export const useAuthStore = create<AuthState>()(
|
||||
? {}
|
||||
: { seekbarStyle: 'truewave' as SeekbarStyle };
|
||||
|
||||
// `reducedAnimations: boolean` superseded by `animationMode: AnimationMode`.
|
||||
// Map legacy true → 'reduced', false/missing → 'full'. Static is opt-in only.
|
||||
let animationModeMigrated: { animationMode?: AnimationMode } = {};
|
||||
const legacyReduced = (state as { reducedAnimations?: unknown }).reducedAnimations;
|
||||
const VALID_ANIM_MODES = new Set<string>(['full', 'reduced', 'static']);
|
||||
if (!VALID_ANIM_MODES.has(state.animationMode as string)) {
|
||||
animationModeMigrated = { animationMode: legacyReduced === true ? 'reduced' : 'full' };
|
||||
}
|
||||
// The `animationMode` 3-state setting was removed; users on `'reduced'`
|
||||
// or `'static'` collapse onto the former `'full'` path automatically as
|
||||
// soon as the field is gone from the store. Strip the persisted field
|
||||
// so it doesn't sit in localStorage as cruft.
|
||||
delete (state as { animationMode?: unknown }).animationMode;
|
||||
// The earlier `reducedAnimations: boolean` predecessor likewise loses
|
||||
// its meaning; clear it for the same reason.
|
||||
delete (state as { reducedAnimations?: unknown }).reducedAnimations;
|
||||
|
||||
const st = state as {
|
||||
loudnessTargetLufs?: unknown;
|
||||
@@ -891,7 +881,6 @@ export const useAuthStore = create<AuthState>()(
|
||||
...lyricsSourcesMigrated,
|
||||
...wheelSmoothOneTime,
|
||||
...seekbarStyleMigrated,
|
||||
...animationModeMigrated,
|
||||
...discordCoverSourceMigrated,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1516,23 +1516,12 @@ html[data-platform="windows"] .player-bar.floating {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* `static` animation mode: never scroll, truncate with ellipsis instead. */
|
||||
.marquee-wrap[data-anim-mode="static"] {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.marquee-scroll {
|
||||
display: inline-block;
|
||||
animation: player-marquee 12s linear infinite;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
/* `reduced` animation mode: double the marquee cycle so visual change
|
||||
per second is roughly halved. */
|
||||
.marquee-wrap[data-anim-mode="reduced"] .marquee-scroll {
|
||||
animation-duration: 24s;
|
||||
}
|
||||
|
||||
@keyframes player-marquee {
|
||||
0%, 12% { transform: translateX(0); }
|
||||
78%, 88% { transform: translateX(var(--marquee-amount)); }
|
||||
|
||||
Reference in New Issue
Block a user