mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-04-23 06:09:43 +08:00
feat: add format and copy functionality to JSON schema code editor with tooltip support
This commit is contained in:
parent
86b1295efa
commit
a32bc341fb
@ -5,6 +5,8 @@ import classNames from '@/utils/classnames'
|
||||
import { Editor } from '@monaco-editor/react'
|
||||
import { RiClipboardLine, RiIndentIncrease } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type CodeEditorProps = {
|
||||
value: string
|
||||
@ -22,6 +24,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
readOnly = false,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const monacoRef = useRef<any>(null)
|
||||
const editorRef = useRef<any>(null)
|
||||
@ -79,6 +82,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
</div>
|
||||
<div className='flex items-center gap-x-0.5'>
|
||||
{showFormatButton && (
|
||||
<Tooltip popupContent={t('common.operation.format')}>
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center justify-center h-6 w-6'
|
||||
@ -86,13 +90,16 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
>
|
||||
<RiIndentIncrease className='w-4 h-4 text-text-tertiary' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip popupContent={t('common.operation.copy')}>
|
||||
<button
|
||||
type='button'
|
||||
className='flex items-center justify-center h-6 w-6'
|
||||
onClick={() => copy(value)}>
|
||||
<RiClipboardLine className='w-4 h-4 text-text-tertiary' />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames('relative', editorWrapperClassName)}>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiAddCircleFill } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useVisualEditorStore } from './store'
|
||||
import { useCallback } from 'react'
|
||||
import { useMittContext } from './context'
|
||||
|
||||
const AddField = () => {
|
||||
@ -30,4 +30,4 @@ const AddField = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default AddField
|
||||
export default React.memo(AddField)
|
||||
|
@ -43,4 +43,4 @@ const Card: FC<CardProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default Card
|
||||
export default React.memo(Card)
|
||||
|
@ -75,4 +75,4 @@ const AdvancedOptions: FC<AdvancedOptionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default AdvancedOptions
|
||||
export default React.memo(AdvancedOptions)
|
||||
|
@ -12,7 +12,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { useVisualEditorStore } from '../store'
|
||||
import { useMittContext } from '../context'
|
||||
import produce from 'immer'
|
||||
import { useUnmount } from 'ahooks'
|
||||
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
|
||||
|
||||
@ -63,13 +62,16 @@ const EditCard: FC<EditCardProps> = ({
|
||||
const { emit, useSubscribe } = useMittContext()
|
||||
const blurWithActions = useRef(false)
|
||||
|
||||
const disableAddBtn = depth >= JSON_SCHEMA_MAX_DEPTH || (fields.type !== Type.object && fields.type !== ArrayType.object)
|
||||
const hasAdvancedOptions = fields.type === Type.string || fields.type === Type.number
|
||||
const disableAddBtn = depth >= JSON_SCHEMA_MAX_DEPTH || (currentFields.type !== Type.object && currentFields.type !== ArrayType.object)
|
||||
const hasAdvancedOptions = currentFields.type === Type.string || currentFields.type === Type.number
|
||||
const isAdvancedEditing = advancedEditing || isAddingNewField
|
||||
|
||||
const advancedOptions = useMemo(() => {
|
||||
return { enum: (currentFields.enum || []).join(', ') }
|
||||
}, [currentFields.enum])
|
||||
let enumValue = ''
|
||||
if (currentFields.type === Type.string || currentFields.type === Type.number)
|
||||
enumValue = (currentFields.enum || []).join(', ')
|
||||
return { enum: enumValue }
|
||||
}, [currentFields.type, currentFields.enum])
|
||||
|
||||
useSubscribe('restorePropertyName', () => {
|
||||
setCurrentFields(prev => ({ ...prev, name: fields.name }))
|
||||
@ -83,19 +85,13 @@ const EditCard: FC<EditCardProps> = ({
|
||||
setAdvancedEditing(false)
|
||||
})
|
||||
|
||||
const emitPropertyNameChange = useCallback((name: string) => {
|
||||
const newFields = produce(fields, (draft) => {
|
||||
draft.name = name
|
||||
})
|
||||
emit('propertyNameChange', { path, parentPath, oldFields: fields, fields: newFields })
|
||||
}, [fields, path, parentPath, emit])
|
||||
const emitPropertyNameChange = useCallback(() => {
|
||||
emit('propertyNameChange', { path, parentPath, oldFields: fields, fields: currentFields })
|
||||
}, [fields, currentFields, path, parentPath, emit])
|
||||
|
||||
const emitPropertyTypeChange = useCallback((type: Type | ArrayType) => {
|
||||
const newFields = produce(fields, (draft) => {
|
||||
draft.type = type
|
||||
})
|
||||
emit('propertyTypeChange', { path, parentPath, oldFields: fields, fields: newFields })
|
||||
}, [fields, path, parentPath, emit])
|
||||
const emitPropertyTypeChange = useCallback(() => {
|
||||
emit('propertyTypeChange', { path, parentPath, oldFields: fields, fields: currentFields })
|
||||
}, [fields, currentFields, path, parentPath, emit])
|
||||
|
||||
const emitPropertyRequiredToggle = useCallback(() => {
|
||||
emit('propertyRequiredToggle', { path, parentPath, oldFields: fields, fields: currentFields })
|
||||
@ -121,15 +117,15 @@ const EditCard: FC<EditCardProps> = ({
|
||||
setCurrentFields(prev => ({ ...prev, name: e.target.value }))
|
||||
}, [])
|
||||
|
||||
const handlePropertyNameBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
const handlePropertyNameBlur = useCallback(() => {
|
||||
if (isAdvancedEditing) return
|
||||
emitPropertyNameChange(e.target.value)
|
||||
emitPropertyNameChange()
|
||||
}, [isAdvancedEditing, emitPropertyNameChange])
|
||||
|
||||
const handleTypeChange = useCallback((item: TypeItem) => {
|
||||
setCurrentFields(prev => ({ ...prev, type: item.value }))
|
||||
if (isAdvancedEditing) return
|
||||
emitPropertyTypeChange(item.value)
|
||||
emitPropertyTypeChange()
|
||||
}, [isAdvancedEditing, emitPropertyTypeChange])
|
||||
|
||||
const toggleRequired = useCallback(() => {
|
||||
@ -142,16 +138,17 @@ const EditCard: FC<EditCardProps> = ({
|
||||
setCurrentFields(prev => ({ ...prev, description: e.target.value }))
|
||||
}, [])
|
||||
|
||||
const handleDescriptionBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
||||
const handleDescriptionBlur = useCallback(() => {
|
||||
if (isAdvancedEditing) return
|
||||
emitPropertyOptionsChange({ description: e.target.value, enum: fields.enum })
|
||||
}, [isAdvancedEditing, emitPropertyOptionsChange, fields])
|
||||
emitPropertyOptionsChange({ description: currentFields.description, enum: currentFields.enum })
|
||||
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
|
||||
|
||||
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
|
||||
const enumValue = options.enum.replace(/\s/g, '').split(',')
|
||||
setCurrentFields(prev => ({ ...prev, enum: enumValue }))
|
||||
if (isAdvancedEditing) return
|
||||
const enumValue = options.enum.replace(' ', '').split(',')
|
||||
emitPropertyOptionsChange({ description: fields.description, enum: enumValue })
|
||||
}, [isAdvancedEditing, emitPropertyOptionsChange, fields])
|
||||
emitPropertyOptionsChange({ description: currentFields.description, enum: enumValue })
|
||||
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
blurWithActions.current = true
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { type Field, Type } from '../../../types'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { RiArrowDropDownLine, RiArrowDropRightLine } from '@remixicon/react'
|
||||
@ -65,8 +65,8 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
setHoveringProperty(path)
|
||||
}, { wait: 50 })
|
||||
|
||||
const hasChildren = getHasChildren(schema)
|
||||
const type = getFieldType(schema)
|
||||
const hasChildren = useMemo(() => getHasChildren(schema), [schema])
|
||||
const type = useMemo(() => getFieldType(schema), [schema])
|
||||
const isHovering = hoveringProperty === path.join('.') && depth > 1
|
||||
|
||||
const handleExpand = () => {
|
||||
@ -137,7 +137,10 @@ const SchemaNode: FC<SchemaNodeProps> = ({
|
||||
schema.description ? 'h-[calc(100%-3rem)]' : 'h-[calc(100%-1.75rem)]',
|
||||
indentLeft[depth + 1],
|
||||
)}>
|
||||
<Divider type='vertical' className='bg-divider-subtle mx-0' />
|
||||
<Divider
|
||||
type='vertical'
|
||||
className={classNames('mx-0', isHovering ? 'bg-divider-deep' : 'bg-divider-subtle')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isExpanded && hasChildren && depth < JSON_SCHEMA_MAX_DEPTH && (
|
||||
|
@ -54,6 +54,7 @@ const translation = {
|
||||
regenerate: 'Regenerate',
|
||||
submit: 'Submit',
|
||||
skip: 'Skip',
|
||||
format: 'Format',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} is required',
|
||||
|
@ -54,6 +54,7 @@ const translation = {
|
||||
regenerate: '重新生成',
|
||||
submit: '提交',
|
||||
skip: '跳过',
|
||||
format: '格式化',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} 为必填项',
|
||||
|
Loading…
x
Reference in New Issue
Block a user