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.
This commit is contained in:
@@ -312,7 +312,7 @@ export class LuxsinAPI {
|
|||||||
setCrossfeedEnable(enable: boolean) { return this.setSetting({ crossfeed_enable: enable ? 1 : 0 }); }
|
setCrossfeedEnable(enable: boolean) { return this.setSetting({ crossfeed_enable: enable ? 1 : 0 }); }
|
||||||
setXlrPolarity(reverse: boolean) { return this.setSetting({ xlr: reverse ? 1 : 0 }); }
|
setXlrPolarity(reverse: boolean) { return this.setSetting({ xlr: reverse ? 1 : 0 }); }
|
||||||
setDacArc(earc: boolean) { return this.setSetting({ dacArc: earc ? 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) {
|
setBootSound(level: number) {
|
||||||
return this.setSetting({ bootSound: Math.max(0, Math.min(10, Math.round(level))) });
|
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. */
|
/** Max `bootSound` index available for the current firmware. */
|
||||||
export function getBootSoundMaxIndex(firmwareVersion: number): number {
|
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). */
|
/** Normalize `bootSound` from syncData (0 = default … 10 = -50 dB). */
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable left-to-right SVG stroke draw animation.
|
* Reusable left-to-right SVG stroke draw animation.
|
||||||
* Pass `enabled=false` to disable and clear dash styles (e.g. while dragging).
|
* 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(
|
export function useStrokeDrawAnimation(
|
||||||
pathRef: React.RefObject<SVGPathElement | null>,
|
pathRef: React.RefObject<SVGPathElement | null>,
|
||||||
@@ -11,11 +12,15 @@ export function useStrokeDrawAnimation(
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
durationMs?: number;
|
durationMs?: number;
|
||||||
easing?: string;
|
easing?: string;
|
||||||
|
/** When false, only the first draw runs; later `pathD` updates show immediately. */
|
||||||
|
replayOnPathChange?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const enabled = options?.enabled ?? true;
|
const enabled = options?.enabled ?? true;
|
||||||
const durationMs = options?.durationMs ?? 1350;
|
const durationMs = options?.durationMs ?? 1350;
|
||||||
const easing = options?.easing ?? "cubic-bezier(0.33, 1, 0.68, 1)";
|
const easing = options?.easing ?? "cubic-bezier(0.33, 1, 0.68, 1)";
|
||||||
|
const replayOnPathChange = options?.replayOnPathChange ?? true;
|
||||||
|
const hasPlayedRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const path = pathRef.current;
|
const path = pathRef.current;
|
||||||
@@ -28,6 +33,13 @@ export function useStrokeDrawAnimation(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!replayOnPathChange && hasPlayedRef.current) {
|
||||||
|
path.style.strokeDasharray = "";
|
||||||
|
path.style.strokeDashoffset = "";
|
||||||
|
path.style.transition = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const length = path.getTotalLength();
|
const length = path.getTotalLength();
|
||||||
if (!Number.isFinite(length) || length <= 0) return;
|
if (!Number.isFinite(length) || length <= 0) return;
|
||||||
|
|
||||||
@@ -36,6 +48,8 @@ export function useStrokeDrawAnimation(
|
|||||||
path.style.transition = "none";
|
path.style.transition = "none";
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
hasPlayedRef.current = true;
|
||||||
|
|
||||||
const id = requestAnimationFrame(() => {
|
const id = requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
@@ -48,5 +62,5 @@ export function useStrokeDrawAnimation(
|
|||||||
cancelled = true;
|
cancelled = true;
|
||||||
cancelAnimationFrame(id);
|
cancelAnimationFrame(id);
|
||||||
};
|
};
|
||||||
}, [pathRef, pathD, enabled, durationMs, easing]);
|
}, [pathRef, pathD, enabled, durationMs, easing, replayOnPathChange]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,18 +274,19 @@ function FreqChart({
|
|||||||
const hasRawCurve = !!rawPathD;
|
const hasRawCurve = !!rawPathD;
|
||||||
const hasEqualizedCurve = !!equalizedPathD;
|
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, {
|
useStrokeDrawAnimation(curvePathRef, pathD, {
|
||||||
|
...strokeAnimOpts,
|
||||||
enabled: !isBandDragging,
|
enabled: !isBandDragging,
|
||||||
durationMs: 1350,
|
|
||||||
});
|
});
|
||||||
useStrokeDrawAnimation(rawPathRef, rawPathD, {
|
useStrokeDrawAnimation(rawPathRef, rawPathD, {
|
||||||
|
...strokeAnimOpts,
|
||||||
enabled: !isBandDragging && !!rawPathD,
|
enabled: !isBandDragging && !!rawPathD,
|
||||||
durationMs: 1350,
|
|
||||||
});
|
});
|
||||||
useStrokeDrawAnimation(equalizedPathRef, equalizedPathD, {
|
useStrokeDrawAnimation(equalizedPathRef, equalizedPathD, {
|
||||||
|
...strokeAnimOpts,
|
||||||
enabled: !isBandDragging && !!equalizedPathD,
|
enabled: !isBandDragging && !!equalizedPathD,
|
||||||
durationMs: 1350,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const targetMode: "A" | "B" = abMode === "A" ? "B" : "A";
|
const targetMode: "A" | "B" = abMode === "A" ? "B" : "A";
|
||||||
@@ -1153,6 +1154,11 @@ export default function EQPage() {
|
|||||||
}
|
}
|
||||||
}, [deviceState?.peqSelect]);
|
}, [deviceState?.peqSelect]);
|
||||||
|
|
||||||
|
// 切换耳机时默认回到 A 组对比
|
||||||
|
useEffect(() => {
|
||||||
|
setAbMode("A");
|
||||||
|
}, [headphoneIdx]);
|
||||||
|
|
||||||
// 获取耳机原始曲线
|
// 获取耳机原始曲线
|
||||||
const getModelCurve = async (brand: string, name: string) => {
|
const getModelCurve = async (brand: string, name: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -1541,10 +1547,6 @@ export default function EQPage() {
|
|||||||
const updateBand = (idx: number, patch: Partial<typeof bands[0]>) => {
|
const updateBand = (idx: number, patch: Partial<typeof bands[0]>) => {
|
||||||
const nextBands = bands.map((b, i) => (i === idx ? { ...b, ...patch } : b));
|
const nextBands = bands.map((b, i) => (i === idx ? { ...b, ...patch } : b));
|
||||||
setBands(nextBands);
|
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 }>) => {
|
const updateCurrentPeqMeta = (patch: Partial<{ autoPre: number; preamp: number }>) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user