import type { WidgetProps } from "@rjsf/utils"; import useSWR from "swr"; import { useMemo, useState, type FocusEvent } from "react"; import { useTranslation } from "react-i18next"; import { LuEye, LuEyeOff } from "react-icons/lu"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { ConfigFormContext } from "@/types/configForm"; import { cn } from "@/lib/utils"; import { getSizedFieldClassName } from "../utils"; import { isMaskedPath, hasCredentials, maskCredentials, } from "@/utils/credentialMask"; type RawPathsResponse = { cameras?: Record< string, { ffmpeg?: { inputs?: Array<{ path?: string; }>; }; } >; }; const getInputIndexFromWidgetId = (id: string): number | undefined => { const match = id.match(/_inputs_(\d+)_path$/); if (!match) { return undefined; } const index = Number(match[1]); return Number.isNaN(index) ? undefined : index; }; export function CameraPathWidget(props: WidgetProps) { const { id, value, disabled, readonly, onChange, onBlur, onFocus, placeholder, schema, options, } = props; const { t } = useTranslation(["common", "views/settings"]); const [showCredentials, setShowCredentials] = useState(false); const formContext = props.registry?.formContext as | ConfigFormContext | undefined; const isCameraLevel = formContext?.level === "camera"; const cameraName = formContext?.cameraName; const inputIndex = useMemo(() => getInputIndexFromWidgetId(id), [id]); const shouldFetchRawPaths = isCameraLevel && !!cameraName && inputIndex !== undefined; const { data: rawPaths } = useSWR( shouldFetchRawPaths ? "config/raw_paths" : null, ); const rawPath = useMemo(() => { if (!cameraName || inputIndex === undefined) { return undefined; } const path = rawPaths?.cameras?.[cameraName]?.ffmpeg?.inputs?.[inputIndex]?.path; return typeof path === "string" ? path : undefined; }, [cameraName, inputIndex, rawPaths]); const rawValue = typeof value === "string" ? value : ""; const resolvedValue = isMaskedPath(rawValue) && rawPath ? rawPath : (rawValue ?? ""); const canReveal = hasCredentials(resolvedValue) && !isMaskedPath(resolvedValue); const canToggle = canReveal || isMaskedPath(rawValue); const isMaskedView = canToggle && !showCredentials; const displayValue = isMaskedView ? maskCredentials(resolvedValue) : resolvedValue; const isNullable = Array.isArray(schema.type) ? schema.type.includes("null") : false; const fieldClassName = getSizedFieldClassName(options, "xs"); const uriLabel = t("cameraWizard.step3.url", { ns: "views/settings", defaultValue: schema.title, }); const toggleLabel = showCredentials ? t("label.hide", { ns: "common", item: uriLabel }) : t("label.show", { ns: "common", item: uriLabel }); const handleFocus = (event: FocusEvent) => { if (isMaskedView && canReveal) { setShowCredentials(true); onFocus(id, resolvedValue); return; } onFocus(id, event.target.value); }; const handleBlur = (event: FocusEvent) => { if (canToggle) { setShowCredentials(false); } onBlur(id, event.target.value); }; return (
onChange( e.target.value === "" ? isNullable ? null : undefined : e.target.value, ) } onBlur={handleBlur} onFocus={handleFocus} aria-label={schema.title} /> {canToggle ? ( ) : null}
); }