257 lines
8.3 KiB
TypeScript
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;
|