mirror of
https://github.com/langgenius/dify.git
synced 2026-05-02 15:17:39 +08:00
form
This commit is contained in:
parent
fd0a8d5834
commit
2572e99a4b
@ -99,7 +99,7 @@ const BaseField = ({
|
|||||||
id={field.name}
|
id={field.name}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
className={cn(inputClassName)}
|
className={cn(inputClassName)}
|
||||||
value={value}
|
value={value || ''}
|
||||||
onChange={e => field.handleChange(e.target.value)}
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@ -114,7 +114,7 @@ const BaseField = ({
|
|||||||
name={field.name}
|
name={field.name}
|
||||||
type='password'
|
type='password'
|
||||||
className={cn(inputClassName)}
|
className={cn(inputClassName)}
|
||||||
value={value}
|
value={value || ''}
|
||||||
onChange={e => field.handleChange(e.target.value)}
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@ -129,7 +129,7 @@ const BaseField = ({
|
|||||||
name={field.name}
|
name={field.name}
|
||||||
type='number'
|
type='number'
|
||||||
className={cn(inputClassName)}
|
className={cn(inputClassName)}
|
||||||
value={value}
|
value={value || ''}
|
||||||
onChange={e => field.handleChange(e.target.value)}
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
AnyFieldApi,
|
AnyFieldApi,
|
||||||
} from '@tanstack/react-form'
|
} from '@tanstack/react-form'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useForm } from '@tanstack/react-form'
|
import { useForm } from '@tanstack/react-form'
|
||||||
import type {
|
import type {
|
||||||
FormRef,
|
FormRef,
|
||||||
@ -18,6 +19,7 @@ import type {
|
|||||||
BaseFieldProps,
|
BaseFieldProps,
|
||||||
} from '.'
|
} from '.'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { useGetFormValues } from '@/app/components/base/form/hooks'
|
||||||
|
|
||||||
export type BaseFormProps = {
|
export type BaseFormProps = {
|
||||||
formSchemas?: FormSchema[]
|
formSchemas?: FormSchema[]
|
||||||
@ -28,7 +30,7 @@ export type BaseFormProps = {
|
|||||||
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
|
} & Pick<BaseFieldProps, 'fieldClassName' | 'labelClassName' | 'inputContainerClassName' | 'inputClassName'>
|
||||||
|
|
||||||
const BaseForm = ({
|
const BaseForm = ({
|
||||||
formSchemas,
|
formSchemas = [],
|
||||||
defaultValues,
|
defaultValues,
|
||||||
formClassName,
|
formClassName,
|
||||||
fieldClassName,
|
fieldClassName,
|
||||||
@ -38,17 +40,22 @@ const BaseForm = ({
|
|||||||
ref,
|
ref,
|
||||||
disabled,
|
disabled,
|
||||||
}: BaseFormProps) => {
|
}: BaseFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
})
|
})
|
||||||
|
const { getFormValues } = useGetFormValues(form)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
return {
|
return {
|
||||||
getForm() {
|
getForm() {
|
||||||
return form
|
return form
|
||||||
},
|
},
|
||||||
|
getFormValues: (option) => {
|
||||||
|
return getFormValues(formSchemas, option)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}, [form])
|
}, [form, formSchemas, getFormValues])
|
||||||
|
|
||||||
const renderField = useCallback((field: AnyFieldApi) => {
|
const renderField = useCallback((field: AnyFieldApi) => {
|
||||||
const formSchema = formSchemas?.find(schema => schema.name === field.name)
|
const formSchema = formSchemas?.find(schema => schema.name === field.name)
|
||||||
@ -73,17 +80,37 @@ const BaseForm = ({
|
|||||||
const renderFieldWrapper = useCallback((formSchema: FormSchema) => {
|
const renderFieldWrapper = useCallback((formSchema: FormSchema) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
validators,
|
||||||
|
required,
|
||||||
} = formSchema
|
} = formSchema
|
||||||
|
let mergedValidators = validators
|
||||||
|
if (required && !validators) {
|
||||||
|
mergedValidators = {
|
||||||
|
onMount: ({ value }: any) => {
|
||||||
|
if (!value)
|
||||||
|
return t('common.errorMsg.fieldRequired', { field: name })
|
||||||
|
},
|
||||||
|
onChange: ({ value }: any) => {
|
||||||
|
if (!value)
|
||||||
|
return t('common.errorMsg.fieldRequired', { field: name })
|
||||||
|
},
|
||||||
|
onBlur: ({ value }: any) => {
|
||||||
|
if (!value)
|
||||||
|
return t('common.errorMsg.fieldRequired', { field: name })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form.Field
|
<form.Field
|
||||||
key={name}
|
key={name}
|
||||||
name={name}
|
name={name}
|
||||||
|
validators={mergedValidators}
|
||||||
>
|
>
|
||||||
{renderField}
|
{renderField}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
)
|
)
|
||||||
}, [renderField, form])
|
}, [renderField, form, t])
|
||||||
|
|
||||||
if (!formSchemas?.length)
|
if (!formSchemas?.length)
|
||||||
return null
|
return null
|
||||||
|
|||||||
2
web/app/components/base/form/hooks/index.ts
Normal file
2
web/app/components/base/form/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './use-check-validated'
|
||||||
|
export * from './use-get-form-values'
|
||||||
36
web/app/components/base/form/hooks/use-check-validated.ts
Normal file
36
web/app/components/base/form/hooks/use-check-validated.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { AnyFormApi } from '@tanstack/react-form'
|
||||||
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
|
export const useCheckValidated = (form: AnyFormApi) => {
|
||||||
|
const { notify } = useToastContext()
|
||||||
|
|
||||||
|
const checkValidated = useCallback(() => {
|
||||||
|
const allError = form?.getAllErrors()
|
||||||
|
|
||||||
|
if (allError) {
|
||||||
|
const fields = allError.fields
|
||||||
|
const errorArray = Object.keys(fields).reduce((acc: string[], key: string) => {
|
||||||
|
const errors: any[] = fields[key].errors
|
||||||
|
|
||||||
|
return [...acc, ...errors]
|
||||||
|
}, [] as string[])
|
||||||
|
|
||||||
|
if (errorArray.length) {
|
||||||
|
notify({
|
||||||
|
type: 'error',
|
||||||
|
message: errorArray[0],
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}, [form, notify])
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkValidated,
|
||||||
|
}
|
||||||
|
}
|
||||||
45
web/app/components/base/form/hooks/use-get-form-values.ts
Normal file
45
web/app/components/base/form/hooks/use-get-form-values.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import type { AnyFormApi } from '@tanstack/react-form'
|
||||||
|
import { useCheckValidated } from './use-check-validated'
|
||||||
|
import type {
|
||||||
|
FormSchema,
|
||||||
|
GetValuesOptions,
|
||||||
|
} from '../types'
|
||||||
|
import { getTransformedValuesWhenSecretInputPristine } from '../utils'
|
||||||
|
|
||||||
|
export const useGetFormValues = (form: AnyFormApi) => {
|
||||||
|
const { checkValidated } = useCheckValidated(form)
|
||||||
|
|
||||||
|
const getFormValues = useCallback((
|
||||||
|
formSchemas: FormSchema[],
|
||||||
|
{
|
||||||
|
needCheckValidatedValues,
|
||||||
|
needTransformWhenSecretFieldIsPristine,
|
||||||
|
}: GetValuesOptions,
|
||||||
|
) => {
|
||||||
|
const values = form?.store.state.values || {}
|
||||||
|
if (!needCheckValidatedValues) {
|
||||||
|
return {
|
||||||
|
values,
|
||||||
|
isCheckValidated: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkValidated()) {
|
||||||
|
return {
|
||||||
|
values: needTransformWhenSecretFieldIsPristine ? getTransformedValuesWhenSecretInputPristine(formSchemas, form) : values,
|
||||||
|
isCheckValidated: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
values: {},
|
||||||
|
isCheckValidated: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [form, checkValidated])
|
||||||
|
|
||||||
|
return {
|
||||||
|
getFormValues,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,7 +60,15 @@ export type FormSchema = {
|
|||||||
|
|
||||||
export type FormValues = Record<string, any>
|
export type FormValues = Record<string, any>
|
||||||
|
|
||||||
|
export type GetValuesOptions = {
|
||||||
|
needTransformWhenSecretFieldIsPristine?: boolean
|
||||||
|
needCheckValidatedValues?: boolean
|
||||||
|
}
|
||||||
export type FormRefObject = {
|
export type FormRefObject = {
|
||||||
getForm: () => AnyFormApi
|
getForm: () => AnyFormApi
|
||||||
|
getFormValues: (obj: GetValuesOptions) => {
|
||||||
|
values: Record<string, any>
|
||||||
|
isCheckValidated: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export type FormRef = ForwardedRef<FormRefObject>
|
export type FormRef = ForwardedRef<FormRefObject>
|
||||||
|
|||||||
1
web/app/components/base/form/utils/index.ts
Normal file
1
web/app/components/base/form/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './secret-input'
|
||||||
29
web/app/components/base/form/utils/secret-input/index.ts
Normal file
29
web/app/components/base/form/utils/secret-input/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { AnyFormApi } from '@tanstack/react-form'
|
||||||
|
import type { FormSchema } from '@/app/components/base/form/types'
|
||||||
|
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||||
|
|
||||||
|
export const transformFormSchemasSecretInput = (isPristineSecretInputNames: string[], values: Record<string, any>) => {
|
||||||
|
const transformedValues: Record<string, any> = { ...values }
|
||||||
|
|
||||||
|
isPristineSecretInputNames.forEach((name) => {
|
||||||
|
if (transformedValues[name])
|
||||||
|
transformedValues[name] = '[__HIDDEN__]'
|
||||||
|
})
|
||||||
|
|
||||||
|
return transformedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTransformedValuesWhenSecretInputPristine = (formSchemas: FormSchema[], form: AnyFormApi) => {
|
||||||
|
const values = form?.store.state.values || {}
|
||||||
|
const isPristineSecretInputNames: string[] = []
|
||||||
|
for (let i = 0; i < formSchemas.length; i++) {
|
||||||
|
const schema = formSchemas[i]
|
||||||
|
if (schema.type === FormTypeEnum.secretInput) {
|
||||||
|
const fieldMeta = form?.getFieldMeta(schema.name)
|
||||||
|
if (fieldMeta?.isPristine)
|
||||||
|
isPristineSecretInputNames.push(schema.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformFormSchemasSecretInput(isPristineSecretInputNames, values)
|
||||||
|
}
|
||||||
@ -9,7 +9,6 @@ import { RiExternalLinkLine } from '@remixicon/react'
|
|||||||
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||||
import Modal from '@/app/components/base/modal/modal'
|
import Modal from '@/app/components/base/modal/modal'
|
||||||
import { CredentialTypeEnum } from '../types'
|
import { CredentialTypeEnum } from '../types'
|
||||||
import { transformFormSchemasSecretInput } from '../utils'
|
|
||||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
||||||
import type { FormRefObject } from '@/app/components/base/form/types'
|
import type { FormRefObject } from '@/app/components/base/form/types'
|
||||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||||
@ -64,42 +63,32 @@ const ApiKeyModal = ({
|
|||||||
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
||||||
const formRef = useRef<FormRefObject>(null)
|
const formRef = useRef<FormRefObject>(null)
|
||||||
const handleConfirm = useCallback(async () => {
|
const handleConfirm = useCallback(async () => {
|
||||||
const form = formRef.current?.getForm()
|
const {
|
||||||
const store = form?.store
|
isCheckValidated,
|
||||||
|
values,
|
||||||
|
} = formRef.current?.getFormValues({
|
||||||
|
needCheckValidatedValues: true,
|
||||||
|
needTransformWhenSecretFieldIsPristine: true,
|
||||||
|
}) || { isCheckValidated: false, values: {} }
|
||||||
|
if (!isCheckValidated)
|
||||||
|
return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
__name__,
|
__name__,
|
||||||
__credential_id__,
|
__credential_id__,
|
||||||
...values
|
...restValues
|
||||||
} = store?.state.values
|
} = values
|
||||||
const isPristineSecretInputNames: string[] = []
|
|
||||||
for (let i = 0; i < formSchemas.length; i++) {
|
|
||||||
const schema = formSchemas[i]
|
|
||||||
if (schema.required && !values[schema.name]) {
|
|
||||||
notify({
|
|
||||||
type: 'error',
|
|
||||||
message: t('common.errorMsg.fieldRequired', { field: schema.name }),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (schema.type === FormTypeEnum.secretInput) {
|
|
||||||
const fieldMeta = form?.getFieldMeta(schema.name)
|
|
||||||
if (fieldMeta?.isPristine)
|
|
||||||
isPristineSecretInputNames.push(schema.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
|
|
||||||
|
|
||||||
if (editValues) {
|
if (editValues) {
|
||||||
await updatePluginCredential({
|
await updatePluginCredential({
|
||||||
credentials: transformedValues,
|
credentials: restValues,
|
||||||
credential_id: __credential_id__,
|
credential_id: __credential_id__,
|
||||||
name: __name__ || '',
|
name: __name__ || '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await addPluginCredential({
|
await addPluginCredential({
|
||||||
credentials: transformedValues,
|
credentials: restValues,
|
||||||
type: CredentialTypeEnum.API_KEY,
|
type: CredentialTypeEnum.API_KEY,
|
||||||
name: __name__ || '',
|
name: __name__ || '',
|
||||||
})
|
})
|
||||||
@ -111,7 +100,7 @@ const ApiKeyModal = ({
|
|||||||
|
|
||||||
onClose?.()
|
onClose?.()
|
||||||
invalidatePluginCredentialInfo()
|
invalidatePluginCredentialInfo()
|
||||||
}, [addPluginCredential, onClose, invalidatePluginCredentialInfo, updatePluginCredential, notify, t, editValues, formSchemas])
|
}, [addPluginCredential, onClose, invalidatePluginCredentialInfo, updatePluginCredential, notify, t, editValues])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -15,8 +15,6 @@ import type {
|
|||||||
FormRefObject,
|
FormRefObject,
|
||||||
FormSchema,
|
FormSchema,
|
||||||
} from '@/app/components/base/form/types'
|
} from '@/app/components/base/form/types'
|
||||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
|
||||||
import { transformFormSchemasSecretInput } from '../utils'
|
|
||||||
import { useToastContext } from '@/app/components/base/toast'
|
import { useToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
type OAuthClientSettingsProps = {
|
type OAuthClientSettingsProps = {
|
||||||
@ -46,33 +44,22 @@ const OAuthClientSettings = ({
|
|||||||
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
const invalidatePluginCredentialInfo = useInvalidPluginCredentialInfoHook(pluginPayload)
|
||||||
const formRef = useRef<FormRefObject>(null)
|
const formRef = useRef<FormRefObject>(null)
|
||||||
const handleConfirm = useCallback(async () => {
|
const handleConfirm = useCallback(async () => {
|
||||||
const form = formRef.current?.getForm()
|
const {
|
||||||
const store = form?.store
|
isCheckValidated,
|
||||||
|
values,
|
||||||
|
} = formRef.current?.getFormValues({
|
||||||
|
needCheckValidatedValues: true,
|
||||||
|
needTransformWhenSecretFieldIsPristine: true,
|
||||||
|
}) || { isCheckValidated: false, values: {} }
|
||||||
|
if (!isCheckValidated)
|
||||||
|
return
|
||||||
const {
|
const {
|
||||||
__oauth_client__,
|
__oauth_client__,
|
||||||
...values
|
...restValues
|
||||||
} = store?.state.values
|
} = values
|
||||||
const isPristineSecretInputNames: string[] = []
|
|
||||||
for (let i = 0; i < schemas.length; i++) {
|
|
||||||
const schema = schemas[i]
|
|
||||||
if (schema.required && !values[schema.name]) {
|
|
||||||
notify({
|
|
||||||
type: 'error',
|
|
||||||
message: t('common.errorMsg.fieldRequired', { field: schema.name }),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (schema.type === FormTypeEnum.secretInput) {
|
|
||||||
const fieldMeta = form?.getFieldMeta(schema.name)
|
|
||||||
if (fieldMeta?.isPristine)
|
|
||||||
isPristineSecretInputNames.push(schema.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformedValues = transformFormSchemasSecretInput(isPristineSecretInputNames, values)
|
|
||||||
|
|
||||||
await setPluginOAuthCustomClient({
|
await setPluginOAuthCustomClient({
|
||||||
client_params: transformedValues,
|
client_params: restValues,
|
||||||
enable_oauth_custom_client: __oauth_client__ === 'custom',
|
enable_oauth_custom_client: __oauth_client__ === 'custom',
|
||||||
})
|
})
|
||||||
notify({
|
notify({
|
||||||
@ -82,7 +69,7 @@ const OAuthClientSettings = ({
|
|||||||
|
|
||||||
onClose?.()
|
onClose?.()
|
||||||
invalidatePluginCredentialInfo()
|
invalidatePluginCredentialInfo()
|
||||||
}, [onClose, invalidatePluginCredentialInfo, setPluginOAuthCustomClient, notify, t, schemas])
|
}, [onClose, invalidatePluginCredentialInfo, setPluginOAuthCustomClient, notify, t])
|
||||||
|
|
||||||
const handleConfirmAndAuthorize = useCallback(async () => {
|
const handleConfirmAndAuthorize = useCallback(async () => {
|
||||||
await handleConfirm()
|
await handleConfirm()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user