Refactor build and start scripts in package.json; add plugin to exclude debug files from production build in vite.config.ts; remove ConnectScreen component and replace with ConnectionPlaceholder in multiple pages; update DeviceContext to auto-connect using device IP from URL; enhance locale files with new device IP labels.

This commit is contained in:
yangy
2026-05-22 15:54:11 +08:00
parent 747331c850
commit 32f15217ad
13 changed files with 199 additions and 173 deletions
+92 -37
View File
@@ -10,7 +10,7 @@
6. iOS list rows (输入源/输出端口/音频设置/系统设置/VU表)
============================================================ */
import { useDevice } from "@/contexts/DeviceContext";
import ConnectScreen from "@/components/ConnectScreen";
import ConnectionPlaceholder from "@/components/ConnectionPlaceholder";
import BottomNav from "@/components/BottomNav";
import {
AlertDialog,
@@ -32,6 +32,7 @@ import {
Sliders,
Activity,
Gauge,
Undo2,
ChevronDown,
Wifi,
Bluetooth,
@@ -57,14 +58,17 @@ const JACK_EGG_SEQUENCE = ["lg", "smTop", "smBot"] as const;
type JackId = (typeof JACK_EGG_SEQUENCE)[number];
const VU_BAR_COUNT = 20;
const EGG_DURATION_MS = 4200;
const EGG_BEAT_MS = 130;
const EGG_DANCE_DURATION_MS = 5000;
const EGG_WAVE_DURATION_MS = 5000;
const EGG_WAVE_HALF_MS = EGG_WAVE_DURATION_MS / 2;
const EGG_BEAT_MS = 400;
const JACK_SEQUENCE_TIMEOUT_MS = 4000;
/** 旋律主音在 20 段 VU 上的位置(约 4.2s */
/** 旋律主音在 20 段 VU 上的位置(约 5s */
const EGG_MELODY_LEAD = [
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1,
0, 4, 8, 12, 16, 19, 16, 12, 8, 4, 0, 2, 5, 9, 13, 17, 19, 14, 10, 6, 2,
0, 3, 7, 11, 15, 18, 15, 11, 7, 3,
];
function idleVuBarHeights(): number[] {
@@ -73,15 +77,44 @@ function idleVuBarHeights(): number[] {
);
}
function vuHeightsForMelodyBeat(lead: number, pulse: number): number[] {
/** 彩蛋律动:主峰随节拍移动,每拍起落,全柱叠加波纹 */
function vuHeightsForDance(elapsedMs: number): number[] {
const t = elapsedMs / 1000;
const beatIdx = Math.floor(elapsedMs / EGG_BEAT_MS) % EGG_MELODY_LEAD.length;
const lead = EGG_MELODY_LEAD[beatIdx];
const beatPhase = (elapsedMs % EGG_BEAT_MS) / EGG_BEAT_MS;
const kick = Math.sin(beatPhase * Math.PI);
return Array.from({ length: VU_BAR_COUNT }, (_, i) => {
const dist = Math.abs(i - lead);
let amp = 0.14;
if (dist === 0) amp = 0.96;
else if (dist === 1) amp = 0.74;
else if (dist === 2) amp = 0.5;
else if (dist === 3) amp = 0.3;
return Math.min(1, amp * (0.82 + pulse * 0.18));
let peak = 0.06;
if (dist === 0) peak = 1;
else if (dist === 1) peak = 0.82;
else if (dist === 2) peak = 0.62;
else if (dist === 3) peak = 0.42;
else if (dist === 4) peak = 0.26;
const ripple = 0.35 * Math.max(0, Math.sin(t * 2.75 + i * 0.62));
const sway = 0.1 * Math.sin(t * 1.4 + i * 0.35);
const h = 0.05 + peak * kick + ripple * (0.55 + kick * 0.45) + sway;
return Math.min(1, Math.max(0.05, h));
});
}
/** 彩蛋律动二:海浪形波从左→右,再右→左 */
function vuHeightsForWave(phaseElapsedMs: number): number[] {
const half = EGG_WAVE_HALF_MS;
const ltr = phaseElapsedMs < half;
const localMs = ltr ? phaseElapsedMs : phaseElapsedMs - half;
const t = localMs / 1000;
const direction = ltr ? 1 : -1;
const speed = 0.75;
return Array.from({ length: VU_BAR_COUNT }, (_, i) => {
const phase = t * speed * Math.PI * 2 - direction * i * 0.72;
const wave = (Math.sin(phase) + 1) / 2;
const foam = 0.06 + 0.05 * Math.sin(i * 0.28 + t * 0.5);
return Math.min(1, Math.max(0.06, foam + wave * 0.9));
});
}
@@ -131,7 +164,7 @@ function ListRow({
}
export default function Home() {
const { isConnected, deviceState, setVolume, updateSetting, api, disconnect, isDemoMode } = useDevice();
const { isConnected, deviceState, setVolume, updateSetting, api, disconnect, isDemoMode, ip } = useDevice();
const [, setLocation] = useLocation();
const powerActionRef = useRef(false);
const hpEqLastTapRef = useRef<number | null>(null);
@@ -144,6 +177,8 @@ export default function Home() {
const jackProgressRef = useRef(0);
const jackResetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const eggRafRef = useRef<number | null>(null);
/** 下次三连击成功时播放的律动:1=旋律,2=海浪(各需单独三连击触发) */
const jackEggNextRef = useRef<1 | 2>(1);
const homeText = useMemo((): HomeLocale => {
if (!deviceState) return {} as HomeLocale;
@@ -257,28 +292,33 @@ export default function Home() {
};
}, []);
const triggerJackEasterEgg = useCallback(() => {
setEasterEggActive(true);
jackProgressRef.current = 0;
const start = performance.now();
const runJackEggAnimation = useCallback(
(mode: "dance" | "wave") => {
setEasterEggActive(true);
setVuHeights(mode === "dance" ? vuHeightsForDance(0) : vuHeightsForWave(0));
jackProgressRef.current = 0;
const duration = mode === "dance" ? EGG_DANCE_DURATION_MS : EGG_WAVE_DURATION_MS;
const start = performance.now();
const tick = (now: number) => {
const elapsed = now - start;
if (elapsed >= EGG_DURATION_MS) {
setEasterEggActive(false);
setVuHeights(idleVuBarHeights());
eggRafRef.current = null;
return;
}
const beat = Math.floor(elapsed / EGG_BEAT_MS) % EGG_MELODY_LEAD.length;
const pulse = 0.5 + 0.5 * Math.sin(elapsed * 0.02);
setVuHeights(vuHeightsForMelodyBeat(EGG_MELODY_LEAD[beat], pulse));
const tick = (now: number) => {
const elapsed = now - start;
if (elapsed >= duration) {
setEasterEggActive(false);
setVuHeights(idleVuBarHeights());
eggRafRef.current = null;
return;
}
setVuHeights(
mode === "dance" ? vuHeightsForDance(elapsed) : vuHeightsForWave(elapsed),
);
eggRafRef.current = requestAnimationFrame(tick);
};
if (eggRafRef.current !== null) cancelAnimationFrame(eggRafRef.current);
eggRafRef.current = requestAnimationFrame(tick);
};
if (eggRafRef.current !== null) cancelAnimationFrame(eggRafRef.current);
eggRafRef.current = requestAnimationFrame(tick);
}, []);
},
[],
);
const onJackClick = useCallback(
(jack: JackId) => {
@@ -288,7 +328,13 @@ export default function Home() {
if (jack === expected) {
jackProgressRef.current += 1;
if (jackProgressRef.current >= JACK_EGG_SEQUENCE.length) {
triggerJackEasterEgg();
if (jackEggNextRef.current === 1) {
runJackEggAnimation("dance");
jackEggNextRef.current = 2;
} else {
runJackEggAnimation("wave");
jackEggNextRef.current = 1;
}
}
} else {
jackProgressRef.current = jack === JACK_EGG_SEQUENCE[0] ? 1 : 0;
@@ -299,7 +345,7 @@ export default function Home() {
jackProgressRef.current = 0;
}, JACK_SEQUENCE_TIMEOUT_MS);
},
[easterEggActive, triggerJackEasterEgg],
[easterEggActive, runJackEggAnimation],
);
useEffect(() => {
@@ -309,7 +355,7 @@ export default function Home() {
};
}, []);
if (!isConnected) return <ConnectScreen />;
if (!isConnected) return <ConnectionPlaceholder />;
const ds = deviceState!;
const inputLabel = INPUT_LABELS[ds.input] ?? "USB-C";
@@ -471,8 +517,8 @@ export default function Home() {
background: isHot ? "#ef4444" : i > 13 ? "#f59e0b" : "#00FFF6",
opacity: easterEggActive ? 0.95 : 0.7,
transition: easterEggActive
? "height 90ms ease-out, opacity 120ms ease-out"
: "height 200ms ease-out",
? "none"
: "height 200ms ease-out, opacity 200ms ease-out",
}}
/>
);
@@ -766,6 +812,15 @@ export default function Home() {
value={vuLabel}
onClick={() => setLocation("/vu")}
/>
<ListRow
icon={<Undo2 size={16} />}
label={homeText.backToLegacy ?? "返回旧版"}
onClick={() => {
const target = ip.trim();
if (!target) return;
window.location.href = `https://am.luxsinaudio.com/x8/i.html?ip=${encodeURIComponent(target)}#/home`;
}}
/>
</div>
</div>