import type { Folder, HttpRequest } from '@yaakapp-internal/models'; import type { FormInput, FormInputCheckbox, FormInputEditor, FormInputFile, FormInputHttpRequest, FormInputSelect, FormInputText, } from '@yaakapp-internal/plugins'; import classNames from 'classnames'; import { useCallback } from 'react'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useFolders } from '../hooks/useFolders'; import { useHttpRequests } from '../hooks/useHttpRequests'; import { capitalize } from '../lib/capitalize'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { Checkbox } from './core/Checkbox'; import { Editor } from './core/Editor/Editor'; import { Input } from './core/Input'; import { Label } from './core/Label'; import { Select } from './core/Select'; import { VStack } from './core/Stacks'; import { SelectFile } from './SelectFile'; // eslint-disable-next-line react-refresh/only-export-components export const DYNAMIC_FORM_NULL_ARG = '__NULL__'; export function DynamicForm>({ config, data, onChange, useTemplating, autocompleteVariables, stateKey, }: { config: FormInput[]; onChange: (value: T) => void; data: T; useTemplating?: boolean; autocompleteVariables?: boolean; stateKey: string; }) { const setDataAttr = useCallback( (name: string, value: string | boolean | null) => { onChange({ ...data, [name]: value == DYNAMIC_FORM_NULL_ARG ? undefined : value }); }, [data, onChange], ); return ( {config.map((a, i) => { switch (a.type) { case 'select': return ( setDataAttr(a.name, v)} value={ data[a.name] ? String(data[a.name]) : (a.defaultValue ?? DYNAMIC_FORM_NULL_ARG) } /> ); case 'text': return ( setDataAttr(a.name, v)} value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')} /> ); case 'editor': return ( setDataAttr(a.name, v)} value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')} /> ); case 'checkbox': return ( setDataAttr(a.name, v)} value={data[a.name] !== undefined ? data[a.name] === true : false} /> ); case 'http_request': return ( setDataAttr(a.name, v)} value={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG} /> ); case 'file': return ( setDataAttr(a.name, v)} filePath={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG} /> ); } })} ); } function TextArg({ arg, onChange, value, useTemplating, autocompleteVariables, stateKey, }: { arg: FormInputText; value: string; onChange: (v: string) => void; useTemplating: boolean; autocompleteVariables: boolean; stateKey: string; }) { const handleChange = useCallback( (value: string) => { onChange(value === '' ? DYNAMIC_FORM_NULL_ARG : value); }, [onChange], ); return ( ); } function EditorArg({ arg, onChange, value, useTemplating, autocompleteVariables, stateKey, }: { arg: FormInputEditor; value: string; onChange: (v: string) => void; useTemplating: boolean; autocompleteVariables: boolean; stateKey: string; }) { const handleChange = useCallback( (value: string) => { onChange(value === '' ? DYNAMIC_FORM_NULL_ARG : value); }, [onChange], ); const id = `input-${arg.name}`; return (
); } function SelectArg({ arg, value, onChange, }: { arg: FormInputSelect; value: string; onChange: (v: string) => void; }) { return ( { return { label: buildRequestBreadcrumbs(r, folders).join(' / ') + (r.id == activeRequest?.id ? ' (current)' : ''), value: r.id, }; }), ]} /> ); } function buildRequestBreadcrumbs(request: HttpRequest, folders: Folder[]): string[] { const ancestors: (HttpRequest | Folder)[] = [request]; const next = () => { const latest = ancestors[0]; if (latest == null) return []; const parent = folders.find((f) => f.id === latest.folderId); if (parent == null) return; ancestors.unshift(parent); next(); }; next(); return ancestors.map((a) => (a.model === 'folder' ? a.name : fallbackRequestName(a))); } function CheckboxArg({ arg, onChange, value, }: { arg: FormInputCheckbox; value: boolean; onChange: (v: boolean) => void; }) { return ( ); }