Files
dsp-thomann-interface/src/pages/AddDspPage/AddDspPage.tsx
T

203 lines
5.0 KiB
TypeScript

import { useState, useRef } from 'react';
import { DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types';
import Button from '../../components/Button/Button';
import Card from '../../components/Card/Card';
import ChoiceStepper from '../../components/ChoiceStepper/ChoiceStepper';
import ChoiceSelect from '../../components/ChoiceSelect/ChoiceSelect';
import './AddDspPage.css';
type DSPForm = {
name: string;
type: DSPTypes;
ip: string;
port: number;
deviceId: number;
demo: boolean;
};
const DSP_TYPES = Object.keys(DSP_DEFAULT_PARAMETERS) as DSPTypes[];
function isValidIPv4(value: string): boolean {
const parts = value.trim().split('.');
if (parts.length !== 4) return false;
return parts.every((part) => {
if (!/^\d+$/.test(part)) return false;
const n = Number(part);
return n >= 0 && n <= 255;
});
}
function AddDSPForm({
onCancel,
onAdd,
}: {
onCancel: () => void;
onAdd: (params: DSPForm) => void;
}) {
const [form, setForm] = useState<DSPForm>({
name: '',
type: 'DSP408',
ip: DSP_DEFAULT_PARAMETERS['DSP408'].ip,
port: DSP_DEFAULT_PARAMETERS['DSP408'].defaultPort,
deviceId: DSP_DEFAULT_PARAMETERS['DSP408'].device_id,
demo: false,
});
const lastValidIp = useRef(form.ip);
const validIp = form.demo || isValidIPv4(form.ip);
const canSubmit =
form.name.trim().length > 0 &&
validIp &&
Number.isInteger(form.deviceId) &&
form.deviceId >= 1 &&
form.deviceId <= 255;
function validateIp() {
if (form.demo) return;
const ip = form.ip.trim();
if (isValidIPv4(ip)) {
lastValidIp.current = ip;
if (ip !== form.ip) {
setForm((prev) => ({ ...prev, ip }));
}
return;
}
setForm((prev) => ({
...prev,
ip: lastValidIp.current,
}));
}
function submit() {
if (!canSubmit) return;
onAdd(form);
}
return (
<Card className="add-dsp-form">
<div className="add-dsp-form__header">
<h1 className="add-dsp-form__title">Add DSP</h1>
<p className="add-dsp-form__hint">Register a new device to control from this app.</p>
</div>
<div className="add-dsp-form__field">
<label htmlFor="f-name">Name</label>
<input
id="f-name"
placeholder="Main Room DSP"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
</div>
<div className="add-dsp-form__row">
<div className="add-dsp-form__field">
<label htmlFor="f-type">Model</label>
<ChoiceSelect
className=""
id="f-type"
value={form.type}
options={DSP_TYPES}
onChange={(type) => setForm({ ...form, type })}
/>
</div>
<div className="add-dsp-form__field">
<label htmlFor="f-deviceid">Device ID</label>
<ChoiceStepper
id="f-deviceid"
className="add-dsp-form__stepper"
value={form.deviceId}
min={1}
max={255}
formatLabel={(v) => String(v)}
onChange={(deviceId) => setForm({ ...form, deviceId })}
/>
</div>
</div>
<div className="add-dsp-form__row">
<div className="add-dsp-form__field">
<label htmlFor="f-ip">IP address</label>
<input
id="f-ip"
className="mono"
value={form.ip}
disabled={form.demo}
onChange={(e) =>
setForm((prev) => ({
...prev,
ip: e.target.value,
}))
}
onBlur={validateIp}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
validateIp();
e.currentTarget.blur();
}
}}
/>
</div>
<div className="add-dsp-form__field">
<label htmlFor="f-port">Port</label>
<ChoiceStepper
id="f-port"
className="add-dsp-form__stepper"
value={form.port}
min={1}
max={65535}
disabled={form.demo}
formatLabel={(v) => String(v)}
onChange={(port) => setForm({ ...form, port })}
/>
</div>
</div>
<div className="add-dsp-form__field add-dsp-form__field--checkbox">
<label htmlFor="f-demo" className="add-dsp-form__checkbox-label">
<input
id="f-demo"
type="checkbox"
checked={form.demo}
onChange={(e) => {
const demo = e.target.checked;
setForm({ ...form, demo });
}}
/>
Demo mode (simulate device, no network connection)
</label>
</div>
<div className="add-dsp-form__actions">
<Button variant="ghost" onClick={onCancel}>
Cancel
</Button>
<Button variant="primary" onClick={submit} disabled={!canSubmit}>
Add DSP
</Button>
</div>
</Card>
);
}
export default AddDSPForm;