From 00b6721c42d11f9d3831db838086a406a1377bb4 Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Sun, 13 Sep 2026 19:09:47 +0200 Subject: [PATCH] feat: add MiscellaneousPanel - need to implement UI --- src-tauri/src/commands.rs | 27 ++++ .../components/DSP408Panel/DSP408Panel.tsx | 103 ++++++------- .../MiscellaneousSection.css | 110 ++++++++++++++ .../MiscellaneousSection.tsx | 140 ++++++++++++++++++ src/features/dsp408/dsp408.ts | 44 ++++++ 5 files changed, 366 insertions(+), 58 deletions(-) create mode 100644 src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.css create mode 100644 src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 675f97f..7806ce2 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -224,7 +224,34 @@ pub async fn copy_channel( } // Command `0x2D` -- TODO +#[tauri::command] +pub async fn unlock_device( + state: State<'_, AppState>, + id: u64, + password: String, +) -> Result { + state.with_device_mut(id, |device| { + device + .dsp + .unlock_device(&password) + .map_err(|e| e.to_string()) + }) +} + // Command `0x2F` -- TODO +#[tauri::command] +pub async fn lock_device( + state: State<'_, AppState>, + id: u64, + password: String, +) -> Result { + state.with_device_mut(id, |device| { + device + .dsp + .lock_device(&password) + .map_err(|e| e.to_string()) + }) +} // Command `0x30` #[tauri::command] diff --git a/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx b/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx index 6e06080..2fe1f11 100644 --- a/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx +++ b/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx @@ -16,6 +16,7 @@ import { DSP408Api } from '../../dsp408'; import { DSP408 } from '../../../../types'; import { SidebarId } from '../../constants'; import './DSP408Panel.css'; +import MiscellaneousPanel from '../MiscellaneousSection/MiscellaneousSection'; function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) { const [activeSidebar, setActiveSidebar] = useState('overview'); @@ -81,7 +82,7 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) { { id: 'miscellaneous', label: 'Miscellaneous', icon: Out 1 }, ]; - const presets = dsp.state?.presets.names + const presets = dsp.state.presets.names ? [ { id: 'f00', name: 'Factory Preset' }, ...dsp.state.presets.names.map((name, index) => ({ @@ -107,6 +108,35 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) { setPresetLoading(false); }; + const renderChannel = (channel: { Input: InputChannel } | { Output: OutputChannel }) => ( + + ); + + const channelMap: Partial< + Record + > = { + ina: { Input: InputChannel.InA }, + inb: { Input: InputChannel.InB }, + inc: { Input: InputChannel.InC }, + ind: { Input: InputChannel.InD }, + + out1: { Output: OutputChannel.Out1 }, + out2: { Output: OutputChannel.Out2 }, + out3: { Output: OutputChannel.Out3 }, + out4: { Output: OutputChannel.Out4 }, + out5: { Output: OutputChannel.Out5 }, + out6: { Output: OutputChannel.Out6 }, + out7: { Output: OutputChannel.Out7 }, + out8: { Output: OutputChannel.Out8 }, + }; + const renderTab = () => { switch (activeSidebar) { case 'overview': @@ -147,68 +177,25 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) { case 'geq': return ; - case 'ina': + case 'miscellaneous': return ( - ); - case 'inb': - return ( - - ); - - case 'inc': - return ( - - ); - - case 'ind': - return ( - - ); - - case 'out1': - return ( - - ); - - default: - return null; + default: { + const channel = channelMap[activeSidebar]; + return channel ? renderChannel(channel) : null; + } } }; diff --git a/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.css b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.css new file mode 100644 index 0000000..e6595bf --- /dev/null +++ b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.css @@ -0,0 +1,110 @@ +.connection-section { + width: 100%; + height: 100%; + + display: flex; + justify-content: center; + align-items: center; + + user-select: none; + -webkit-user-select: none; +} + +.connection-panel { + width: 340px; + display: flex; + flex-direction: column; +} + +.connection-panel__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-3); + min-width: 0; +} + +.connection-panel__header-text { + flex: 1; + min-width: 0; +} + +.connection-panel__title { + margin: 0; + + font-size: var(--font-lg); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.connection-panel__type { + margin-top: var(--space-1); + + font-size: var(--font-sm); + color: var(--text-muted); + font-family: var(--font-mono); + letter-spacing: 0.03em; +} + +.connection-panel__specs { + margin: var(--space-5) 0; + + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.connection-panel__spec { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-3); +} + +.connection-panel__spec dt { + color: var(--text-muted); + font-size: var(--font-sm); +} + +.connection-panel__spec dd { + margin: 0; + color: var(--text-primary); +} + +.connection-panel__mono { + font-family: var(--font-mono); + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.connection-panel__actions { + margin-top: auto; + + display: flex; + justify-content: flex-end; +} + +.connection-panel__status { + display: flex; + align-items: center; + gap: var(--space-2); + + flex-shrink: 0; +} + +.connection-panel__led { + height: 14px; + aspect-ratio: 1 / 1; + + display: flex; + align-items: center; + justify-content: center; + + flex: 0 0 auto; +} diff --git a/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx new file mode 100644 index 0000000..d879dcd --- /dev/null +++ b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx @@ -0,0 +1,140 @@ +import { useState } from 'react'; + +import { DSP } from '../../../../types'; +import { Channel, InputSource } from '../../types'; +import Button from '../../../../components/Button/Button'; +import Card from '../../../../components/Card/Card'; +import Led from '../../../../components/Led/Led'; + +import './MiscellaneousSection.css'; + +function MiscellaneousPanel({ + dsp, + onConnect, + onDisconnect, + factoryReset, + copyChannel, + setInputSource, + linkChannels, + unlockDevice, + lockDevice, +}: { + dsp: DSP; + onConnect: () => void; + onDisconnect: () => void; + factoryReset: (force: boolean) => Promise; + copyChannel: (channel1: Channel, channel2: Channel) => Promise; + setInputSource: (inputSource: InputSource) => Promise; + linkChannels: (source: Channel, destinations: Channel[]) => Promise; + unlockDevice: (password: string) => Promise; + lockDevice: (password: string) => Promise; +}) { + const [isFactoryResetting, setIsFactoryResetting] = useState(false); + + const statusLabel = + dsp.status === 'connected' + ? 'Connected' + : dsp.status === 'connecting' + ? 'Connecting…' + : 'Disconnected'; + + const handleFactoryReset = async () => { + const confirmed = window.confirm( + 'Are you sure you want to factory reset this DSP? This cannot be undone.', + ); + + if (!confirmed) { + return; + } + + setIsFactoryResetting(true); + + try { + await factoryReset(true); + } finally { + setIsFactoryResetting(false); + } + }; + + return ( +
+ +
+
+

