/* ============================================================
DASHBOARD — Luxsin X9 Controller
Design: Main overview with volume knob, input/output, BT status
============================================================ */
import Layout from "@/components/Layout";
import GlassToggle from "@/components/GlassToggle";
import { useDevice } from "@/contexts/DeviceContext";
import {
INPUT_LABELS,
OUTPUT_LABELS,
} from "@/lib/luxsinApi";
import { cn } from "@/lib/utils";
import {
Activity,
Bluetooth,
ChevronDown,
ChevronUp,
Cpu,
Headphones,
Music,
SkipBack,
SkipForward,
Volume2,
VolumeX,
} from "lucide-react";
import { useCallback, useRef, useState } from "react";
// ---- Volume Knob ----
function VolumeKnob() {
const { deviceState, setVolume } = useDevice();
const volume = deviceState?.volume ?? 0;
const percent = volume / 200;
const isDragging = useRef(false);
const startY = useRef(0);
const startVol = useRef(0);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
isDragging.current = true;
startY.current = e.clientY;
startVol.current = volume;
document.addEventListener("mousemove", handleMouseMove as EventListener);
document.addEventListener("mouseup", handleMouseUp);
}, [volume]);
const handleMouseMove = useCallback((e: MouseEvent) => {
if (!isDragging.current) return;
const delta = (startY.current - e.clientY) * 1.5;
const newVol = Math.max(0, Math.min(200, Math.round(startVol.current + delta)));
setVolume(newVol);
}, [setVolume]);
const handleMouseUp = useCallback(() => {
isDragging.current = false;
document.removeEventListener("mousemove", handleMouseMove as EventListener);
document.removeEventListener("mouseup", handleMouseUp);
}, [handleMouseMove]);
const handleWheel = useCallback((e: React.WheelEvent) => {
e.preventDefault();
const delta = e.deltaY > 0 ? -2 : 2;
const newVol = Math.max(0, Math.min(200, volume + delta));
setVolume(newVol);
}, [volume, setVolume]);
// SVG arc calculation
const size = 160;
const cx = size / 2;
const cy = size / 2;
const r = 60;
const startAngle = 135;
const endAngle = 405; // 270 degree sweep
const sweepAngle = (endAngle - startAngle) * percent;
const polarToXY = (angle: number, radius: number) => {
const rad = ((angle - 90) * Math.PI) / 180;
return { x: cx + radius * Math.cos(rad), y: cy + radius * Math.sin(rad) };
};
const arcPath = (start: number, end: number, radius: number) => {
const s = polarToXY(start, radius);
const e = polarToXY(end, radius);
const large = end - start > 180 ? 1 : 0;
return `M ${s.x} ${s.y} A ${radius} ${radius} 0 ${large} 1 ${e.x} ${e.y}`;
};
const thumbPos = polarToXY(startAngle + sweepAngle, r);
return (
{/* Center display */}
{/* Volume controls */}
{Math.round(percent * 100)}%
);
}
// ---- Input Selector ----
function InputSelector() {
const { deviceState, setInput } = useDevice();
const current = deviceState?.input ?? 0;
const inputIcons: Record = {
0: USB,
1: C,
2: CO,
3: OPT,
4: ,
5: ARC,
6: RCA,
};
return (
Input Source
{INPUT_LABELS.map((label, i) => (
))}
);
}
// ---- Output Selector ----
function OutputSelector() {
const { deviceState, setOutput } = useDevice();
const current = deviceState?.output ?? 0;
const outputIcons = [
XLR,
RCA,
,
BOTH,
];
return (
Output
{OUTPUT_LABELS.map((label, i) => (
))}
);
}
// ---- BT Player ----
function BluetoothPlayer() {
const { deviceState, updateSetting } = useDevice();
if (!deviceState) return null;
const { bt_status, bt_title, bt_artist, bt_srcname } = deviceState;
const isPlaying = bt_status === 1;
const isConnected = bt_status > 0;
return (
Bluetooth
{isConnected &&
}
{isConnected ? (
<>
{bt_title || "Unknown Track"}
{bt_artist || bt_srcname}
>
) : (
)}
);
}
// ---- Status Cards ----
function StatusCards() {
const { deviceState, updateSetting } = useDevice();
if (!deviceState) return null;
const cards = [
{
label: "DSP",
value: deviceState.dsp_enable === 0 ? "Enabled" : "Disabled",
active: deviceState.dsp_enable === 0,
icon: ,
toggle: () => updateSetting({ dsp_enable: deviceState.dsp_enable === 0 ? 1 : 0 }),
},
{
label: "Audio Out",
value: deviceState.audio_enable === 1 ? "Enabled" : "Muted",
active: deviceState.audio_enable === 1,
icon: deviceState.audio_enable === 1 ? : ,
toggle: () => updateSetting({ audio_enable: deviceState.audio_enable === 1 ? 0 : 1 }),
},
{
label: "PEQ",
value: deviceState.peqEnable === 1 ? "Enabled" : "Disabled",
active: deviceState.peqEnable === 1,
icon: ,
toggle: () => updateSetting({ peqEnable: deviceState.peqEnable === 1 ? 0 : 1 }),
},
];
return (
{cards.map((card) => (
))}
);
}
// ---- VU Meter Visual ----
function VuMeterDisplay() {
const { deviceState, updateSetting } = useDevice();
if (!deviceState) return null;
const { vu, vu_count } = deviceState;
const bars = 12;
return (
{Array.from({ length: vu_count || 5 }, (_, i) => (
))}
{/* Animated VU bars */}
{Array.from({ length: bars }, (_, i) => {
const height = Math.random() * 100;
const isHot = i >= bars - 3;
return (
= bars - 5
? "rgba(255,200,0,0.7)"
: "rgba(0,255,246,0.6)",
boxShadow: isHot
? "0 0 4px rgba(255,80,80,0.5)"
: "0 0 4px rgba(0,255,246,0.3)",
animation: `vu-pulse ${0.3 + Math.random() * 0.4}s ease-in-out infinite alternate`,
animationDelay: `${i * 0.05}s`,
}}
/>
);
})}
);
}
// ---- Balance Slider ----
function BalanceControl() {
const { deviceState, setBalance } = useDevice();
const balance = deviceState?.balance ?? 0;
return (
L/R Balance
{balance === 0 ? "Center" : balance > 0 ? `R+${balance}` : `L${balance}`}
L
setBalance(parseInt(e.target.value))}
className="flex-1"
style={{
background: `linear-gradient(to right,
rgba(0,255,246,0.3) 0%,
rgba(0,255,246,0.3) ${((balance + 10) / 20) * 100}%,
rgba(255,255,255,0.08) ${((balance + 10) / 20) * 100}%,
rgba(255,255,255,0.08) 100%)`
}}
/>
R
);
}
// ---- Device Info ----
function DeviceInfo() {
const { deviceState } = useDevice();
if (!deviceState) return null;
return (
Device Info
{[
{ label: "Model", value: deviceState.device },
{ label: "Firmware", value: deviceState.version },
{ label: "MAC", value: deviceState.mac },
{ label: "Format", value: deviceState.audioFormat },
].map(({ label, value }) => (
{label}
{value || "—"}
))}
);
}
// ---- Main Dashboard ----
export default function Dashboard() {
const { deviceState } = useDevice();
return (
{/* Hero row: Volume + Input/Output */}
{/* Volume Knob Card */}
{/* Input / Output */}
{/* Status + BT row */}
{/* VU + Device Info */}
);
}