From ef665a8c3d865ac5c78f7d579e4d862ae37e6beb Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 16 Feb 2026 08:45:39 -0600 Subject: [PATCH] clean up camera inputs fields --- web/public/locales/en/views/settings.json | 2 +- .../config-form/section-configs/ffmpeg.ts | 7 +++ .../theme/fields/CameraInputsField.tsx | 20 ++++--- .../theme/templates/FieldTemplate.tsx | 3 +- .../theme/widgets/FfmpegArgsWidget.tsx | 52 +++++++++++-------- .../theme/widgets/InputRolesWidget.tsx | 50 +++++++++--------- 6 files changed, 79 insertions(+), 55 deletions(-) diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 776e9f459..60285bb7d 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -1252,7 +1252,7 @@ "manualPlaceholder": "Enter FFmpeg arguments" }, "cameraInputs": { - "itemTitle": "Stream {{index}}: {{path}}" + "itemTitle": "Stream {{index}}" }, "restartRequiredField": "Restart required", "restartRequiredFooter": "Configuration changed - Restart required", diff --git a/web/src/components/config-form/section-configs/ffmpeg.ts b/web/src/components/config-form/section-configs/ffmpeg.ts index d8cb7b8fa..cada4bd45 100644 --- a/web/src/components/config-form/section-configs/ffmpeg.ts +++ b/web/src/components/config-form/section-configs/ffmpeg.ts @@ -30,6 +30,7 @@ const ffmpeg: SectionConfigOverrides = { output_args: "/configuration/ffmpeg_presets#output-args-presets", "inputs.output_args": "/configuration/ffmpeg_presets#output-args-presets", "output_args.record": "/configuration/ffmpeg_presets#output-args-presets", + "inputs.roles": "/configuration/cameras/#setting-up-camera-inputs", }, restartRequired: [], fieldOrder: [ @@ -85,6 +86,9 @@ const ffmpeg: SectionConfigOverrides = { }, roles: { "ui:widget": "inputRoles", + "ui:options": { + showArrayItemDescription: true, + }, }, global_args: { "ui:widget": "hidden", @@ -92,10 +96,13 @@ const ffmpeg: SectionConfigOverrides = { hwaccel_args: ffmpegArgsWidget("hwaccel_args", { allowInherit: true, hideDescription: true, + forceSplitLayout: true, showArrayItemDescription: true, }), input_args: ffmpegArgsWidget("input_args", { + allowInherit: true, hideDescription: true, + forceSplitLayout: true, showArrayItemDescription: true, }), output_args: { diff --git a/web/src/components/config-form/theme/fields/CameraInputsField.tsx b/web/src/components/config-form/theme/fields/CameraInputsField.tsx index 4f3c290ad..375aef3bd 100644 --- a/web/src/components/config-form/theme/fields/CameraInputsField.tsx +++ b/web/src/components/config-form/theme/fields/CameraInputsField.tsx @@ -87,7 +87,7 @@ const normalizeNonDetectHwaccel = (inputs: FfmpegInput[]): FfmpegInput[] => return { ...input, - hwaccel_args: null, + hwaccel_args: undefined, }; }); @@ -311,8 +311,9 @@ export function CameraInputsField(props: FieldProps) { const itemTitle = t("configForm.cameraInputs.itemTitle", { ns: "views/settings", index: index + 1, - path: typeof input.path === "string" ? input.path.trim() : "", }); + const itemPath = + typeof input.path === "string" ? input.path.trim() : ""; return ( @@ -328,7 +329,14 @@ export function CameraInputsField(props: FieldProps) {
- {itemTitle} + + {itemTitle} + {itemPath ? ( + + {itemPath} + + ) : null} + {open ? ( ) : ( @@ -352,11 +360,7 @@ export function CameraInputsField(props: FieldProps) { })}
-
- {renderField(index, "roles", { - showSchemaDescription: true, - })} -
+
{renderField(index, "roles")}
{renderField(index, "input_args")} diff --git a/web/src/components/config-form/theme/templates/FieldTemplate.tsx b/web/src/components/config-form/theme/templates/FieldTemplate.tsx index 38e146f4d..e37279222 100644 --- a/web/src/components/config-form/theme/templates/FieldTemplate.tsx +++ b/web/src/components/config-form/theme/templates/FieldTemplate.tsx @@ -135,9 +135,10 @@ export function FieldTemplate(props: FieldTemplateProps) { !isMultiSchemaWrapper && !isObjectField && !isAdditionalProperty; + const forceSplitLayout = uiOptionsFromSchema.forceSplitLayout === true; const useSplitLayout = uiOptionsFromSchema.splitLayout !== false && - isScalarValueField && + (isScalarValueField || forceSplitLayout) && !isBoolean && !isMultiSchemaWrapper && !isObjectField && diff --git a/web/src/components/config-form/theme/widgets/FfmpegArgsWidget.tsx b/web/src/components/config-form/theme/widgets/FfmpegArgsWidget.tsx index 76de7ae94..415cd2603 100644 --- a/web/src/components/config-form/theme/widgets/FfmpegArgsWidget.tsx +++ b/web/src/components/config-form/theme/widgets/FfmpegArgsWidget.tsx @@ -64,6 +64,10 @@ const resolveMode = ( return "inherit"; } + if (allowInherit && Array.isArray(value) && value.length === 0) { + return "inherit"; + } + if (Array.isArray(value)) { return "manual"; } @@ -116,6 +120,7 @@ export function FfmpegArgsWidget(props: WidgetProps) { const presetField = options?.ffmpegPresetField as PresetField | undefined; const allowInherit = options?.allowInherit === true; const hideDescription = options?.hideDescription === true; + const useSplitLayout = options?.splitLayout !== false; const { data } = useSWR("ffmpeg/presets"); @@ -148,7 +153,7 @@ export function FfmpegArgsWidget(props: WidgetProps) { setMode(nextMode); if (nextMode === "inherit") { - onChange(null); + onChange(undefined); return; } @@ -164,10 +169,15 @@ export function FfmpegArgsWidget(props: WidgetProps) { return; } + if (mode === "preset") { + onChange(""); + return; + } + const manualText = normalizeManualText(value); onChange(manualText); }, - [onChange, presetOptions, value], + [mode, onChange, presetOptions, value], ); const handlePresetChange = useCallback( @@ -237,6 +247,23 @@ export function FfmpegArgsWidget(props: WidgetProps) { onValueChange={(next) => handleModeChange(next as FfmpegArgsMode)} className="gap-3" > + {allowInherit ? ( +
+ + +
+ ) : null}
- {allowInherit ? ( -
- - -
- ) : null} {mode === "inherit" ? null : mode === "preset" && canUsePresets ? ( @@ -292,7 +302,7 @@ export function FfmpegArgsWidget(props: WidgetProps) { onValueChange={handlePresetChange} disabled={disabled || readonly} > - + )} - {!hideDescription && fieldDescription ? ( + {!hideDescription && !useSplitLayout && fieldDescription ? (

{fieldDescription}

) : null} diff --git a/web/src/components/config-form/theme/widgets/InputRolesWidget.tsx b/web/src/components/config-form/theme/widgets/InputRolesWidget.tsx index efe48bb98..c50cf7652 100644 --- a/web/src/components/config-form/theme/widgets/InputRolesWidget.tsx +++ b/web/src/components/config-form/theme/widgets/InputRolesWidget.tsx @@ -35,31 +35,33 @@ export function InputRolesWidget(props: WidgetProps) { }; return ( -
- {INPUT_ROLES.map((role) => { - const checked = selectedRoles.includes(role); - const label = t(`configForm.inputRoles.options.${role}`, { - ns: "views/settings", - defaultValue: role, - }); +
+
+ {INPUT_ROLES.map((role) => { + const checked = selectedRoles.includes(role); + const label = t(`configForm.inputRoles.options.${role}`, { + ns: "views/settings", + defaultValue: role, + }); - return ( -
- - toggleRole(role, !!enabled)} - /> -
- ); - })} + return ( +
+ + toggleRole(role, !!enabled)} + /> +
+ ); + })} +
); }