config UI updates
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Power, RefreshCw, Save, X } from "lucide-react";
|
||||
import { Pencil, Power, RefreshCw, Save, X } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { fetchJson, postJsonResult } from "../api/controlApi";
|
||||
@@ -50,7 +50,15 @@ function TextField({ config, label, path, setConfig }) {
|
||||
);
|
||||
}
|
||||
|
||||
function InputDeviceField({ config, ndiSources, ndiSourcesBusy, onRefreshNdiSources, setConfig }) {
|
||||
function InputDeviceField({
|
||||
config,
|
||||
manualOpen,
|
||||
ndiSources,
|
||||
ndiSourcesBusy,
|
||||
onManualOpenChange,
|
||||
onRefreshNdiSources,
|
||||
setConfig,
|
||||
}) {
|
||||
if (readPath(config, "input.backend") !== "ndi") {
|
||||
return <TextField config={config} label="Device" path="input.device" setConfig={setConfig} />;
|
||||
}
|
||||
@@ -61,7 +69,9 @@ function InputDeviceField({ config, ndiSources, ndiSourcesBusy, onRefreshNdiSour
|
||||
(name) => !presetOptions.includes(name)
|
||||
);
|
||||
const selectOptions = [...presetOptions, ...discoveredOptions];
|
||||
const selectValue = selectOptions.includes(currentDevice) ? currentDevice : currentDevice ? "__current__" : "default";
|
||||
const customDevice = Boolean(currentDevice) && !selectOptions.includes(currentDevice);
|
||||
const selectValue = customDevice ? "__custom__" : currentDevice || "default";
|
||||
const showManualField = manualOpen || customDevice;
|
||||
|
||||
return (
|
||||
<Field label="Device">
|
||||
@@ -70,10 +80,12 @@ function InputDeviceField({ config, ndiSources, ndiSourcesBusy, onRefreshNdiSour
|
||||
<select
|
||||
value={selectValue}
|
||||
onChange={(event) => {
|
||||
if (event.target.value === "__current__") return;
|
||||
if (event.target.value === "__custom__") return;
|
||||
onManualOpenChange(false);
|
||||
setConfig((current) => writePath(current, "input.device", event.target.value));
|
||||
}}
|
||||
>
|
||||
{customDevice && <option value="__custom__">Custom source</option>}
|
||||
<optgroup label="Preset">
|
||||
{presetOptions.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
@@ -81,9 +93,6 @@ function InputDeviceField({ config, ndiSources, ndiSourcesBusy, onRefreshNdiSour
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{currentDevice && !selectOptions.includes(currentDevice) && (
|
||||
<option value="__current__">Current: {currentDevice}</option>
|
||||
)}
|
||||
<optgroup label="Discovered NDI sources">
|
||||
{discoveredOptions.length > 0 ? (
|
||||
discoveredOptions.map((sourceName) => (
|
||||
@@ -107,14 +116,24 @@ function InputDeviceField({ config, ndiSources, ndiSourcesBusy, onRefreshNdiSour
|
||||
>
|
||||
<RefreshCw size={16} strokeWidth={1.9} aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button"
|
||||
onClick={() => onManualOpenChange(!manualOpen)}
|
||||
title={showManualField ? "Hide manual source entry" : "Edit source manually"}
|
||||
>
|
||||
<Pencil size={15} strokeWidth={1.9} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Manual NDI source name"
|
||||
placeholder="Manual source name"
|
||||
type="text"
|
||||
value={currentDevice}
|
||||
onChange={(event) => setConfig((current) => writePath(current, "input.device", event.target.value))}
|
||||
/>
|
||||
{showManualField && (
|
||||
<input
|
||||
aria-label="Manual NDI source name"
|
||||
placeholder="Manual source name"
|
||||
type="text"
|
||||
value={currentDevice}
|
||||
onChange={(event) => setConfig((current) => writePath(current, "input.device", event.target.value))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
);
|
||||
@@ -203,6 +222,7 @@ export function ConfigEditor({ onClose }) {
|
||||
const [restartRequired, setRestartRequired] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [manualInputDeviceOpen, setManualInputDeviceOpen] = useState(false);
|
||||
const [ndiSources, setNdiSources] = useState([]);
|
||||
const [ndiSourcesBusy, setNdiSourcesBusy] = useState(false);
|
||||
|
||||
@@ -249,6 +269,8 @@ export function ConfigEditor({ onClose }) {
|
||||
useEffect(() => {
|
||||
if (inputBackend === "ndi") {
|
||||
loadNdiSources();
|
||||
} else {
|
||||
setManualInputDeviceOpen(false);
|
||||
}
|
||||
}, [inputBackend]);
|
||||
|
||||
@@ -340,8 +362,10 @@ export function ConfigEditor({ onClose }) {
|
||||
<SelectField config={draft} label="Backend" options={backendOptions} path="input.backend" setConfig={setDraft} />
|
||||
<InputDeviceField
|
||||
config={draft}
|
||||
manualOpen={manualInputDeviceOpen}
|
||||
ndiSources={ndiSources}
|
||||
ndiSourcesBusy={ndiSourcesBusy}
|
||||
onManualOpenChange={setManualInputDeviceOpen}
|
||||
onRefreshNdiSources={loadNdiSources}
|
||||
setConfig={setDraft}
|
||||
/>
|
||||
|
||||
@@ -74,7 +74,7 @@ export function StatusPanels({ app, performance, runtime, video, videoOutput })
|
||||
{
|
||||
label: "Output",
|
||||
value: formatEndpoint(app.output),
|
||||
meta: `${formatVideoMode(app.output)} / ${video.width || 0} x ${video.height || 0}`,
|
||||
meta: formatVideoMode(app.output),
|
||||
},
|
||||
{
|
||||
label: "Schedule",
|
||||
|
||||
@@ -667,7 +667,7 @@ pre {
|
||||
|
||||
.config-device-picker__source {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 38px;
|
||||
grid-template-columns: minmax(0, 1fr) 38px 38px;
|
||||
gap: 0.45rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user