feat: prevent text selection on add dsp page
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user