+ {dsp.name} +

+ +
{dsp.type}
+
+ +
+ + + + + + {statusLabel} + +
+
+ +
+
+
IP address
+
+ {dsp.ip} +
+
+ +
+
Port
+
{dsp.port}
+
+ +
+
Device ID
+
+ {dsp.deviceId || '—'} +
+
+
+ +
+ {dsp.status === 'connected' ? ( + + ) : ( + + )} +
+ +
+ +
+
+
+ ); +} + +export default MiscellaneousPanel; \ No newline at end of file diff --git a/src/features/dsp408/dsp408.ts b/src/features/dsp408/dsp408.ts index 2083b1a..7dfe6c1 100644 --- a/src/features/dsp408/dsp408.ts +++ b/src/features/dsp408/dsp408.ts @@ -844,5 +844,49 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { } }); }, + + async unlockDevice(password: string): Promise { + return withPollingPaused(dsp, setDsps, async () => { + try { + const success = await invoke('unlock_device', { + id: dsp.id, + password, + }); + + if (!success) { + return false; + } + + await updateDSPState(); + + return true; + } catch (err) { + notify(`Unlock failed: ${String(err)}`, 'error', dsp.name); + return false; + } + }); + }, + + async lockDevice(password: string): Promise { + return withPollingPaused(dsp, setDsps, async () => { + try { + const success = await invoke('lock_device', { + id: dsp.id, + password, + }); + + if (!success) { + return false; + } + + await updateDSPState(); + + return true; + } catch (err) { + notify(`Lock failed: ${String(err)}`, 'error', dsp.name); + return false; + } + }); + }, }; }