Skip to content

Commit beac5ad

Browse files
committed
feat: add type obj, array to variables
1 parent 91e6b37 commit beac5ad

File tree

10 files changed

+641
-542
lines changed

10 files changed

+641
-542
lines changed

client/src/components/forms/variables-setting.tsx

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { useErrorsLngChange } from '@/hooks/use-errors-lng-change'
22
import { TFlowInput, useFlowInputSchema } from '@/lib/schema/flow-input'
3-
import { isStringBoolean, isStringNumber, toBoolean } from '@/utils'
3+
import {
4+
isStringArray,
5+
isStringBoolean,
6+
isStringNumber,
7+
isStringObject,
8+
toArray,
9+
toBoolean,
10+
} from '@/utils'
411
import { zodResolver } from '@hookform/resolvers/zod'
512
import { toNumber } from 'lodash'
613
import { X } from 'lucide-react'
@@ -21,20 +28,74 @@ import {
2128
SelectItem,
2229
SelectTrigger,
2330
SelectValue,
31+
Textarea,
2432
} from '../ui'
2533

34+
const closeChars = new Map([
35+
['{', '}'],
36+
['[', ']'],
37+
['(', ')'],
38+
])
39+
2640
type Props = {
2741
id?: string
2842
onSubmit?: (data: TFlowInput) => void
2943
defaultValues?: TFlowInput
3044
}
3145

