From edd677c79db68ea6f5315d00cbabbd3c1fd88bf6 Mon Sep 17 00:00:00 2001 From: yangy Date: Mon, 18 May 2026 17:21:00 +0800 Subject: [PATCH] Refactor LuxsinAPI to correct firmware version comparison in boot sound settings; enhance useStrokeDrawAnimation hook to support path updates without replaying animation. Update EQPage to utilize new animation options for smoother transitions during EQ parameter edits. --- client/src/lib/luxsinApi.ts | 4 ++-- client/src/lib/useStrokeDrawAnimation.ts | 18 ++++++++++++++++-- client/src/pages/EQPage.tsx | 18 ++++++++++-------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/client/src/lib/luxsinApi.ts b/client/src/lib/luxsinApi.ts index 25996cc..08d29a8 100644 --- a/client/src/lib/luxsinApi.ts +++ b/client/src/lib/luxsinApi.ts @@ -312,7 +312,7 @@ export class LuxsinAPI { setCrossfeedEnable(enable: boolean) { return this.setSetting({ crossfeed_enable: enable ? 1 : 0 }); } setXlrPolarity(reverse: boolean) { return this.setSetting({ xlr: reverse ? 1 : 0 }); } setDacArc(earc: boolean) { return this.setSetting({ dacArc: earc ? 1 : 0 }); } - /** Boot volume level: 0 = default, 1..6 = -5..-30 dB, 7..10 = -35..-50 dB (firmware > 26). */ + /** Boot volume level: 0 = default, 1..6 = -5..-30 dB, 7..10 = -35..-50 dB (firmware >= 26). */ setBootSound(level: number) { return this.setSetting({ bootSound: Math.max(0, Math.min(10, Math.round(level))) }); } @@ -335,7 +335,7 @@ export function parseFirmwareVersion(version: unknown): number { /** Max `bootSound` index available for the current firmware. */ export function getBootSoundMaxIndex(firmwareVersion: number): number { - return firmwareVersion > 26 ? 10 : 6; + return firmwareVersion >= 26 ? 10 : 6; } /** Normalize `bootSound` from syncData (0 = default … 10 = -50 dB). */ diff --git a/client/src/lib/useStrokeDrawAnimation.ts b/client/src/lib/useStrokeDrawAnimation.ts index 03c5d7d..5d134b2 100644 --- a/client/src/lib/useStrokeDrawAnimation.ts +++ b/client/src/lib/useStrokeDrawAnimation.ts @@ -1,8 +1,9 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; /** * Reusable left-to-right SVG stroke draw animation. * Pass `enabled=false` to disable and clear dash styles (e.g. while dragging). + * Pass `replayOnPathChange=false` to update the path in place without replaying the intro. */ export function useStrokeDrawAnimation( pathRef: React.RefObject, @@ -11,11 +12,15 @@ export function useStrokeDrawAnimation( enabled?: boolean; durationMs?: number; easing?: string; + /** When false, only the first draw runs; later `pathD` updates show immediately. */ + replayOnPathChange?: boolean; }, ) { const enabled = options?.enabled ?? true; const durationMs = options?.durationMs ?? 1350; const easing = options?.easing ?? "cubic-bezier(0.33, 1, 0.68, 1)"; + const replayOnPathChange = options?.replayOnPathChange ?? true; + const hasPlayedRef = useRef(false); useEffect(() => { const path = pathRef.current; @@ -28,6 +33,13 @@ export function useStrokeDrawAnimation( return; } + if (!replayOnPathChange && hasPlayedRef.current) { + path.style.strokeDasharray = ""; + path.style.strokeDashoffset = ""; + path.style.transition = ""; + return; + } + const length = path.getTotalLength(); if (!Number.isFinite(length) || length <= 0) return; @@ -36,6 +48,8 @@ export function useStrokeDrawAnimation( path.style.transition = "none"; let cancelled = false; + hasPlayedRef.current = true; + const id = requestAnimationFrame(() => { requestAnimationFrame(() => { if (cancelled) return; @@ -48,5 +62,5 @@ export function useStrokeDrawAnimation( cancelled = true; cancelAnimationFrame(id); }; - }, [pathRef, pathD, enabled, durationMs, easing]); + }, [pathRef, pathD, enabled, durationMs, easing, replayOnPathChange]); } diff --git a/client/src/pages/EQPage.tsx b/client/src/pages/EQPage.tsx index c0ab20c..5586c38 100644 --- a/client/src/pages/EQPage.tsx +++ b/client/src/pages/EQPage.tsx @@ -274,18 +274,19 @@ function FreqChart({ const hasRawCurve = !!rawPathD; const hasEqualizedCurve = !!equalizedPathD; - // Reusable left-to-right stroke draw animation (skip while dragging a band). + // Intro draw once; EQ param edits update path in place without replaying the stroke animation. + const strokeAnimOpts = { durationMs: 1350, replayOnPathChange: false as const }; useStrokeDrawAnimation(curvePathRef, pathD, { + ...strokeAnimOpts, enabled: !isBandDragging, - durationMs: 1350, }); useStrokeDrawAnimation(rawPathRef, rawPathD, { + ...strokeAnimOpts, enabled: !isBandDragging && !!rawPathD, - durationMs: 1350, }); useStrokeDrawAnimation(equalizedPathRef, equalizedPathD, { + ...strokeAnimOpts, enabled: !isBandDragging && !!equalizedPathD, - durationMs: 1350, }); const targetMode: "A" | "B" = abMode === "A" ? "B" : "A"; @@ -1153,6 +1154,11 @@ export default function EQPage() { } }, [deviceState?.peqSelect]); + // 切换耳机时默认回到 A 组对比 + useEffect(() => { + setAbMode("A"); + }, [headphoneIdx]); + // 获取耳机原始曲线 const getModelCurve = async (brand: string, name: string) => { try { @@ -1541,10 +1547,6 @@ export default function EQPage() { const updateBand = (idx: number, patch: Partial) => { const nextBands = bands.map((b, i) => (i === idx ? { ...b, ...patch } : b)); setBands(nextBands); - // Re-render chart when band changes - if (peqItems.length > 0 && headphoneIdx < peqItems.length) { - renderCharts(nextBands, currentRawCurve, false); - } }; const updateCurrentPeqMeta = (patch: Partial<{ autoPre: number; preamp: number }>) => {