feat: prevent text selection on add dsp page

This commit is contained in:
2026-09-12 13:35:02 +02:00
parent 014ccf5197
commit 3793ee9f5b
3 changed files with 82 additions and 8 deletions
+11
View File
@@ -161,3 +161,14 @@
font-family: var(--font-mono); font-family: var(--font-mono);
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
} }
.add-dsp-form {
user-select: none;
-webkit-user-select: none;
}
.add-dsp-form input,
.add-dsp-form select {
user-select: text;
-webkit-user-select: text;
}
+68 -7
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useRef } from 'react';
import { DSP, DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types'; import { DSP, DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types';
import Button from '../../components/Button/Button'; import Button from '../../components/Button/Button';
import Card from '../../components/Card/Card'; import Card from '../../components/Card/Card';
@@ -18,12 +18,26 @@ type DSPForm = {
const DSP_TYPES = Object.keys(DSP_DEFAULT_PARAMETERS) as DSPTypes[]; 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({ function AddDSPForm({
onCancel, onCancel,
onAdd, onAdd,
}: { }: {
onCancel: () => void; onCancel: () => void;
onAdd: (d: Omit<DSP, 'status' | 'id' | 'state' | 'meters' | 'pollingPaused'>) => void; onAdd: (dsp: Omit<DSP, 'status' | 'id' | 'state' | 'meters' | 'pollingPaused'>) => void;
}) { }) {
const [form, setForm] = useState<DSPForm>({ const [form, setForm] = useState<DSPForm>({
name: '', name: '',
@@ -34,23 +48,51 @@ function AddDSPForm({
demo: false, demo: false,
}); });
const lastValidIp = useRef(form.ip);
const validIp = form.demo || isValidIPv4(form.ip);
const canSubmit = const canSubmit =
form.name.trim().length > 0 && form.name.trim().length > 0 &&
(form.demo || form.ip.trim().length > 0) && validIp &&
Number.isInteger(form.deviceId) && Number.isInteger(form.deviceId) &&
form.deviceId >= 1 && form.deviceId >= 1 &&
form.deviceId <= 255; 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() { function submit() {
if (!canSubmit) return; if (!canSubmit) return;
onAdd({ ...form }); onAdd({ ...form });
} }
return ( return (
<Card> <Card className="add-dsp-form">
<div className="add-dsp-form__header"> <div className="add-dsp-form__header">
<h1 className="add-dsp-form__title">Add DSP</h1> <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> <p className="add-dsp-form__hint">
Register a new device to control from this app.
</p>
</div> </div>
<div className="add-dsp-form__field"> <div className="add-dsp-form__field">
@@ -91,12 +133,26 @@ function AddDSPForm({
<div className="add-dsp-form__row"> <div className="add-dsp-form__row">
<div className="add-dsp-form__field"> <div className="add-dsp-form__field">
<label htmlFor="f-ip">IP address</label> <label htmlFor="f-ip">IP address</label>
<input <input
id="f-ip" id="f-ip"
className="mono" className="mono"
value={form.ip} value={form.ip}
disabled={form.demo} disabled={form.demo}
onChange={(e) => setForm({ ...form, ip: e.target.value })} 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>
@@ -121,7 +177,11 @@ function AddDSPForm({
id="f-demo" id="f-demo"
type="checkbox" type="checkbox"
checked={form.demo} checked={form.demo}
onChange={(e) => setForm({ ...form, demo: e.target.checked })} onChange={(e) => {
const demo = e.target.checked;
setForm({ ...form, demo });
}}
/> />
Demo mode (simulate device, no network connection) Demo mode (simulate device, no network connection)
</label> </label>
@@ -131,6 +191,7 @@ function AddDSPForm({
<Button variant="ghost" onClick={onCancel}> <Button variant="ghost" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" onClick={submit} disabled={!canSubmit}> <Button variant="primary" onClick={submit} disabled={!canSubmit}>
Add DSP Add DSP
</Button> </Button>
+2
View File
@@ -9,6 +9,8 @@
padding: var(--space-6); padding: var(--space-6);
box-sizing: border-box; box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
} }
.specs { .specs {