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;