203 lines
5.0 KiB
TypeScript
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;
|