mirror of https://github.com/langgenius/dify.git
feat: Enhance form components with hidden fields and popup properties for improved configuration
This commit is contained in:
parent
839fe12087
commit
d1d83f8a2a
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { type BaseConfiguration, BaseFieldType } from './types'
|
||||
import { withForm } from '../..'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
|
|
@ -16,18 +16,30 @@ const BaseField = <T,>({
|
|||
render: function Render({
|
||||
form,
|
||||
}) {
|
||||
const { type, label, placeholder, variable, tooltip, showConditions, max, min, options, required, showOptional } = config
|
||||
const {
|
||||
type,
|
||||
label,
|
||||
placeholder,
|
||||
variable,
|
||||
tooltip,
|
||||
showConditions,
|
||||
max,
|
||||
min,
|
||||
options,
|
||||
required,
|
||||
showOptional,
|
||||
popupProps,
|
||||
} = config
|
||||
|
||||
const fieldValues = useStore(form.store, state => state.values)
|
||||
|
||||
const isAllConditionsMet = useMemo(() => {
|
||||
const isAllConditionsMet = useStore(form.store, (state) => {
|
||||
const fieldValues = state.values
|
||||
if (!showConditions.length) return true
|
||||
return showConditions.every((condition) => {
|
||||
const { variable, value } = condition
|
||||
const fieldValue = fieldValues[variable as keyof typeof fieldValues]
|
||||
return fieldValue === value
|
||||
})
|
||||
}, [fieldValues, showConditions])
|
||||
})
|
||||
|
||||
if (!isAllConditionsMet)
|
||||
return <></>
|
||||
|
|
@ -98,6 +110,7 @@ const BaseField = <T,>({
|
|||
showOptional,
|
||||
}}
|
||||
options={options!}
|
||||
popupProps={popupProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ export type NumberConfiguration = {
|
|||
|
||||
export type SelectConfiguration = {
|
||||
options: Option[] // Options for select field
|
||||
popupProps?: {
|
||||
wrapperClassName?: string
|
||||
className?: string
|
||||
itemClassName?: string
|
||||
title?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type BaseConfiguration<T> = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { type InputFieldConfiguration, InputFieldType } from './types'
|
||||
import { withForm } from '../..'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
|
|
@ -31,18 +31,17 @@ const InputField = <T,>({
|
|||
description,
|
||||
options,
|
||||
listeners,
|
||||
popupProps,
|
||||
} = config
|
||||
|
||||
const fieldValues = useStore(form.store, state => state.values)
|
||||
|
||||
const isAllConditionsMet = useMemo(() => {
|
||||
if (!showConditions.length) return true
|
||||
const isAllConditionsMet = useStore(form.store, (state) => {
|
||||
const fieldValues = state.values
|
||||
return showConditions.every((condition) => {
|
||||
const { variable, value } = condition
|
||||
const fieldValue = fieldValues[variable as keyof typeof fieldValues]
|
||||
return fieldValue === value
|
||||
})
|
||||
}, [fieldValues, showConditions])
|
||||
})
|
||||
|
||||
if (!isAllConditionsMet)
|
||||
return <></>
|
||||
|
|
@ -134,6 +133,7 @@ const InputField = <T,>({
|
|||
showOptional,
|
||||
}}
|
||||
options={options!}
|
||||
popupProps={popupProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react'
|
||||
import { withForm } from '@/app/components/base/form'
|
||||
import type { FormData } from './types'
|
||||
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import { useHiddenConfigurations } from './hooks'
|
||||
|
||||
type HiddenFieldsProps = {
|
||||
initialData?: FormData
|
||||
}
|
||||
|
||||
const HiddenFields = ({
|
||||
initialData,
|
||||
}: HiddenFieldsProps) => withForm({
|
||||
defaultValues: initialData,
|
||||
render: function Render({
|
||||
form,
|
||||
}) {
|
||||
const options = useStore(form.store, state => state.values.options)
|
||||
|
||||
const hiddenConfigurations = useHiddenConfigurations({
|
||||
options,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{hiddenConfigurations.map((config, index) => {
|
||||
const FieldComponent = InputField<FormData>({
|
||||
initialData,
|
||||
config,
|
||||
})
|
||||
return <FieldComponent key={index} form={form} />
|
||||
})}
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default HiddenFields
|
||||
|
|
@ -64,40 +64,11 @@ export const useHiddenFieldNames = (type: InputVarType) => {
|
|||
}
|
||||
|
||||
export const useConfigurations = (props: {
|
||||
type: string,
|
||||
options: string[] | undefined,
|
||||
setFieldValue: (fieldName: DeepKeys<FormData>, value: any) => void,
|
||||
supportFile: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { type, options, setFieldValue, supportFile } = props
|
||||
|
||||
const { data: fileUploadConfigResponse } = useFileUploadConfig()
|
||||
const {
|
||||
imgSizeLimit,
|
||||
docSizeLimit,
|
||||
audioSizeLimit,
|
||||
videoSizeLimit,
|
||||
} = useFileSizeLimit(fileUploadConfigResponse)
|
||||
|
||||
const isSelectInput = type === InputVarType.select
|
||||
|
||||
const defaultSelectOptions = useMemo(() => {
|
||||
if (isSelectInput && options) {
|
||||
const defaultOptions = [
|
||||
{
|
||||
value: '',
|
||||
label: t('appDebug.variableConfig.noDefaultSelected'),
|
||||
},
|
||||
]
|
||||
const otherOptions = options.map((option: string) => ({
|
||||
value: option,
|
||||
label: option,
|
||||
}))
|
||||
return [...defaultOptions, ...otherOptions]
|
||||
}
|
||||
return []
|
||||
}, [isSelectInput, options, t])
|
||||
const { setFieldValue, supportFile } = props
|
||||
|
||||
const handleTypeChange = useCallback((type: string) => {
|
||||
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type as InputVarType)) {
|
||||
|
|
@ -186,6 +157,41 @@ export const useConfigurations = (props: {
|
|||
}]
|
||||
}, [handleTypeChange, supportFile, t])
|
||||
|
||||
return initialConfigurations
|
||||
}
|
||||
|
||||
export const useHiddenConfigurations = (props: {
|
||||
options: string[] | undefined,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { options } = props
|
||||
|
||||
const { data: fileUploadConfigResponse } = useFileUploadConfig()
|
||||
const {
|
||||
imgSizeLimit,
|
||||
docSizeLimit,
|
||||
audioSizeLimit,
|
||||
videoSizeLimit,
|
||||
} = useFileSizeLimit(fileUploadConfigResponse)
|
||||
|
||||
const defaultSelectOptions = useMemo(() => {
|
||||
if (options) {
|
||||
const defaultOptions = [
|
||||
{
|
||||
value: '',
|
||||
label: t('appDebug.variableConfig.noDefaultSelected'),
|
||||
},
|
||||
]
|
||||
const otherOptions = options.map((option: string) => ({
|
||||
value: option,
|
||||
label: option,
|
||||
}))
|
||||
return [...defaultOptions, ...otherOptions]
|
||||
}
|
||||
return []
|
||||
}, [options, t])
|
||||
|
||||
const hiddenConfigurations = useMemo((): InputFieldConfiguration<FormData>[] => {
|
||||
return [{
|
||||
type: InputFieldType.textInput,
|
||||
|
|
@ -220,6 +226,9 @@ export const useConfigurations = (props: {
|
|||
}],
|
||||
showOptional: true,
|
||||
options: defaultSelectOptions,
|
||||
popupProps: {
|
||||
wrapperClassName: 'z-40',
|
||||
},
|
||||
}, {
|
||||
type: InputFieldType.textInput,
|
||||
label: t('appDebug.variableConfig.placeholder'),
|
||||
|
|
@ -296,8 +305,5 @@ export const useConfigurations = (props: {
|
|||
}]
|
||||
}, [defaultSelectOptions, imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit, t])
|
||||
|
||||
return {
|
||||
initialConfigurations,
|
||||
hiddenConfigurations,
|
||||
}
|
||||
return hiddenConfigurations
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { DeepKeys } from '@tanstack/react-form'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import { ChangeType } from '@/app/components/workflow/types'
|
||||
import { useFileUploadConfig } from '@/service/use-common'
|
||||
import type { FormData, InputFieldFormProps } from './types'
|
||||
import type { InputFieldFormProps } from './types'
|
||||
import { useAppForm } from '@/app/components/base/form'
|
||||
import { createInputFieldSchema } from './schema'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
|
||||
import { useConfigurations, useHiddenFieldNames } from './hooks'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import ShowAllSettings from './show-all-settings'
|
||||
import Button from '@/app/components/base/button'
|
||||
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
|
||||
import InitialFields from './initial-fields'
|
||||
import HiddenFields from './hidden-fields'
|
||||
|
||||
const InputFieldForm = ({
|
||||
initialData,
|
||||
|
|
@ -59,25 +57,24 @@ const InputFieldForm = ({
|
|||
})
|
||||
|
||||
const [showAllSettings, setShowAllSettings] = useState(false)
|
||||
const type = useStore(inputFieldForm.store, state => state.values.type)
|
||||
const options = useStore(inputFieldForm.store, state => state.values.options)
|
||||
|
||||
const setFieldValue = useCallback((fieldName: DeepKeys<FormData>, value: any) => {
|
||||
inputFieldForm.setFieldValue(fieldName, value)
|
||||
}, [inputFieldForm])
|
||||
|
||||
const hiddenFieldNames = useHiddenFieldNames(type)
|
||||
const { initialConfigurations, hiddenConfigurations } = useConfigurations({
|
||||
type,
|
||||
options,
|
||||
setFieldValue,
|
||||
const InitialFieldsComp = InitialFields({
|
||||
initialData,
|
||||
supportFile,
|
||||
})
|
||||
const HiddenFieldsComp = HiddenFields({
|
||||
initialData,
|
||||
})
|
||||
|
||||
const handleShowAllSettings = useCallback(() => {
|
||||
setShowAllSettings(true)
|
||||
}, [])
|
||||
|
||||
const ShowAllSettingComp = ShowAllSettings({
|
||||
initialData,
|
||||
handleShowAllSettings,
|
||||
})
|
||||
|
||||
return (
|
||||
<form
|
||||
className='w-full'
|
||||
|
|
@ -88,30 +85,13 @@ const InputFieldForm = ({
|
|||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 px-4 py-2'>
|
||||
{initialConfigurations.map((config, index) => {
|
||||
const FieldComponent = InputField<FormData>({
|
||||
initialData,
|
||||
config,
|
||||
})
|
||||
return <FieldComponent key={`${config.type}-${index}`} form={inputFieldForm} />
|
||||
})}
|
||||
<InitialFieldsComp form={inputFieldForm} />
|
||||
<Divider type='horizontal' />
|
||||
{!showAllSettings && (
|
||||
<ShowAllSettings
|
||||
handleShowAllSettings={handleShowAllSettings}
|
||||
description={hiddenFieldNames}
|
||||
/>
|
||||
<ShowAllSettingComp form={inputFieldForm} />
|
||||
)}
|
||||
{showAllSettings && (
|
||||
<>
|
||||
{hiddenConfigurations.map((config, index) => {
|
||||
const FieldComponent = InputField<FormData>({
|
||||
initialData,
|
||||
config,
|
||||
})
|
||||
return <FieldComponent key={`hidden-${config.type}-${index}`} form={inputFieldForm} />
|
||||
})}
|
||||
</>
|
||||
<HiddenFieldsComp form={inputFieldForm} />
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center justify-end gap-x-2 p-4 pt-2'>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import { withForm } from '@/app/components/base/form'
|
||||
import type { FormData } from './types'
|
||||
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
|
||||
import type { DeepKeys } from '@tanstack/react-form'
|
||||
import { useConfigurations } from './hooks'
|
||||
|
||||
type InitialFieldsProps = {
|
||||
initialData?: FormData
|
||||
supportFile: boolean
|
||||
}
|
||||
|
||||
const InitialFields = ({
|
||||
initialData,
|
||||
supportFile,
|
||||
}: InitialFieldsProps) => withForm({
|
||||
defaultValues: initialData,
|
||||
render: function Render({
|
||||
form,
|
||||
}) {
|
||||
const setFieldValue = useCallback((fieldName: DeepKeys<FormData>, value: any) => {
|
||||
form.setFieldValue(fieldName, value)
|
||||
}, [form])
|
||||
|
||||
const initialConfigurations = useConfigurations({
|
||||
setFieldValue,
|
||||
supportFile,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{initialConfigurations.map((config, index) => {
|
||||
const FieldComponent = InputField<FormData>({
|
||||
initialData,
|
||||
config,
|
||||
})
|
||||
return <FieldComponent key={index} form={form} />
|
||||
})}
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default InitialFields
|
||||
|
|
@ -1,30 +1,43 @@
|
|||
import React from 'react'
|
||||
import { withForm } from '@/app/components/base/form'
|
||||
import type { FormData } from './types'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import { useHiddenFieldNames } from './hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightSLine } from '@remixicon/react'
|
||||
|
||||
type ShowAllSettingsProps = {
|
||||
description: string
|
||||
initialData?: FormData
|
||||
handleShowAllSettings: () => void
|
||||
}
|
||||
|
||||
const ShowAllSettings = ({
|
||||
description,
|
||||
initialData,
|
||||
handleShowAllSettings,
|
||||
}: ShowAllSettingsProps) => {
|
||||
const { t } = useTranslation()
|
||||
}: ShowAllSettingsProps) => withForm({
|
||||
defaultValues: initialData,
|
||||
render: function Render({
|
||||
form,
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const type = useStore(form.store, state => state.values.type)
|
||||
|
||||
return (
|
||||
<div className='flex cursor-pointer items-center gap-x-4' onClick={handleShowAllSettings}>
|
||||
<div className='flex grow flex-col'>
|
||||
<span className='system-sm-medium flex min-h-6 items-center text-text-secondary'>
|
||||
{t('appDebug.variableConfig.showAllSettings')}
|
||||
</span>
|
||||
<span className='body-xs-regular pb-0.5 text-text-tertiary first-letter:capitalize'>
|
||||
{description}
|
||||
</span>
|
||||
const hiddenFieldNames = useHiddenFieldNames(type)
|
||||
|
||||
return (
|
||||
<div className='flex cursor-pointer items-center gap-x-4' onClick={handleShowAllSettings}>
|
||||
<div className='flex grow flex-col'>
|
||||
<span className='system-sm-medium flex min-h-6 items-center text-text-secondary'>
|
||||
{t('appDebug.variableConfig.showAllSettings')}
|
||||
</span>
|
||||
<span className='body-xs-regular pb-0.5 text-text-tertiary first-letter:capitalize'>
|
||||
{hiddenFieldNames}
|
||||
</span>
|
||||
</div>
|
||||
<RiArrowRightSLine className='h-4 w-4 shrink-0 text-text-secondary' />
|
||||
</div>
|
||||
<RiArrowRightSLine className='h-4 w-4 shrink-0 text-text-secondary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default ShowAllSettings
|
||||
|
|
|
|||
Loading…
Reference in New Issue