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:
@@ -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>
|
||||
@@ -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).
|
||||
*/
|
||||
@@ -87,7 +89,9 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
|
||||
"w-full min-w-0",
|
||||
showSplit
|
||||
? "flex h-dvh max-h-dvh flex-row overflow-hidden"
|
||||
: "min-h-dvh",
|
||||
: isLegacyBuild
|
||||
? "min-h-0"
|
||||
: "min-h-dvh",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@@ -97,7 +101,9 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
|
||||
? "h-full min-h-0 overflow-y-auto overflow-x-hidden"
|
||||
: isLgUp
|
||||
? "min-h-dvh overflow-x-hidden"
|
||||
: "min-h-dvh max-h-dvh overflow-x-hidden overflow-y-auto overscroll-y-contain [touch-action:pan-y]",
|
||||
: 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]",
|
||||
)}
|
||||
style={
|
||||
showSplit
|
||||
|
||||
@@ -157,6 +157,7 @@ export default function EQPage() {
|
||||
const [selectedCatalogTarget, setSelectedCatalogTarget] = useState<string>("");
|
||||
const [isConfirmingTarget, setIsConfirmingTarget] = useState(false);
|
||||
const [currentRawCurve, setCurrentRawCurve] = useState<number[] | null>(null);
|
||||
const [rawCurveLoading, setRawCurveLoading] = useState(false);
|
||||
const allowPeqRemoteSyncRef = useRef(false);
|
||||
const lastPeqCatalogSyncKeyRef = useRef("");
|
||||
const syncingHeadphoneRef = useRef(false);
|
||||
@@ -663,9 +664,13 @@ export default function EQPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const copyBrand = currentPeq?.brand?.trim() ?? "";
|
||||
const copyModel = currentPeq?.model?.trim() ?? "";
|
||||
const copyPayload: PeqChangePayload = {
|
||||
peqChange: {
|
||||
name: nextName,
|
||||
...(copyBrand ? { brand: copyBrand } : {}),
|
||||
...(copyModel ? { model: copyModel } : {}),
|
||||
filters: bands.map(bandToPeqFilter),
|
||||
autoPre: currentPeq?.autoPre ?? 0,
|
||||
preamp: currentPeq?.preamp ?? 0,
|
||||
@@ -704,6 +709,8 @@ export default function EQPage() {
|
||||
...prev,
|
||||
{
|
||||
name: nextName,
|
||||
...(addPresetMode === "copy" && copyBrand ? { brand: copyBrand } : {}),
|
||||
...(addPresetMode === "copy" && copyModel ? { model: copyModel } : {}),
|
||||
filters: localFilters,
|
||||
autoPre: localAutoPre,
|
||||
preamp: localPreamp,
|
||||
@@ -817,15 +824,8 @@ export default function EQPage() {
|
||||
|
||||
const loadRawCurveForPeq = useCallback(
|
||||
async (peq: { brand?: string; model?: string; name?: string } | undefined): Promise<number[] | null> => {
|
||||
let brand = peq?.brand?.trim() ?? "";
|
||||
let 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(" ");
|
||||
}
|
||||
const brand = peq?.brand?.trim() ?? "";
|
||||
const model = peq?.model?.trim() ?? "";
|
||||
if (!brand || !model) return null;
|
||||
|
||||
const modelCurve = await getModelCurve(brand, model);
|
||||
@@ -966,11 +966,20 @@ export default function EQPage() {
|
||||
syncingHeadphoneRef.current = true;
|
||||
setBandsForBothModes(nextBands);
|
||||
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;
|
||||
void (async () => {
|
||||
const raw = await loadRawCurveForPeq(peq as { brand?: string; model?: string });
|
||||
if (cancelled) return;
|
||||
setCurrentRawCurve(raw);
|
||||
setRawCurveLoading(false);
|
||||
renderCharts(nextBands, raw, false);
|
||||
})();
|
||||
requestAnimationFrame(() => {
|
||||
@@ -1463,6 +1472,7 @@ export default function EQPage() {
|
||||
<FreqChart
|
||||
bands={bands}
|
||||
rawCurve={currentRawCurve}
|
||||
rawCurveLoading={rawCurveLoading}
|
||||
selectedBand={selectedBand}
|
||||
abMode={abMode}
|
||||
onAbToggle={handleAbToggle}
|
||||
|
||||
@@ -135,7 +135,7 @@ function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: bool
|
||||
|
||||
// ── List row ──
|
||||
function ListRow({
|
||||
icon, label, value, onClick, toggle, checked, onToggle, thumbnailSrc,
|
||||
icon, label, value, onClick, toggle, checked, onToggle, thumbnail,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
@@ -144,8 +144,7 @@ function ListRow({
|
||||
toggle?: boolean;
|
||||
checked?: boolean;
|
||||
onToggle?: (v: boolean) => void;
|
||||
/** Optional small thumbnail shown between value text and chevron. */
|
||||
thumbnailSrc?: string;
|
||||
thumbnail?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="ios-list-row cursor-pointer" onClick={onClick}>
|
||||
@@ -159,13 +158,12 @@ function ListRow({
|
||||
{toggle ? (
|
||||
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
|
||||
) : (
|
||||
<div className="flex items-center gap-1.5">
|
||||
{thumbnailSrc && (
|
||||
<div className="flex items-center gap-1">
|
||||
{thumbnail && (
|
||||
<img
|
||||
src={thumbnailSrc}
|
||||
alt=""
|
||||
className="w-16 h-12 rounded-[8px] object-cover flex-shrink-0"
|
||||
style={{ border: "1px solid rgba(255,255,255,0.12)" }}
|
||||
src={thumbnail}
|
||||
alt={value ?? label}
|
||||
className="h-8 w-14 object-contain rounded bg-black/20 border border-white/10"
|
||||
/>
|
||||
)}
|
||||
{value && <span className="ios-row-value">{value}</span>}
|
||||
@@ -883,7 +881,7 @@ export default function Home() {
|
||||
icon={<Gauge size={16} />}
|
||||
label={homeText.vu ?? "VU表"}
|
||||
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")}
|
||||
/>
|
||||
<ListRow
|
||||
|
||||
@@ -5,9 +5,29 @@ import { buildPeqSvgCurveData, sampleCombinedPeqMagnitudeDb } from "@/lib/peqAud
|
||||
import { eqInterp } from "../eqFormatters";
|
||||
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 = {
|
||||
bands: EqBand[];
|
||||
rawCurve: number[] | null;
|
||||
rawCurveLoading?: boolean;
|
||||
selectedBand: number;
|
||||
abMode: "A" | "B";
|
||||
onAbToggle: (m: "A" | "B") => void;
|
||||
@@ -22,6 +42,7 @@ export type FreqChartProps = {
|
||||
export function FreqChart({
|
||||
bands,
|
||||
rawCurve,
|
||||
rawCurveLoading = false,
|
||||
selectedBand,
|
||||
abMode,
|
||||
onAbToggle,
|
||||
@@ -212,10 +233,13 @@ export function FreqChart({
|
||||
type="button"
|
||||
className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => toggleCurveVisibility("raw")}
|
||||
disabled={!hasRawCurve}
|
||||
disabled={rawCurveLoading || !hasRawCurve}
|
||||
>
|
||||
<div className="w-3 h-[2px] rounded" style={{ background: "#ffffff" }} />
|
||||
<span className="text-[10px]" style={{ color: "#ffffff", opacity: showRaw ? 1 : 0.35 }}>
|
||||
<LegendCurveIndicator loading={rawCurveLoading} color="#ffffff" />
|
||||
<span
|
||||
className="text-[10px]"
|
||||
style={{ color: "#ffffff", opacity: rawCurveLoading || showRaw ? 1 : 0.35 }}
|
||||
>
|
||||
Raw
|
||||
</span>
|
||||
</button>
|
||||
@@ -223,10 +247,13 @@ export function FreqChart({
|
||||
type="button"
|
||||
className="flex items-center gap-1.5 active:opacity-80 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
onClick={() => toggleCurveVisibility("equalized")}
|
||||
disabled={!hasEqualizedCurve}
|
||||
disabled={rawCurveLoading || !hasEqualizedCurve}
|
||||
>
|
||||
<div className="w-3 h-[2px] rounded" style={{ background: "#23d2fe" }} />
|
||||
<span className="text-[10px]" style={{ color: "#23d2fe", opacity: showEqualized ? 1 : 0.35 }}>
|
||||
<LegendCurveIndicator loading={rawCurveLoading} color="#23d2fe" />
|
||||
<span
|
||||
className="text-[10px]"
|
||||
style={{ color: "#23d2fe", opacity: rawCurveLoading || showEqualized ? 1 : 0.35 }}
|
||||
>
|
||||
Equalized
|
||||
</span>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user