229 lines
6.7 KiB
TypeScript
229 lines
6.7 KiB
TypeScript
import type { ConfigDeviceForm, ParameterSpec } from "../types";
|
|
import { PROTOCOL_SPECS } from "../constants/protocols";
|
|
|
|
interface ConfigFormProps {
|
|
deviceId?: number;
|
|
configForm: ConfigDeviceForm;
|
|
configFeedback: { type: "success" | "error"; message: string } | null;
|
|
isConfigMutating: boolean;
|
|
hasExistingConfig: boolean;
|
|
currentProtocolSpec: (typeof PROTOCOL_SPECS)[number] | undefined;
|
|
availableParameterSpecs: ParameterSpec[];
|
|
onUpdateField: (field: "name" | "protocol" | "model", value: string) => void;
|
|
onUpdateParameterRow: (
|
|
index: number,
|
|
field: "key" | "value",
|
|
value: string
|
|
) => void;
|
|
onAddParameterRow: () => void;
|
|
onRemoveParameterRow: (index: number) => void;
|
|
onSave: (deviceId?: number) => void;
|
|
onReset: (deviceId?: number) => void;
|
|
onCancel: () => void;
|
|
onDelete?: (deviceId: number) => void;
|
|
}
|
|
|
|
const isEmpty = (value: string) =>
|
|
value.trim() === "" || value === "null" || value === "0";
|
|
|
|
export const ConfigForm = ({
|
|
deviceId,
|
|
configForm,
|
|
configFeedback,
|
|
isConfigMutating,
|
|
hasExistingConfig,
|
|
currentProtocolSpec,
|
|
availableParameterSpecs,
|
|
onUpdateField,
|
|
onUpdateParameterRow,
|
|
onAddParameterRow,
|
|
onRemoveParameterRow,
|
|
onSave,
|
|
onReset,
|
|
onCancel,
|
|
onDelete,
|
|
}: ConfigFormProps) => {
|
|
const idSuffix = deviceId ? `-${deviceId}` : "-new";
|
|
|
|
return (
|
|
<div className="config-form">
|
|
<div className="config-form-header">
|
|
<h3>{deviceId ? `Configure Device #${deviceId}` : "Add New Device"}</h3>
|
|
{hasExistingConfig && deviceId && onDelete && (
|
|
<button
|
|
type="button"
|
|
className="danger"
|
|
onClick={() => onDelete(deviceId)}
|
|
disabled={isConfigMutating}
|
|
>
|
|
Delete
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<label className="input-label" htmlFor={`config-name${idSuffix}`}>
|
|
Name
|
|
</label>
|
|
<input
|
|
id={`config-name${idSuffix}`}
|
|
value={configForm.name}
|
|
onChange={(event) => onUpdateField("name", event.target.value)}
|
|
placeholder="Living room lamp"
|
|
/>
|
|
|
|
<label className="input-label" htmlFor={`config-protocol${idSuffix}`}>
|
|
Protocol
|
|
</label>
|
|
<select
|
|
id={`config-protocol${idSuffix}`}
|
|
value={configForm.protocol}
|
|
onChange={(event) => onUpdateField("protocol", event.target.value)}
|
|
>
|
|
<option value="">Select protocol...</option>
|
|
{PROTOCOL_SPECS.map((spec) => (
|
|
<option key={spec.name} value={spec.name}>
|
|
{spec.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
<label className="input-label" htmlFor={`config-model${idSuffix}`}>
|
|
Model {currentProtocolSpec?.models ? "" : "(optional)"}
|
|
</label>
|
|
{currentProtocolSpec?.models ? (
|
|
<select
|
|
id={`config-model${idSuffix}`}
|
|
value={configForm.model}
|
|
onChange={(event) => onUpdateField("model", event.target.value)}
|
|
>
|
|
<option value="">Select model...</option>
|
|
{currentProtocolSpec.models.map((model) => (
|
|
<option key={model.name} value={model.name}>
|
|
{model.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<input
|
|
id={`config-model${idSuffix}`}
|
|
value={configForm.model}
|
|
onChange={(event) => onUpdateField("model", event.target.value)}
|
|
placeholder="Optional model name"
|
|
disabled={!configForm.protocol}
|
|
/>
|
|
)}
|
|
|
|
<div className="parameter-header">
|
|
<h4>Parameters</h4>
|
|
<button
|
|
type="button"
|
|
className="outline"
|
|
onClick={onAddParameterRow}
|
|
disabled={isConfigMutating}
|
|
>
|
|
Add parameter
|
|
</button>
|
|
</div>
|
|
|
|
<div className="parameter-rows">
|
|
{configForm.parameters.map((parameter, index) => {
|
|
const paramSpec = availableParameterSpecs.find(
|
|
(spec) => spec.name === parameter.key
|
|
);
|
|
console.log(
|
|
"parameter:",
|
|
parameter,
|
|
"paramSpec:",
|
|
paramSpec,
|
|
!isEmpty(parameter.value)
|
|
);
|
|
if (!paramSpec && isEmpty(parameter.value)) {
|
|
return null;
|
|
}
|
|
return (
|
|
<div className="parameter-row" key={`parameter-${index}`}>
|
|
{availableParameterSpecs.length > 0 ? (
|
|
<select
|
|
value={parameter.key}
|
|
onChange={(event) =>
|
|
onUpdateParameterRow(index, "key", event.target.value)
|
|
}
|
|
disabled={!configForm.protocol}
|
|
>
|
|
<option value="">Select parameter...</option>
|
|
{availableParameterSpecs.map((spec) => (
|
|
<option key={spec.name} value={spec.name}>
|
|
{spec.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<input
|
|
value={parameter.key}
|
|
placeholder="Parameter key"
|
|
onChange={(event) =>
|
|
onUpdateParameterRow(index, "key", event.target.value)
|
|
}
|
|
disabled={!configForm.protocol}
|
|
/>
|
|
)}
|
|
<input
|
|
value={parameter.value}
|
|
placeholder={paramSpec ? paramSpec.description : "Value"}
|
|
onChange={(event) =>
|
|
onUpdateParameterRow(index, "value", event.target.value)
|
|
}
|
|
disabled={!parameter.key}
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="icon-btn subtle"
|
|
onClick={() => onRemoveParameterRow(index)}
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{configFeedback && (
|
|
<div className={`config-feedback ${configFeedback.type}`}>
|
|
{configFeedback.message}
|
|
</div>
|
|
)}
|
|
|
|
<div className="actions config-form-actions">
|
|
<button
|
|
type="button"
|
|
className="primary"
|
|
onClick={() => onSave(deviceId)}
|
|
disabled={isConfigMutating}
|
|
>
|
|
{hasExistingConfig
|
|
? "Save changes"
|
|
: deviceId
|
|
? "Create config"
|
|
: "Create device"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="ghost"
|
|
onClick={() => onReset(deviceId)}
|
|
disabled={isConfigMutating}
|
|
>
|
|
Reset
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="ghost"
|
|
onClick={onCancel}
|
|
disabled={isConfigMutating}
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|