Update project dependencies and configurations; remove deprecated package settings and enhance legacy build support

- Upgrade package manager to pnpm@11.5.2 and remove patched dependencies from package.json.
- Update pnpm-lock.yaml to reflect new dependency versions and remove obsolete entries.
- Refactor Vite configuration to improve legacy build handling, including new plugins for legacy CSS management.
- Remove unused client/index.html file and adjust paths in Vite config for better project structure.
- Enhance DesktopAIShell and EQPage components to support legacy build conditions and improve loading states for raw curves.
This commit is contained in:
eafonyang
2026-06-09 14:24:30 +08:00
parent ccc404b75a
commit f3961cc72a
9 changed files with 2247 additions and 1380 deletions
-12
View File
@@ -1,12 +0,0 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X8 Controller</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+6
View File
@@ -65,6 +65,8 @@ function SplitDivider({
); );
} }
const isLegacyBuild = import.meta.env.VITE_X8_LEGACY === "1";
/** /**
* lg+: optional side-by-side AI panel with draggable divider. Mobile unchanged (router only). * lg+: optional side-by-side AI panel with draggable divider. Mobile unchanged (router only).
*/ */
@@ -87,6 +89,8 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
"w-full min-w-0", "w-full min-w-0",
showSplit showSplit
? "flex h-dvh max-h-dvh flex-row overflow-hidden" ? "flex h-dvh max-h-dvh flex-row overflow-hidden"
: isLegacyBuild
? "min-h-0"
: "min-h-dvh", : "min-h-dvh",
)} )}
> >
@@ -97,6 +101,8 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
? "h-full min-h-0 overflow-y-auto overflow-x-hidden" ? "h-full min-h-0 overflow-y-auto overflow-x-hidden"
: isLgUp : isLgUp
? "min-h-dvh overflow-x-hidden" ? "min-h-dvh overflow-x-hidden"
: isLegacyBuild
? "w-full overflow-x-hidden"
: "min-h-dvh max-h-dvh overflow-x-hidden overflow-y-auto overscroll-y-contain [touch-action:pan-y]", : "min-h-dvh max-h-dvh overflow-x-hidden overflow-y-auto overscroll-y-contain [touch-action:pan-y]",
)} )}
style={ style={
+19 -9
View File
@@ -157,6 +157,7 @@ export default function EQPage() {
const [selectedCatalogTarget, setSelectedCatalogTarget] = useState<string>(""); const [selectedCatalogTarget, setSelectedCatalogTarget] = useState<string>("");
const [isConfirmingTarget, setIsConfirmingTarget] = useState(false); const [isConfirmingTarget, setIsConfirmingTarget] = useState(false);
const [currentRawCurve, setCurrentRawCurve] = useState<number[] | null>(null); const [currentRawCurve, setCurrentRawCurve] = useState<number[] | null>(null);
const [rawCurveLoading, setRawCurveLoading] = useState(false);
const allowPeqRemoteSyncRef = useRef(false); const allowPeqRemoteSyncRef = useRef(false);
const lastPeqCatalogSyncKeyRef = useRef(""); const lastPeqCatalogSyncKeyRef = useRef("");
const syncingHeadphoneRef = useRef(false); const syncingHeadphoneRef = useRef(false);
@@ -663,9 +664,13 @@ export default function EQPage() {
return; return;
} }
const copyBrand = currentPeq?.brand?.trim() ?? "";
const copyModel = currentPeq?.model?.trim() ?? "";
const copyPayload: PeqChangePayload = { const copyPayload: PeqChangePayload = {
peqChange: { peqChange: {
name: nextName, name: nextName,
...(copyBrand ? { brand: copyBrand } : {}),
...(copyModel ? { model: copyModel } : {}),
filters: bands.map(bandToPeqFilter), filters: bands.map(bandToPeqFilter),
autoPre: currentPeq?.autoPre ?? 0, autoPre: currentPeq?.autoPre ?? 0,
preamp: currentPeq?.preamp ?? 0, preamp: currentPeq?.preamp ?? 0,
@@ -704,6 +709,8 @@ export default function EQPage() {
...prev, ...prev,
{ {
name: nextName, name: nextName,
...(addPresetMode === "copy" && copyBrand ? { brand: copyBrand } : {}),
...(addPresetMode === "copy" && copyModel ? { model: copyModel } : {}),
filters: localFilters, filters: localFilters,
autoPre: localAutoPre, autoPre: localAutoPre,
preamp: localPreamp, preamp: localPreamp,
@@ -817,15 +824,8 @@ export default function EQPage() {
const loadRawCurveForPeq = useCallback( const loadRawCurveForPeq = useCallback(
async (peq: { brand?: string; model?: string; name?: string } | undefined): Promise<number[] | null> => { async (peq: { brand?: string; model?: string; name?: string } | undefined): Promise<number[] | null> => {
let brand = peq?.brand?.trim() ?? ""; const brand = peq?.brand?.trim() ?? "";
let model = peq?.model?.trim() ?? ""; const model = peq?.model?.trim() ?? "";
// Some presets only have `name` (e.g. "Apple AirPods Pro") and miss explicit brand/model.
if ((!brand || !model) && peq?.name) {
const [first, ...rest] = peq.name.trim().split(/\s+/);
if (!brand && first) brand = first;
if (!model && rest.length > 0) model = rest.join(" ");
}
if (!brand || !model) return null; if (!brand || !model) return null;
const modelCurve = await getModelCurve(brand, model); const modelCurve = await getModelCurve(brand, model);
@@ -966,11 +966,20 @@ export default function EQPage() {
syncingHeadphoneRef.current = true; syncingHeadphoneRef.current = true;
setBandsForBothModes(nextBands); setBandsForBothModes(nextBands);
setSelectedBandByMode({ A: 0, B: 0 }); setSelectedBandByMode({ A: 0, B: 0 });
const shouldFetchModelCurve = !!(peq.brand?.trim() && peq.model?.trim());
if (shouldFetchModelCurve) {
setRawCurveLoading(true);
setCurrentRawCurve(null);
} else {
setRawCurveLoading(false);
}
let cancelled = false; let cancelled = false;
void (async () => { void (async () => {
const raw = await loadRawCurveForPeq(peq as { brand?: string; model?: string }); const raw = await loadRawCurveForPeq(peq as { brand?: string; model?: string });
if (cancelled) return; if (cancelled) return;
setCurrentRawCurve(raw); setCurrentRawCurve(raw);
setRawCurveLoading(false);
renderCharts(nextBands, raw, false); renderCharts(nextBands, raw, false);
})(); })();
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -1463,6 +1472,7 @@ export default function EQPage() {
<FreqChart <FreqChart
bands={bands} bands={bands}
rawCurve={currentRawCurve} rawCurve={currentRawCurve}
rawCurveLoading={rawCurveLoading}
selectedBand={selectedBand} selectedBand={selectedBand}
abMode={abMode} abMode={abMode}
onAbToggle={handleAbToggle} onAbToggle={handleAbToggle}
+8 -10
View File
@@ -135,7 +135,7 @@ function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: bool
// ── List row ── // ── List row ──
function ListRow({ function ListRow({
icon, label, value, onClick, toggle, checked, onToggle, thumbnailSrc, icon, label, value, onClick, toggle, checked, onToggle, thumbnail,
}: { }: {
icon: React.ReactNode; icon: React.ReactNode;
label: string; label: string;
@@ -144,8 +144,7 @@ function ListRow({
toggle?: boolean; toggle?: boolean;
checked?: boolean; checked?: boolean;
onToggle?: (v: boolean) => void; onToggle?: (v: boolean) => void;
/** Optional small thumbnail shown between value text and chevron. */ thumbnail?: string;
thumbnailSrc?: string;
}) { }) {
return ( return (
<div className="ios-list-row cursor-pointer" onClick={onClick}> <div className="ios-list-row cursor-pointer" onClick={onClick}>
@@ -159,13 +158,12 @@ function ListRow({
{toggle ? ( {toggle ? (
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} /> <IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
) : ( ) : (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1">
{thumbnailSrc && ( {thumbnail && (
<img <img
src={thumbnailSrc} src={thumbnail}
alt="" alt={value ?? label}
className="w-16 h-12 rounded-[8px] object-cover flex-shrink-0" className="h-8 w-14 object-contain rounded bg-black/20 border border-white/10"
style={{ border: "1px solid rgba(255,255,255,0.12)" }}
/> />
)} )}
{value && <span className="ios-row-value">{value}</span>} {value && <span className="ios-row-value">{value}</span>}
@@ -883,7 +881,7 @@ export default function Home() {
icon={<Gauge size={16} />} icon={<Gauge size={16} />}
label={homeText.vu ?? "VU表"} label={homeText.vu ?? "VU表"}
value={vuLabel} value={vuLabel}
thumbnailSrc={`${import.meta.env.BASE_URL}vu/vu${(ds.vu ?? 0) + 1}.png`} thumbnail={`${import.meta.env.BASE_URL}vu/vu${(ds.vu ?? 0) + 1}.png`}
onClick={() => setLocation("/vu")} onClick={() => setLocation("/vu")}
/> />
<ListRow <ListRow
+33 -6
View File
@@ -5,9 +5,29 @@ import { buildPeqSvgCurveData, sampleCombinedPeqMagnitudeDb } from "@/lib/peqAud
import { eqInterp } from "../eqFormatters"; import { eqInterp } from "../eqFormatters";
import type { EqBand, PeqEqUi } from "../types"; import type { EqBand, PeqEqUi } from "../types";
function LegendCurveIndicator({
loading,
color,
}: {
loading: boolean;
color: string;
}) {
if (loading) {
return (
<span
className="inline-block w-3 h-3 rounded-full border-2 animate-spin flex-shrink-0"
style={{ borderColor: `${color}40`, borderTopColor: color }}
aria-hidden="true"
/>
);
}
return <div className="w-3 h-[2px] rounded flex-shrink-0" style={{ background: color }} />;
}
export type FreqChartProps = { export type FreqChartProps = {
bands: EqBand[]; bands: EqBand[];
rawCurve: number[] | null; rawCurve: number[] | null;
rawCurveLoading?: boolean;
selectedBand: number; selectedBand: number;
abMode: "A" | "B"; abMode: "A" | "B";
onAbToggle: (m: "A" | "B") => void; onAbToggle: (m: "A" | "B") => void;
@@ -22,6 +42,7 @@ export type FreqChartProps = {
export function FreqChart({ export function FreqChart({
bands, bands,
rawCurve, rawCurve,
rawCurveLoading = false,
selectedBand, selectedBand,
abMode, abMode,
onAbToggle, onAbToggle,
@@ -212,10 +233,13 @@ export function FreqChart({
type="button" type="button"
className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed" className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed"
onClick={() => toggleCurveVisibility("raw")} onClick={() => toggleCurveVisibility("raw")}
disabled={!hasRawCurve} disabled={rawCurveLoading || !hasRawCurve}
>
<LegendCurveIndicator loading={rawCurveLoading} color="#ffffff" />
<span
className="text-[10px]"
style={{ color: "#ffffff", opacity: rawCurveLoading || showRaw ? 1 : 0.35 }}
> >
<div className="w-3 h-[2px] rounded" style={{ background: "#ffffff" }} />
<span className="text-[10px]" style={{ color: "#ffffff", opacity: showRaw ? 1 : 0.35 }}>
Raw Raw
</span> </span>
</button> </button>
@@ -223,10 +247,13 @@ export function FreqChart({
type="button" type="button"
className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed" className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed"
onClick={() => toggleCurveVisibility("equalized")} onClick={() => toggleCurveVisibility("equalized")}
disabled={!hasEqualizedCurve} disabled={rawCurveLoading || !hasEqualizedCurve}
>
<LegendCurveIndicator loading={rawCurveLoading} color="#23d2fe" />
<span
className="text-[10px]"
style={{ color: "#23d2fe", opacity: rawCurveLoading || showEqualized ? 1 : 0.35 }}
> >
<div className="w-3 h-[2px] rounded" style={{ background: "#23d2fe" }} />
<span className="text-[10px]" style={{ color: "#23d2fe", opacity: showEqualized ? 1 : 0.35 }}>
Equalized Equalized
</span> </span>
</button> </button>
+1 -9
View File
@@ -94,13 +94,5 @@
"vite-plugin-manus-runtime": "^0.0.57", "vite-plugin-manus-runtime": "^0.0.57",
"vitest": "^2.1.4" "vitest": "^2.1.4"
}, },
"packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af", "packageManager": "pnpm@11.5.2+sha512.71c631e382066efc25625d5cf029075de07b61b37f6e27350fbd84b1bda5864c8c1967adc280776b45c30a715c0359a3be08fef42d5bb09e2b99029979692916"
"pnpm": {
"patchedDependencies": {
"wouter@3.7.1": "patches/wouter@3.7.1.patch"
},
"overrides": {
"tailwindcss>nanoid": "3.3.7"
}
}
} }
+2133 -1313
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
packages:
- .
allowBuilds:
esbuild: true
overrides:
tailwindcss>nanoid: 3.3.7
+37 -19
View File
@@ -4,21 +4,19 @@ import path from "node:path";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import { defineConfig, type Plugin } from "vite"; import { defineConfig, type Plugin } from "vite";
const PROJECT_ROOT = import.meta.dirname;
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const tailwindcss3 = require("tailwindcss3"); const tailwindcss3 = require("tailwindcss3");
const autoprefixer = require("autoprefixer"); const autoprefixer = require("autoprefixer");
function legacyIndexRewritePlugin(): Plugin { function legacyIndexRewritePlugin(): Plugin {
// Vite dev server always serves /index.html for "/".
// For legacy dev, we must serve i.legacy.html instead, otherwise it loads
// modern main.tsx + index.css (Tailwind v4) and breaks the Tailwind v3 pipeline.
return { return {
name: "legacy-index-rewrite", name: "legacy-index-rewrite",
apply: "serve", apply: "serve",
configureServer(server) { configureServer(server) {
server.middlewares.use((req, _res, next) => { server.middlewares.use((req, _res, next) => {
const url = req.url ?? ""; const url = (req.url ?? "").split("?")[0];
if (url === "/" || url === "/index.html") { if (url === "/" || url === "/index.html" || url === "/i.modern.html") {
req.url = "/i.legacy.html"; req.url = "/i.legacy.html";
} }
next(); next();
@@ -27,38 +25,59 @@ function legacyIndexRewritePlugin(): Plugin {
}; };
} }
/** Dev MPA may discover modern CSS — stub them so Tailwind v3 postcss does not choke. */
function legacySkipModernCssPlugin(): Plugin {
return {
name: "legacy-skip-modern-css",
enforce: "pre",
apply: "serve",
load(id) {
const norm = id.replace(/\\/g, "/");
if (norm.endsWith("/client/src/index.css")) {
return "/* legacy dev: skip modern index.css */";
}
if (/node_modules\/tailwindcss\/index\.css/.test(norm)) {
return "/* legacy dev: skip tailwind v4 */";
}
},
};
}
export default defineConfig({ export default defineConfig({
// Legacy build targets Chrome 91 WebView-class browsers. plugins: [legacySkipModernCssPlugin(), legacyIndexRewritePlugin(), react(), jsxLocPlugin()],
// Uses Tailwind v3 via css.postcss (see postcss.legacy.config.cjs). define: {
plugins: [legacyIndexRewritePlugin(), react(), jsxLocPlugin()], "import.meta.env.VITE_X8_LEGACY": JSON.stringify("1"),
},
base: process.env.NODE_ENV === "production" ? "/x8/v2/legacy/" : "/", base: process.env.NODE_ENV === "production" ? "/x8/v2/legacy/" : "/",
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(import.meta.dirname, "client", "src"), "@": path.resolve(PROJECT_ROOT, "client", "src"),
"@shared": path.resolve(import.meta.dirname, "shared"), "@shared": path.resolve(PROJECT_ROOT, "shared"),
"@assets": path.resolve(import.meta.dirname, "attached_assets"), "@assets": path.resolve(PROJECT_ROOT, "attached_assets"),
}, },
dedupe: ["react", "react-dom"], dedupe: ["react", "react-dom"],
}, },
envDir: path.resolve(import.meta.dirname), envDir: path.resolve(PROJECT_ROOT),
root: path.resolve(import.meta.dirname, "client"), root: path.resolve(PROJECT_ROOT, "client"),
publicDir: path.resolve(import.meta.dirname, "client/public"), publicDir: path.resolve(PROJECT_ROOT, "client/public"),
css: { css: {
// Force PostCSS for Tailwind v3 (Chrome 91 legacy).
transformer: "postcss", transformer: "postcss",
postcss: { postcss: {
plugins: [tailwindcss3({ config: "./tailwind.legacy.config.cjs" }), autoprefixer()], plugins: [
tailwindcss3({ config: path.join(PROJECT_ROOT, "tailwind.legacy.config.cjs") }),
autoprefixer(),
],
}, },
}, },
build: { build: {
target: ["chrome91", "edge91"], target: ["chrome91", "edge91"],
cssTarget: "chrome91", cssTarget: "chrome91",
outDir: path.resolve(import.meta.dirname, "dist/legacy"), outDir: path.resolve(PROJECT_ROOT, "dist/legacy"),
emptyOutDir: true, emptyOutDir: true,
minify: "terser", minify: "terser",
rollupOptions: { rollupOptions: {
input: { input: {
i: path.resolve(import.meta.dirname, "client", "i.legacy.html"), i: path.resolve(PROJECT_ROOT, "client", "i.legacy.html"),
}, },
}, },
}, },
@@ -75,4 +94,3 @@ export default defineConfig({
}, },
}, },
}); });