Files
dsp-thomann-interface/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx
T

257 lines
8.3 KiB
TypeScript

import { useState } from 'react';
import { DSP408 } from '../../../../types';
import { Channel, InputSource, InputType, DiscreteFrequency } from '../../types';
import { SidebarId } from '../../constants';
import Button from '../../../../components/Button/Button';
import Card from '../../../../components/Card/Card';
import ChoiceSelect from '../../../../components/ChoiceSelect/ChoiceSelect';
import LinkChannelsModal from './LinkChannelsModal';
import CopyChannelModal from './CopyChannelModal';
import './MiscellaneousSection.css';
import { EyeIcon, EyeOffIcon } from '../../../../assets/icons/EyesIcons';
const INPUT_TYPES: readonly InputType[] = Object.values(InputType);
const DISCRETE_FREQUENCIES: readonly DiscreteFrequency[] = Object.values(DiscreteFrequency);
function MiscellaneousPanel({
dsp,
factoryReset,
copyChannel,
setInputSource,
linkChannels,
lockDevice,
setActiveSidebar,
}: {
dsp: DSP408;
factoryReset: (force: boolean) => Promise<boolean>;
copyChannel: (channel1: Channel, channel2: Channel) => Promise<boolean>;
setInputSource: (inputSource: InputSource) => Promise<boolean>;
linkChannels: (source: Channel, destinations: Channel[]) => Promise<boolean>;
lockDevice: (password: string) => Promise<boolean>;
setActiveSidebar: (id: SidebarId) => void;
}) {
const [isFactoryResetting, setIsFactoryResetting] = useState(false);
const [isResetConfirmOpen, setIsResetConfirmOpen] = useState(false);
const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
const [isCopyModalOpen, setIsCopyModalOpen] = useState(false);
const [inputSource, setInputSourceState] = useState<InputSource>({
type: InputType.Analog,
frequency: DiscreteFrequency.F20,
});
const [password, setPassword] = useState('');
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const handleInputTypeChange = async (type: InputType) => {
const nextSource: InputSource = {
type,
frequency: type === InputType.SineWave ? inputSource.frequency : DiscreteFrequency.F20,
};
setInputSourceState(nextSource);
await setInputSource(nextSource);
};
const handleFrequencyChange = async (frequency: DiscreteFrequency) => {
const nextSource: InputSource = {
...inputSource,
frequency,
};
setInputSourceState(nextSource);
await setInputSource(nextSource);
};
const handleFactoryReset = () => {
setIsResetConfirmOpen(true);
};
const confirmFactoryReset = async () => {
setIsResetConfirmOpen(false);
setIsFactoryResetting(true);
try {
await factoryReset(true);
} finally {
setIsFactoryResetting(false);
}
};
const handleLock = async () => {
if (!password) {
return;
}
const success = await lockDevice(password);
if (success) {
setPassword('');
setActiveSidebar('overview');
}
};
return (
<div className="miscellaneous-section">
<Card className="miscellaneous-panel">
{/* Input Source */}
<section className="miscellaneous-panel__section">
<div className="miscellaneous-panel__section-header">
<h2>Input Source</h2>
<p>Select the signal source used by the DSP.</p>
</div>
<div className="miscellaneous-panel__row">
<div className="miscellaneous-panel__field">
<label htmlFor="misc-input-type">Source</label>
<ChoiceSelect<InputType>
id="misc-input-type"
value={inputSource.type}
options={INPUT_TYPES}
onChange={handleInputTypeChange}
style={{ color: 'var(--font-primary)' }}
/>
</div>
{inputSource.type === InputType.SineWave && (
<div className="miscellaneous-panel__field">
<label htmlFor="misc-frequency">Frequency</label>
<ChoiceSelect<DiscreteFrequency>
id="misc-frequency"
value={inputSource.frequency}
options={DISCRETE_FREQUENCIES}
formatLabel={(frequency) => {
if (frequency === DiscreteFrequency.F31_5) {
return '31.5 Hz';
}
return `${frequency.slice(1)} Hz`;
}}
onChange={handleFrequencyChange}
style={{ color: 'var(--font-primary)' }}
/>
</div>
)}
</div>
</section>
{/* Channel Tools */}
<section className="miscellaneous-panel__section">
<div className="miscellaneous-panel__section-header">
<h2>Channel Tools</h2>
<p>Copy settings between channels or link multiple channels.</p>
</div>
<div className="miscellaneous-panel__actions">
<Button variant="outline" onClick={() => setIsCopyModalOpen(true)}>
Copy Channel
</Button>
<Button variant="outline" onClick={() => setIsLinkModalOpen(true)}>
Link Channels
</Button>
</div>
</section>
{/* Security */}
<section className="miscellaneous-panel__section">
<div className="miscellaneous-panel__section-header">
<h2>Device Security</h2>
<p>Lock access to the DSP.</p>
</div>
<div className="miscellaneous-panel__field">
<label htmlFor="misc-password">Password</label>
<div className="miscellaneous-panel__password-wrap">
<input
id="misc-password"
type={isPasswordVisible ? 'text' : 'password'}
maxLength={4}
value={password}
placeholder="Enter password"
onChange={(e) => {
const value = e.target.value;
if (/^[\x20-\x7E]*$/.test(value)) {
setPassword(value);
}
}}
/>
<button
type="button"
className="miscellaneous-panel__password-toggle"
onClick={() => setIsPasswordVisible((v) => !v)}
aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
aria-pressed={isPasswordVisible}
tabIndex={-1}
>
{isPasswordVisible ? <EyeOffIcon /> : <EyeIcon />}
</button>
</div>
</div>
<div className="miscellaneous-panel__actions">
<Button variant="outline" onClick={handleLock} disabled={password.length !== 4}>
Lock Device
</Button>
</div>
</section>
{/* Factory Reset */}
<section className="miscellaneous-panel__section miscellaneous-panel__section--danger">
<div className="miscellaneous-panel__section-header">
<h2>Factory Reset</h2>
<p>Restore the DSP to its factory configuration. This cannot be undone.</p>
</div>
<div className="miscellaneous-panel__actions">
<Button variant="outline" onClick={handleFactoryReset} disabled={isFactoryResetting}>
{isFactoryResetting ? 'Resetting...' : 'Factory Reset'}
</Button>
</div>
</section>
</Card>
{isLinkModalOpen && (
<LinkChannelsModal
dsp={dsp}
onClose={() => setIsLinkModalOpen(false)}
linkChannels={linkChannels}
/>
)}
{isCopyModalOpen && (
<CopyChannelModal onClose={() => setIsCopyModalOpen(false)} copyChannel={copyChannel} />
)}
{isResetConfirmOpen && (
<div className="confirm-modal-overlay" onClick={() => setIsResetConfirmOpen(false)}>
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
<h3>Factory Reset DSP?</h3>
<p>
This will restore the DSP to its factory configuration. This action cannot be undone.
</p>
<div className="confirm-modal__actions">
<Button variant="outline" onClick={() => setIsResetConfirmOpen(false)}>
Cancel
</Button>
<Button variant="primary" onClick={confirmFactoryReset}>
Factory Reset
</Button>
</div>
</div>
</div>
)}
</div>
);
}
export default MiscellaneousPanel;