This commit is contained in:
eafonyang
2026-07-17 16:47:19 +08:00
parent 4f3427b44f
commit 5696f8233c
27 changed files with 35 additions and 174 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller</title>
<title>Luxsin B9 Controller</title>
<style>
body { margin:0; display:flex; align-items:center; justify-content:center; min-height:100vh; background:#000; color:rgba(255,255,255,.5); font-family:-apple-system,sans-serif; }
.spinner { width:24px; height:24px; border:3px solid rgba(255,255,255,.15); border-top-color:#00FFF6; border-radius:50%; animation:spin .8s linear infinite; margin-right:12px; }
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller (Legacy)</title>
<title>Luxsin B9 Controller (Legacy)</title>
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller</title>
<title>Luxsin B9 Controller</title>
</head>
<body>
<div id="root"></div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 90 KiB

-2
View File
@@ -11,7 +11,6 @@ import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
import Dashboard from "./pages/Dashboard";
import AudioPage from "./pages/AudioPage";
import BluetoothPage from "./pages/BluetoothPage";
import SystemPage from "./pages/SystemPage";
import IOPage from "./pages/IOPage";
import VUPage from "./pages/VUPage";
@@ -61,7 +60,6 @@ function AppRouter() {
<Route path="/audio" component={AudioPage} />
<Route path="/eq" component={EQPageRoute} />
<Route path="/effects" component={EffectsPageRoute} />
<Route path="/bluetooth" component={BluetoothPage} />
<Route path="/system" component={SystemPage} />
<Route path="/io" component={IOPage} />
<Route path="/vu" component={VUPage} />
-2
View File
@@ -5,7 +5,6 @@
import { useDevice } from "@/contexts/DeviceContext";
import { cn } from "@/lib/utils";
import {
Bluetooth,
ChevronRight,
Gauge,
Layers,
@@ -27,7 +26,6 @@ const NAV_ITEMS: NavItem[] = [
{ path: "/audio", icon: Volume2, label: "Audio" },
{ path: "/eq", icon: Sliders, label: "Parametric EQ" },
{ path: "/effects", icon: Layers, label: "Effects" },
{ path: "/bluetooth", icon: Bluetooth, label: "Bluetooth" },
{ path: "/system", icon: Settings, label: "System" },
];
+1 -1
View File
@@ -257,7 +257,7 @@ export interface PeqSoftPayload {
// ============================================================
// Input/Output Labels
// ============================================================
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA"];
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS", "RCA"];
export const OUTPUT_LABELS = ["XLR", "RCA", "Headset", "XLR/RCA"];
/** Device `input` index for Bluetooth source. */
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "I/O",
"inputSection": "Eingang",
"outputSection": "Ausgang",
"inputOptions": ["USB-B", "USB-C", "Koaxial", "Optisch", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
"inputOptions": ["USB-B", "USB-C", "Koaxial", "Optisch", "Bluetooth", "IIS", "RCA", "USB Driver"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Kopfhörer"],
"input": {
"coaxial": "KOAXIAL",
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "I/O",
"inputSection": "Input",
"outputSection": "Output",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS", "RCA", "USB Driver"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Headset"],
"input": {
"coaxial": "COAXIAL",
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "E/S",
"inputSection": "Entrada",
"outputSection": "Salida",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Óptico", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Óptico", "Bluetooth", "IIS", "RCA", "USB Driver"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Auriculares"],
"input": {
"coaxial": "COAXIAL",
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "E/S",
"inputSection": "Entrée",
"outputSection": "Sortie",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optique", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optique", "Bluetooth", "IIS", "RCA", "USB Driver"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Casque"],
"input": {
"coaxial": "COAXIAL",
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "輸入/輸出",
"inputSection": "輸入",
"outputSection": "輸出",
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "HDMI-EARC", "類比RCA", "USB隨身碟"],
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "IIS", "類比RCA", "USB隨身碟"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳機"],
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "類比RCA" },
"output": { "headset": "耳機" }
+1 -1
View File
@@ -55,7 +55,7 @@
"pageTitle": "输入输出",
"inputSection": "输入",
"outputSection": "输出",
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "模拟RCA", "U盘"],
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "模拟RCA", "U盘"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳机"],
"input": { "coaxial": "同轴", "optical": "光纤", "bluetooth": "蓝牙", "rca": "模拟RCA" },
"output": { "headset": "耳机" }
+23
View File
@@ -4,6 +4,7 @@
Sections:
- 平衡与灵敏度: 左右平衡, VU灵敏度, 模拟输入增益 sliders
- 输出特性: 滤波特性, 耳机增益, 音量幅度
- IIS设置: IIS静音电平, IIS模式
- 系统与接口: 开机音量, XLR极性
All list rows → navigate to /select page
============================================================ */
@@ -107,6 +108,7 @@ type LocaleDac = {
dac?: {
sectionBalanceSensitivity?: string;
sectionOutput?: string;
sectionIisSettings?: string;
sectionSystemInterface?: string;
balance?: string;
sensitivity?: string;
@@ -119,6 +121,8 @@ type LocaleDac = {
volumeStep?: string;
bootVolume?: string | { label?: string; options?: Array<{ index: number; label: string }> };
xlr?: { label?: string; options?: Array<{ index: number; label: string }> };
mutePolar?: { label?: string; options?: Array<{ index: number; label: string }> };
IISMode?: { label?: string; options?: Array<{ index: number; label: string }> };
};
home?: { audioSet?: string };
};
@@ -222,6 +226,8 @@ export default function AudioPage() {
const stepIdx = ds?.soundStep ?? 1;
const filterIdx = ds?.pcm ?? 0;
const xlrIdx = ds?.xlr ?? 0;
const mutePolarIdx = (ds as ({ hdmimutepolar?: number } | null))?.hdmimutepolar ?? 0;
const iisModeIdx = (ds as ({ hdmiType?: number } | null))?.hdmiType ?? 0;
const bootSoundVal = normalizeBootSound(localBootSound);
const firmwareVersion = parseFirmwareVersion(ds?.version);
const bootSoundMaxIdx = getBootSoundMaxIndex(firmwareVersion);
@@ -233,6 +239,8 @@ export default function AudioPage() {
const DAC_VOLUME_DIRECT_OPTIONS = optionLabels(dacText.dacVolumeDirect?.options, ["关闭", "0dB", "-12dB"]);
const DAC_ARC_OPTIONS = optionLabels(dacText.dacArc?.options, ["ARC", "EARC"]);
const XLR_OPTIONS = optionLabels(dacText.xlr?.options, ["正常", "反转"]);
const MUTE_POLAR_OPTIONS = optionLabels(dacText.mutePolar?.options, ["低电平", "高电平"]);
const IIS_MODE_OPTIONS = optionLabels(dacText.IISMode?.options, ["模式1", "模式2", "模式3", "模式4", "模式5", "模式6", "模式7", "模式8"]);
const pageTitle = usePageTitleWithConnection(localeData.home?.audioSet ?? "Audio");
@@ -380,6 +388,21 @@ export default function AudioPage() {
/>
</div>
{/* ── IIS 设置 ── */}
<SectionHeader title={dacText.sectionIisSettings ?? "IIS设置"} />
<div className="ios-list-group">
<ListRow
label={dacText.mutePolar?.label ?? "IIS 静音电平"}
value={MUTE_POLAR_OPTIONS[mutePolarIdx] ?? MUTE_POLAR_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.mutePolar?.label ?? "选择 IIS 静音电平", MUTE_POLAR_OPTIONS, mutePolarIdx, "mutePolar")}
/>
<ListRow
label={dacText.IISMode?.label ?? "IIS 模式"}
value={IIS_MODE_OPTIONS[iisModeIdx] ?? IIS_MODE_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.IISMode?.label ?? "选择 IIS 模式", IIS_MODE_OPTIONS, iisModeIdx, "IISMode")}
/>
</div>
{/* ── 系统与接口 ── */}
<SectionHeader title={dacText.sectionSystemInterface ?? "系统与接口"} />
<div className="ios-list-group">
-148
View File
@@ -1,148 +0,0 @@
/* ============================================================
BLUETOOTH PAGE — BT status, transport controls
============================================================ */
import Layout from "@/components/Layout";
import { useDevice } from "@/contexts/DeviceContext";
import { cn } from "@/lib/utils";
import { BT_STATUS_LABELS } from "@/lib/luxsinApi";
import { Bluetooth, Music, SkipBack, SkipForward, Volume2 } from "lucide-react";
export default function BluetoothPage() {
const { deviceState, updateSetting } = useDevice();
if (!deviceState) {
return (
<Layout title="Bluetooth" subtitle="Bluetooth audio source">
<div className="glass-card p-8 text-center text-white/30">Connect to device</div>
</Layout>
);
}
const { bt_status, bt_title, bt_artist, bt_srcname } = deviceState;
const isPlaying = bt_status === 1;
const isConnected = bt_status > 0;
return (
<Layout title="Bluetooth" subtitle="Bluetooth audio source">
<div className="max-w-2xl mx-auto space-y-4">
{/* Status card */}
<div className={cn(
"glass-card p-6 animate-fade-in-up transition-all duration-300",
isConnected && "glass-cyan"
)}>
<div className="flex items-center gap-4 mb-6">
<div className={cn(
"w-14 h-14 rounded-2xl flex items-center justify-center",
isConnected ? "glass-cyan" : "bg-white/5"
)}>
<Bluetooth size={24} className={isConnected ? "text-[#00FFF6]" : "text-white/30"} />
</div>
<div>
<h2 className={cn(
"text-lg font-bold",
isConnected ? "text-[#00FFF6]" : "text-white/50"
)} style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
{isConnected ? bt_srcname || "Connected" : "Not Connected"}
</h2>
<p className="text-white/40 text-sm">{BT_STATUS_LABELS[bt_status]}</p>
</div>
{isConnected && <div className="ml-auto dot-active" />}
</div>
{isConnected ? (
<>
{/* Track info */}
<div className="glass-card p-4 mb-5 rounded-[16px]">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-[10px] bg-white/5 flex items-center justify-center flex-shrink-0">
<Music size={16} className="text-white/30" />
</div>
<div className="flex-1 min-w-0">
<p className="text-white font-semibold text-sm truncate"
style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
{bt_title || "Unknown Track"}
</p>
<p className="text-white/50 text-xs truncate mt-0.5">
{bt_artist || "Unknown Artist"}
</p>
</div>
{isPlaying && (
<div className="flex items-end gap-0.5 h-5 flex-shrink-0">
{[1, 2, 3, 4].map(i => (
<div
key={i}
className="w-1 rounded-sm bg-[#00FFF6]"
style={{
height: `${30 + Math.random() * 70}%`,
animation: `vu-pulse ${0.3 + i * 0.1}s ease-in-out infinite alternate`,
}}
/>
))}
</div>
)}
</div>
</div>
{/* Transport controls */}
<div className="flex items-center justify-center gap-6">
<button
onClick={() => updateSetting({ bt_next: 0 })}
className="btn-glass w-12 h-12 flex items-center justify-center"
>
<SkipBack size={18} className="text-white/60" />
</button>
<button
onClick={() => updateSetting({ bt_play: 1 })}
className={cn(
"w-16 h-16 rounded-full flex items-center justify-center transition-all duration-200",
isPlaying ? "btn-cyan" : "btn-glass"
)}
style={isPlaying ? {
boxShadow: "0 0 30px rgba(0,255,246,0.4)"
} : {}}
>
{isPlaying ? (
<div className="flex gap-1">
<div className="w-1.5 h-5 bg-current rounded-sm" />
<div className="w-1.5 h-5 bg-current rounded-sm" />
</div>
) : (
<div className="w-0 h-0 border-l-[14px] border-l-current border-y-[9px] border-y-transparent ml-1" />
)}
</button>
<button
onClick={() => updateSetting({ bt_next: 1 })}
className="btn-glass w-12 h-12 flex items-center justify-center"
>
<SkipForward size={18} className="text-white/60" />
</button>
</div>
</>
) : (
<div className="flex flex-col items-center py-8 text-white/30">
<Bluetooth size={40} className="mb-3 opacity-20" />
<p className="text-sm">No Bluetooth device connected</p>
<p className="text-xs mt-1 opacity-60">Switch input to Bluetooth to use this feature</p>
</div>
)}
</div>
{/* Volume info */}
{isConnected && (
<div className="glass-card p-4 flex items-center gap-3 animate-fade-in-up" style={{ animationDelay: "60ms" }}>
<Volume2 size={14} className="text-[#00FFF6]" />
<span className="text-white/60 text-sm flex-1">Current Volume</span>
<span className="text-[#00FFF6] font-bold text-lg"
style={{ fontFamily: "'JetBrains Mono', monospace" }}>
{deviceState.volume}
</span>
<span className="text-white/30 text-xs">/ 200</span>
</div>
)}
</div>
</Layout>
);
}
+1 -11
View File
@@ -36,7 +36,6 @@ import {
Sliders,
Activity,
Gauge,
Undo2,
Bluetooth,
SkipBack,
SkipForward,
@@ -635,7 +634,7 @@ export default function Home() {
</div>
{/* Brand label */}
<div className="absolute top-5 left-8">
<span className="text-[11px] font-bold tracking-widest text-white/30">LUXSIN</span>
<span className="text-[11px] font-bold tracking-widest text-white/30">{deviceState?.device ?? "LUXSIN"}</span>
</div>
</div>
</div>
@@ -965,15 +964,6 @@ export default function Home() {
thumbnail={`${import.meta.env.BASE_URL}images/vu/vu${(ds.vu ?? 0) + 1}.png`}
onClick={() => setLocation("/vu")}
/>
<ListRow
icon={<Undo2 size={18} />}
label={homeText.backToLegacy ?? "返回旧版"}
onClick={() => {
const target = ip.trim();
if (!target) return;
window.location.href = `https://am.luxsinaudio.com/i.html?ip=${encodeURIComponent(target)}`;
}}
/>
</div>
</div>
+1 -1
View File
@@ -164,7 +164,7 @@ const OUTPUT_OPTION_DEFS: IOOptionDef[] = [
},
];
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "RCA", "USB Driver"];
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "RCA", "USB Driver"];
const FALLBACK_OUTPUT_LABELS = ["XLR", "RCA", "XLR/RCA", "耳机"];
function withLabels(defs: IOOptionDef[], labels: string[] | undefined, fallback: string[]): IOOption[] {