diff --git a/client/i.html b/client/i.html new file mode 100644 index 0000000..ed884c3 --- /dev/null +++ b/client/i.html @@ -0,0 +1,12 @@ + + + + + + Luxsin X8 Controller + + +
+ + + diff --git a/client/src/components/ConnectScreen.tsx b/client/src/components/ConnectScreen.tsx index 5e855d4..0324cd0 100644 --- a/client/src/components/ConnectScreen.tsx +++ b/client/src/components/ConnectScreen.tsx @@ -11,9 +11,11 @@ export default function ConnectScreen() { const [inputIp, setInputIp] = useState(ip); const handleConnect = async () => { - setIp(inputIp); + const targetIp = inputIp.trim(); + if (!targetIp) return; + setIp(targetIp); setDemoMode(false); - await connect(); + await connect(false, targetIp); }; const handleDemo = async () => { diff --git a/client/src/contexts/DeviceContext.tsx b/client/src/contexts/DeviceContext.tsx index f8eac7d..d6ae6c7 100644 --- a/client/src/contexts/DeviceContext.tsx +++ b/client/src/contexts/DeviceContext.tsx @@ -20,7 +20,7 @@ interface DeviceContextType { peqState: PeqState | null; lastUpdated: Date | null; error: string | null; - connect: (forceDemo?: boolean) => Promise; + connect: (forceDemo?: boolean, connectIp?: string) => Promise; disconnect: () => void; refresh: () => Promise; api: LuxsinAPI | null; @@ -62,8 +62,9 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) { lastMsgCountRef.current = state.msgCount; }, []); - const connect = useCallback(async (forceDemo?: boolean) => { + const connect = useCallback(async (forceDemo?: boolean, connectIp?: string) => { const useDemo = forceDemo !== undefined ? forceDemo : isDemoMode; + const targetIp = (connectIp ?? ip).trim(); // Demo mode — use mock data immediately, no network call if (useDemo) { setIsConnecting(true); @@ -80,7 +81,7 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) { setIsConnecting(true); setError(null); try { - const newApi = new LuxsinAPI(ip); + const newApi = new LuxsinAPI(targetIp); // Fetch device info from http://{ip}/dev/info.cgi?action=syncData // The response is encoded with custom base64, needs decoding const state = await newApi.getDeviceState(); diff --git a/client/src/lib/luxsinApi.ts b/client/src/lib/luxsinApi.ts index 0262fdb..6bcb98b 100644 --- a/client/src/lib/luxsinApi.ts +++ b/client/src/lib/luxsinApi.ts @@ -165,6 +165,17 @@ export const FILTER_TYPE_LABELS = ["Low Pass", "High Pass", "Band Pass", "Notch" export const DAC_ARC_LABELS = ["ARC", "eARC"]; export const BT_STATUS_LABELS = ["Disconnected", "Playing", "Paused"]; +function resolveDeviceProtocol(): "http" | "https" { + if (typeof window !== "undefined" && window.location.protocol === "https:") { + return "https"; + } + return "http"; +} + +function normalizeDeviceHost(input: string): string { + return input.trim().replace(/^https?:\/\//i, "").replace(/\/+$/, ""); +} + // ============================================================ // API Class // ============================================================ @@ -172,7 +183,9 @@ export class LuxsinAPI { private baseUrl: string; constructor(ip: string) { - this.baseUrl = `http://${ip}`; + const host = normalizeDeviceHost(ip); + const protocol = resolveDeviceProtocol(); + this.baseUrl = `${protocol}://${host}`; } async getDeviceState(): Promise { diff --git a/client/src/pages/VUPage.tsx b/client/src/pages/VUPage.tsx index 46ac04e..6fb68b2 100644 --- a/client/src/pages/VUPage.tsx +++ b/client/src/pages/VUPage.tsx @@ -19,7 +19,7 @@ interface VUStyle { const VU_STYLES: VUStyle[] = Array.from({ length: 16 }, (_, i) => ({ index: i, name: `VU ${i + 1}`, - preview: `/vu/vu${i + 1}.png`, + preview: `${import.meta.env.BASE_URL}vu/vu${i + 1}.png`, })); export default function VUPage() { diff --git a/vite.config.ts b/vite.config.ts index a4e58ca..1735015 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -179,6 +179,9 @@ export default defineConfig({ }, }, rollupOptions: { + input: { + i: path.resolve(import.meta.dirname, "client", "i.html"), + }, output: { manualChunks(id) { if (!id.includes("node_modules")) return;