32-
const parseVariableValue = (value: string) => {
33-
if (isStringNumber(value)) return toNumber(value)
34-
if (isStringBoolean(value)) return toBoolean(value)
46+
const parseVariableValue = (value: string, type: string) => {
47+
if (isStringNumber(value) && type === 'number') return toNumber(value)
48+
if (isStringBoolean(value) && type === 'boolean') return toBoolean(value)
49+
50+
if (isStringArray(value) && type === 'array') return toArray(value)
51+
52+
if (isStringObject(value) && type === 'object') return JSON.parse(value)
53+
3554
return value
3655
}
3756

57+
const handleJsonChange = (
58+
e: React.ChangeEvent<HTMLTextAreaElement>,
59+
onChange: (value: any) => void,
60+
) => {
61+
const cursorPosition = e.target.selectionStart
62+
63+
const val = [...e.target.value]
64+
65+
const char = val.slice(cursorPosition - 1, cursorPosition)[0]
66+
67+
const closeChar = closeChars.get(char)
68+
69+
if (closeChar) {
70+
val.splice(cursorPosition, 0, closeChar)
71+
e.target.value = val.join('')
72+
e.target.selectionEnd = cursorPosition
73+
}
74+
75+
onChange(e.target.value)
76+
}
77+
78+
const handleJsonKeyDown = (
79+
e: React.KeyboardEvent<HTMLTextAreaElement>,
80+
onChange: (value: any) => void,
81+
) => {
82+
if (e.key === 'Tab') {
83+
e.preventDefault()
84+
85+
const cursorPosition = e.currentTarget.selectionStart
86+
const value = e.currentTarget.value
87+
e.currentTarget.value =
88+
value.substring(0, cursorPosition) +
89+
' ' +
90+
value.substring(cursorPosition, value.length)
91+
92+
e.currentTarget.selectionStart = cursorPosition + 2
93+
e.currentTarget.selectionEnd = cursorPosition + 2
94+
95+
onChange(e.currentTarget.value)
96+
}
97+
}
98+
3899
export const VariablesSettingForm = ({
39100
id = 'variables-setting-form',
40101
onSubmit,
@@ -47,19 +108,22 @@ export const VariablesSettingForm = ({
47108
mode: 'onChange',
48109
defaultValues: {
49110
...defaultValues,
111+
variables: defaultValues?.variables?.map((variable) => ({
112+
...variable,
113+
value: JSON.stringify(variable.value),
114+
})),
50115
},
51116
})
117+
118+
const variablesWatch = form.watch('variables')
119+
52120
const {
53121
fields: variables,
54122
append,
55-
prepend,
56123
remove,
57-
swap,
58-
move,
59-
insert,
60124
} = useFieldArray({
61125
control: form.control,
62-
name: 'variables', // unique name for your Field Array
126+
name: 'variables',
63127
})
64128

65129
useErrorsLngChange(form)
@@ -70,7 +134,7 @@ export const VariablesSettingForm = ({
70134
...data,
71135
variables: data.variables?.map((variable) => ({
72136
...variable,
73-
value: parseVariableValue(variable.value),
137+
value: parseVariableValue(variable.value, variable.type as string),
74138
})),
75139
})
76140
}
@@ -123,11 +187,27 @@ export const VariablesSettingForm = ({
123187
return (
124188
<FormItem className='w-full'>
125189
<FormControl>
126-
<Input
127-
{...field}
128-
autoComplete='off'
129-
placeholder={t('variable_value.placeholder')}
130-
/>
190+
{variablesWatch?.[index]?.type === 'object' ? (
191+
<Textarea
192+
{...field}
193+
onChange={(e) => {
194+
handleJsonChange(e, field.onChange)
195+
}}
196+
onKeyDown={(e) =>
197+
handleJsonKeyDown(e, field.onChange)
198+
}
199+
autoComplete='off'
200+
placeholder={t('variable_value.placeholder')}
201+
className='hidden-scroll resize-none'
202+
rows={5}
203+
/>
204+
) : (
205+
<Input
206+
{...field}
207+
autoComplete='off'
208+
placeholder={t('variable_value.placeholder')}
209+
/>
210+
)}
131211
</FormControl>
132212
<FormMessage />
133213
</FormItem>
@@ -152,7 +232,13 @@ export const VariablesSettingForm = ({
152232
</SelectTrigger>
153233
</FormControl>
154234
<SelectContent>
155-
{['string', 'number', 'boolean'].map((type) => (
235+
{[
236+
'string',
237+
'number',
238+
'boolean',
239+
'array',
240+
'object',
241+
].map((type) => (
156242
<SelectItem key={type} value={type}>
157243
{type}
158244
</SelectItem>

client/src/lib/schema/variable-input.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { isStringBoolean, isStringNumber } from '@/utils'
1+
import {
2+
isStringArray,
3+
isStringBoolean,
4+
isStringNumber,
5+
isStringObject,
6+
} from '@/utils'
27
import { useTranslation } from 'react-i18next'
38
import * as z from 'zod'
49

@@ -16,7 +21,7 @@ export const useVariableInputSchema = () => {
1621
}),
1722
value: z.any().optional(),
1823
type: z
19-
.enum(['string', 'number', 'boolean'])
24+
.enum(['string', 'number', 'boolean', 'array', 'object'])
2025
.default('string')
2126
.optional(),
2227
})
@@ -38,6 +43,22 @@ export const useVariableInputSchema = () => {
3843
path: ['value'],
3944
})
4045
}
46+
47+
if (type === 'array' && !isStringArray(value)) {
48+
ctx.addIssue({
49+
code: z.ZodIssueCode.custom,
50+
message: t('variable_value.errors.array'),
51+
path: ['value'],
52+
})
53+
}
54+
55+
if (type === 'object' && !isStringObject(value)) {
56+
ctx.addIssue({
57+
code: z.ZodIssueCode.custom,
58+
message: t('variable_value.errors.object'),
59+
path: ['value'],
60+
})
61+
}
4162
})
4263
}
4364

client/src/locales/en/forms.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@
121121
"errors": {
122122
"required": "Please enter variable value.",
123123
"number": "Please enter a valid number.",
124-
"boolean": "Please enter a valid boolean value."
124+
"boolean": "Please enter a valid boolean value.",
125+
"array": "Please enter a valid array value. (e.g. a,b,c)",
126+
"object": "Please enter a valid object value. (e.g. {\"key\": \"value\"})"
125127
}
126128
},
127129
"variable_type": {

client/src/locales/vi/forms.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@
119119
"label": "Giá trị {{index}}",
120120
"placeholder": "Nhập giá trị biến",
121121
"errors": {
122-
"required": "Vui lòng nhập giá trị biến."
122+
"required": "Vui lòng nhập giá trị biến.",
123+
"number": "Vu lòng nhập một giá trị số.",
124+
"boolean": "Vui lòng nhập một giá trị boolean.",
125+
"array": "Vui lòng nhập một giá trị mảng. (e.g. a,b,c)",
126+
"object": "Vui lòng nhập một giá trị object."
123127
}
124128
},
125129
"variable_type": {

client/src/utils/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,26 @@ export const toBoolean = (value: string | number) => {
121121

122122
return Boolean(value)
123123
}
124+
125+
export const isStringArray = (value: string) => {
126+
return /^[^,]+(,[^,]+)+$/g.test(value)
127+
}
128+
129+
export const toArray = (value: string) => {
130+
if (!isStringArray(value)) throw new Error('Invalid array')
131+
132+
return value.split(',').map((v) => {
133+
if (isStringNumber(v)) return Number(v)
134+
if (isStringBoolean(v)) return toBoolean(v)
135+
return v
136+
})
137+
}
138+
139+
export const isStringObject = (value: string) => {
140+
try {
141+
JSON.parse(value)
142+
return true
143+
} catch (error) {
144+
return false
145+
}
146+
}

server/src/i18n/i18n-node.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import type { Locales, Translations, TranslationFunctions } from './i18n-types'
88

99
loadAllLocales()
1010

11-
export const L: LocaleTranslationFunctions<
12-
Locales,
13-
Translations,
14-
TranslationFunctions
15-
> = i18n()
11+
export const L: LocaleTranslationFunctions<Locales, Translations, TranslationFunctions> = i18n()
1612

1713
export default L

0 commit comments

Comments
 (0)