Compare commits

...

2 Commits

Author SHA1 Message Date
Reinier van der Leer
0440581cf5 fix(builder): Fix array input field deleting items 2024-07-26 12:17:38 +02:00
Reinier van der Leer
c3aa5fe638 fix(builder): Rewrite additionalProperties input field 2024-07-25 18:41:57 +02:00
2 changed files with 61 additions and 77 deletions

View File

@@ -1,3 +1,4 @@
import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons";
import { beautifyString } from "@/lib/utils"; import { beautifyString } from "@/lib/utils";
import { FC, useState } from "react"; import { FC, useState } from "react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
@@ -15,27 +16,25 @@ type BlockInputFieldProps = {
const NodeInputField: FC<BlockInputFieldProps> = const NodeInputField: FC<BlockInputFieldProps> =
({ keyName: key, schema, parentKey = '', value, handleInputClick, handleInputChange, errors }) => { ({ keyName: key, schema, parentKey = '', value, handleInputClick, handleInputChange, errors }) => {
const [newKey, setNewKey] = useState<string>('');
const [newValue, setNewValue] = useState<string>('');
const [keyValuePairs, setKeyValuePairs] = useState<{ key: string, value: string }[]>([]);
const fullKey = parentKey ? `${parentKey}.${key}` : key; const fullKey = parentKey ? `${parentKey}.${key}` : key;
const error = errors[fullKey]; const error = errors[fullKey];
const displayKey = schema.title || beautifyString(key); const displayKey = schema.title || beautifyString(key);
const handleAddProperty = () => { const [keyValuePairs, _setKeyValuePairs] = useState<{ key: string, value: string }[]>(
if (newKey && newValue) { "additionalProperties" in schema && value
const newPairs = [...keyValuePairs, { key: newKey, value: newValue }]; ? Object.entries(value).map(([key, value]) => ({ key: key, value: value}))
setKeyValuePairs(newPairs); : []
setNewKey(''); );
setNewValue('');
const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {}); function setKeyValuePairs(newKVPairs: typeof keyValuePairs): void {
handleInputChange('expected_format', expectedFormat); _setKeyValuePairs(newKVPairs);
} handleInputChange(
}; fullKey,
newKVPairs.reduce((obj, {key, value}) => ({ ...obj, [key]: value }), {})
);
}
const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => { const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => {
// if secret is true, then the input field will be a password field if the value is not null // if secret is true, then the input field will be a password field if the value is not null
return secret ? ( return secret ? (
<div className="clickable-input" onClick={() => handleInputClick(fullKey)}> <div className="clickable-input" onClick={() => handleInputClick(fullKey)}>
@@ -70,67 +69,46 @@ const NodeInputField: FC<BlockInputFieldProps> =
} }
if (schema.type === 'object' && schema.additionalProperties) { if (schema.type === 'object' && schema.additionalProperties) {
const objectValue = value || {};
return ( return (
<div key={fullKey} className="object-input"> <div key={fullKey}>
<strong>{displayKey}:</strong> <div>
{Object.entries(objectValue).map(([propKey, propValue]: [string, any]) => ( {keyValuePairs.map(({ key, value }, index) => (
<div key={`${fullKey}.${propKey}`} className="nested-input"> <div key={index} className="flex items-center w-[325px] space-x-2 mb-2">
<div className="clickable-input" onClick={() => handleInputClick(`${fullKey}.${propKey}`)}>
{beautifyString(propKey)}: {typeof propValue === 'object' ? JSON.stringify(propValue, null, 2) : propValue}
</div>
<Button onClick={() => handleInputChange(`${fullKey}.${propKey}`, undefined)} className="array-item-remove">
&times;
</Button>
</div>
))}
{key === 'expected_format' && (
<div className="nested-input">
{keyValuePairs.map((pair, index) => (
<div key={index} className="key-value-input">
<Input
type="text"
placeholder="Key"
value={beautifyString(pair.key)}
onChange={(e) => {
const newPairs = [...keyValuePairs];
newPairs[index].key = e.target.value;
setKeyValuePairs(newPairs);
const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {});
handleInputChange('expected_format', expectedFormat);
}}
/>
<Input
type="text"
placeholder="Value"
value={beautifyString(pair.value)}
onChange={(e) => {
const newPairs = [...keyValuePairs];
newPairs[index].value = e.target.value;
setKeyValuePairs(newPairs);
const expectedFormat = newPairs.reduce((acc, pair) => ({ ...acc, [pair.key]: pair.value }), {});
handleInputChange('expected_format', expectedFormat);
}}
/>
</div>
))}
<div className="key-value-input">
<Input <Input
type="text" type="text"
placeholder="Key" placeholder="Key"
value={newKey} value={key}
onChange={(e) => setNewKey(e.target.value)} onChange={(e) => setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: e.target.value, value: value
})
)}
/> />
<Input <Input
type="text" type="text"
placeholder="Value" placeholder="Value"
value={newValue} value={value}
onChange={(e) => setNewValue(e.target.value)} onChange={(e) => setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: key, value: e.target.value
})
)}
/> />
<Button variant="ghost" className="px-2"
onClick={() => setKeyValuePairs(keyValuePairs.toSpliced(index, 1))}
>
<Cross2Icon />
</Button>
</div> </div>
<Button onClick={handleAddProperty}>Add Property</Button> ))}
</div> <Button className="w-full"
)} onClick={() => setKeyValuePairs(
keyValuePairs.concat({ key: "", value: "" })
)}
>
<PlusIcon className="mr-2" /> Add Property
</Button>
</div>
{error && <span className="error-message">{error}</span>} {error && <span className="error-message">{error}</span>}
</div> </div>
); );
@@ -257,25 +235,32 @@ const NodeInputField: FC<BlockInputFieldProps> =
</div> </div>
); );
case 'array': case 'array':
if (schema.items && schema.items.type === 'string') { if (schema.items) {
const arrayValues = value as Array<string> || []; const arrayValues = value as Array<string> || [];
return ( return (
<div key={fullKey} className="input-container"> <div key={fullKey} className="input-container">
{arrayValues.map((item: string, index: number) => ( {arrayValues.map((item: string, index: number) => (
<div key={`${fullKey}.${index}`} className="array-item-container"> <div key={`${fullKey}.${index}`} className="flex items-center space-x-2 mb-2">
<input <NodeInputField
type="text"
value={item} value={item}
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)} keyName={index.toString()}
className="array-item-input" parentKey={fullKey}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
schema={schema.items}
errors={errors}
/> />
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove"> <Button variant="ghost" className="px-2" onClick={
&times; () => handleInputChange(fullKey, arrayValues.toSpliced(index, 1))
}>
<Cross2Icon />
</Button> </Button>
</div> </div>
))} ))}
<Button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add"> <Button onClick={
Add Item () => handleInputChange(fullKey, [...arrayValues, ''])
}>
<PlusIcon className="mr-2"/> Add Item
</Button> </Button>
{error && <span className="error-message">{error}</span>} {error && <span className="error-message">{error}</span>}
</div> </div>

View File

@@ -38,7 +38,6 @@ input, textarea {
border-radius: 4px; border-radius: 4px;
width: calc(100% - 18px); width: calc(100% - 18px);
box-sizing: border-box; box-sizing: border-box;
margin-top: 5px;
} }
input::placeholder, textarea::placeholder { input::placeholder, textarea::placeholder {