修复&优化听力补偿模块功能
This commit is contained in:
+25
-3
@@ -1,7 +1,7 @@
|
||||
import { lazy, Suspense } from "react";
|
||||
import { lazy, Suspense, useEffect, useRef } from "react";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { Route, Switch, Router } from "wouter";
|
||||
import { Route, Switch, Router, useLocation } from "wouter";
|
||||
import { useHashLocation } from "wouter/use-hash-location";
|
||||
import ErrorBoundary from "./components/ErrorBoundary";
|
||||
import PageLoader from "./components/PageLoader";
|
||||
@@ -15,7 +15,7 @@ import BluetoothPage from "./pages/BluetoothPage";
|
||||
import SystemPage from "./pages/SystemPage";
|
||||
import IOPage from "./pages/IOPage";
|
||||
import VUPage from "./pages/VUPage";
|
||||
import SelectPage from "./pages/SelectPage";
|
||||
import SelectPage, { consumeSelectReturnScroll } from "./pages/SelectPage";
|
||||
import CommunityPage from "./pages/CommunityPage";
|
||||
import ChangelogPage from "./pages/ChangelogPage";
|
||||
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
||||
@@ -54,6 +54,27 @@ function EffectsPageRoute() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the calling page's scroll position when returning from /select.
|
||||
* The select page is short, so the browser clamps window scroll to 0 while on
|
||||
* it; navigateToSelect() captures the offset before navigating and we re-apply
|
||||
* it once the calling page (e.g. Effects) has remounted.
|
||||
*/
|
||||
function SelectScrollRestore() {
|
||||
const [location] = useLocation();
|
||||
const prevRef = useRef(location);
|
||||
useEffect(() => {
|
||||
const prev = prevRef.current;
|
||||
prevRef.current = location;
|
||||
if (prev !== "/select" || location === "/select") return;
|
||||
const y = consumeSelectReturnScroll();
|
||||
if (y == null || y <= 0) return;
|
||||
// Wait a frame so the returning page has rendered and the document has height.
|
||||
requestAnimationFrame(() => window.scrollTo(0, y));
|
||||
}, [location]);
|
||||
return null;
|
||||
}
|
||||
|
||||
function AppRouter() {
|
||||
return (
|
||||
<Switch>
|
||||
@@ -85,6 +106,7 @@ function App() {
|
||||
<Toaster />
|
||||
<Router hook={useHashLocation}>
|
||||
<AIDrawerProvider>
|
||||
<SelectScrollRestore />
|
||||
<div className={appShellOuterClass}>
|
||||
<div className={appShellColumnClass}>
|
||||
<DesktopAIShell>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import * as echarts from "echarts";
|
||||
import {
|
||||
HEARING_FREQ_LABELS,
|
||||
buildHearingSplineSeries,
|
||||
resolveHearingFreqLabels,
|
||||
} from "@/lib/hearingCurve";
|
||||
|
||||
const LEFT_COLOR = "rgb(0, 255, 246)";
|
||||
@@ -29,6 +29,11 @@ export default function HearingCompensationChart({
|
||||
const leftSeries = useMemo(() => buildHearingSplineSeries(left), [left]);
|
||||
const rightSeries = useMemo(() => buildHearingSplineSeries(right), [right]);
|
||||
|
||||
// X axis adapts to the control-point count: 7-band (legacy) or 10-band (adds 3k/5k/6k).
|
||||
const pointCount = Math.max(left.length, right.length);
|
||||
const freqLabels = useMemo(() => resolveHearingFreqLabels(pointCount), [pointCount]);
|
||||
const xMax = Math.max(0, pointCount - 1);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
@@ -76,13 +81,13 @@ export default function HearingCompensationChart({
|
||||
xAxis: {
|
||||
type: "value",
|
||||
min: 0,
|
||||
max: HEARING_FREQ_LABELS.length - 1,
|
||||
max: xMax,
|
||||
interval: 1,
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
const idx = Math.round(value);
|
||||
if (Math.abs(value - idx) > 1e-6) return "";
|
||||
return HEARING_FREQ_LABELS[idx] ?? "";
|
||||
return freqLabels[idx] ?? "";
|
||||
},
|
||||
fontSize: 14,
|
||||
color: "#999",
|
||||
@@ -98,7 +103,9 @@ export default function HearingCompensationChart({
|
||||
yAxis: {
|
||||
type: "value",
|
||||
min: -80,
|
||||
max: 0,
|
||||
// Top raised to +20 dB: levels above 70 map to positive dB and were
|
||||
// clipped at the old max of 0, leaving gaps at the top of the curve.
|
||||
max: 20,
|
||||
interval: 10,
|
||||
name: "dB",
|
||||
nameLocation: "end",
|
||||
@@ -157,7 +164,7 @@ export default function HearingCompensationChart({
|
||||
ro.disconnect();
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, [leftSeries, rightSeries, leftLabel, rightLabel]);
|
||||
}, [leftSeries, rightSeries, leftLabel, rightLabel, freqLabels, xMax]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -171,7 +178,7 @@ export default function HearingCompensationChart({
|
||||
ref={containerRef}
|
||||
className={
|
||||
className
|
||||
?? "w-full h-[min(42vw,200px)] min-h-[140px] md:h-[260px] lg:h-[300px] rounded-lg bg-black/30"
|
||||
?? "w-full h-[min(52vw,240px)] min-h-[180px] md:h-[320px] lg:h-[380px] rounded-lg bg-black/30"
|
||||
}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
/** Hearing compensation curve helpers: map levels → dB and cubic-spline smooth. */
|
||||
|
||||
/** 7-band control points (legacy): 125/250/500/1k/2k/4k/8k. */
|
||||
export const HEARING_FREQ_LABELS = ["125", "250", "500", "1k", "2k", "4k", "8k"] as const;
|
||||
|
||||
/** 10-band control points: adds 3k/5k/6k → 125/250/500/1k/2k/3k/4k/5k/6k/8k (ascending). */
|
||||
export const HEARING_FREQ_LABELS_10 = [
|
||||
"125", "250", "500", "1k", "2k", "3k", "4k", "5k", "6k", "8k",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Resolve the X-axis frequency labels for a given control-point `count`.
|
||||
* 10 points → the extended 3k/5k/6k set; any other count → the legacy 7-band
|
||||
* set. Keeps the chart compatible with both 7-point and 10-point hearing data.
|
||||
*/
|
||||
export function resolveHearingFreqLabels(count: number): readonly string[] {
|
||||
return count === HEARING_FREQ_LABELS_10.length ? HEARING_FREQ_LABELS_10 : HEARING_FREQ_LABELS;
|
||||
}
|
||||
|
||||
/** Y = n + (-70). Values outside [-80, 0] are kept as-is (axis still -80…0). */
|
||||
export function mapHearingLevelToDb(n: number): number {
|
||||
return n - 70;
|
||||
@@ -92,7 +107,8 @@ export function createNaturalCubicSpline(
|
||||
|
||||
/**
|
||||
* Build densely sampled [xIndex, dB] pairs for a smooth ECharts line.
|
||||
* X uses equal spacing 0…6 matching HEARING_FREQ_LABELS.
|
||||
* X uses equal spacing 0…N-1 where N = `levels.length`, so it adapts to both
|
||||
* 7-point and 10-point hearing data (see `resolveHearingFreqLabels`).
|
||||
*/
|
||||
export function buildHearingSplineSeries(
|
||||
levels: number[],
|
||||
|
||||
@@ -917,7 +917,7 @@ export default function EffectsPage() {
|
||||
</button>
|
||||
{hearingOn && hearingProfiles[hearingIdx] && (
|
||||
<HearingCompensationChart
|
||||
className="mt-3 w-full h-[min(42vw,200px)] min-h-[140px] md:h-[260px] lg:h-[300px] rounded-lg bg-black/30"
|
||||
className="mt-3 w-full h-[min(52vw,240px)] min-h-[180px] md:h-[320px] lg:h-[380px] rounded-lg bg-black/30"
|
||||
left={hearingProfiles[hearingIdx].l}
|
||||
right={hearingProfiles[hearingIdx].r}
|
||||
leftLabel={effectText.hearing?.left ?? "Left"}
|
||||
|
||||
@@ -185,10 +185,23 @@ export function consumeSelectPageResult() {
|
||||
|
||||
|
||||
|
||||
// Scroll position of the calling page, captured before navigating to /select.
|
||||
// The select page is short, so the browser clamps window scroll to 0 while on
|
||||
// it; without restoring, the calling page (e.g. Effects) reopens at the top.
|
||||
let selectReturnScroll: number | null = null;
|
||||
|
||||
/** Consume the stored scroll position (once). Returns null when absent. */
|
||||
export function consumeSelectReturnScroll(): number | null {
|
||||
const y = selectReturnScroll;
|
||||
selectReturnScroll = null;
|
||||
return y;
|
||||
}
|
||||
|
||||
// Helper function to set select page state
|
||||
|
||||
export function navigateToSelect(title: string, options: string[], selected: number, back: string, key: string) {
|
||||
|
||||
selectReturnScroll = window.scrollY;
|
||||
selectPageState = { title, options, selected, back, key };
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user