mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 16:36:28 +08:00
refactor(web): snippet main
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { useSnippetInputFieldActions } from '../use-snippet-input-field-actions'
|
||||
|
||||
const mockSyncInputFieldsDraft = vi.fn()
|
||||
const mockCloseEditor = vi.fn()
|
||||
const mockOpenEditor = vi.fn()
|
||||
const mockSetInputPanelOpen = vi.fn()
|
||||
const mockToggleInputPanel = vi.fn()
|
||||
|
||||
let snippetDetailStoreState: {
|
||||
editingField: SnippetInputField | null
|
||||
isEditorOpen: boolean
|
||||
isInputPanelOpen: boolean
|
||||
closeEditor: typeof mockCloseEditor
|
||||
openEditor: typeof mockOpenEditor
|
||||
setInputPanelOpen: typeof mockSetInputPanelOpen
|
||||
toggleInputPanel: typeof mockToggleInputPanel
|
||||
}
|
||||
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../../hooks/use-nodes-sync-draft', () => ({
|
||||
useNodesSyncDraft: () => ({
|
||||
syncInputFieldsDraft: mockSyncInputFieldsDraft,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: typeof snippetDetailStoreState) => unknown) => selector(snippetDetailStoreState),
|
||||
}))
|
||||
|
||||
const createField = (overrides: Partial<SnippetInputField> = {}): SnippetInputField => ({
|
||||
type: PipelineInputVarType.textInput,
|
||||
label: 'Blog URL',
|
||||
variable: 'blog_url',
|
||||
required: true,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('useSnippetInputFieldActions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
snippetDetailStoreState = {
|
||||
editingField: null,
|
||||
isEditorOpen: false,
|
||||
isInputPanelOpen: true,
|
||||
closeEditor: mockCloseEditor,
|
||||
openEditor: mockOpenEditor,
|
||||
setInputPanelOpen: mockSetInputPanelOpen,
|
||||
toggleInputPanel: mockToggleInputPanel,
|
||||
}
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
describe('Field sync', () => {
|
||||
it('should remove a field and sync the draft', () => {
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleRemoveField(0)
|
||||
})
|
||||
|
||||
expect(result.current.fields).toEqual([])
|
||||
expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith([], {
|
||||
onRefresh: expect.any(Function),
|
||||
})
|
||||
})
|
||||
|
||||
it('should append a new field and close the editor after syncing', () => {
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleSubmitField(createField({
|
||||
label: 'Topic',
|
||||
variable: 'topic',
|
||||
}))
|
||||
})
|
||||
|
||||
expect(result.current.fields).toEqual([
|
||||
createField(),
|
||||
createField({
|
||||
label: 'Topic',
|
||||
variable: 'topic',
|
||||
}),
|
||||
])
|
||||
expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith([
|
||||
createField(),
|
||||
createField({
|
||||
label: 'Topic',
|
||||
variable: 'topic',
|
||||
}),
|
||||
], {
|
||||
onRefresh: expect.any(Function),
|
||||
})
|
||||
expect(mockCloseEditor).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should reject duplicated variables without syncing', () => {
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleSubmitField(createField({
|
||||
label: 'Duplicated',
|
||||
variable: 'blog_url',
|
||||
}))
|
||||
})
|
||||
|
||||
expect(toast.error).toHaveBeenCalledWith('datasetPipeline.inputFieldPanel.error.variableDuplicate')
|
||||
expect(mockSyncInputFieldsDraft).not.toHaveBeenCalled()
|
||||
expect(mockCloseEditor).not.toHaveBeenCalled()
|
||||
expect(result.current.fields).toEqual([createField()])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Panel actions', () => {
|
||||
it('should close the editor before toggling the input panel when the panel is open', () => {
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleToggleInputPanel()
|
||||
})
|
||||
|
||||
expect(mockCloseEditor).toHaveBeenCalledTimes(1)
|
||||
expect(mockToggleInputPanel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should close the input panel and clear the editor state', () => {
|
||||
const { result } = renderHook(() => useSnippetInputFieldActions({
|
||||
snippetId: 'snippet-1',
|
||||
initialFields: [createField()],
|
||||
}))
|
||||
|
||||
act(() => {
|
||||
result.current.handleCloseInputPanel()
|
||||
})
|
||||
|
||||
expect(mockCloseEditor).toHaveBeenCalledTimes(1)
|
||||
expect(mockSetInputPanelOpen).toHaveBeenCalledWith(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,127 @@
|
||||
import { act, renderHook, waitFor } from '@testing-library/react'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useSnippetPublish } from '../use-snippet-publish'
|
||||
|
||||
const mockMutateAsync = vi.fn()
|
||||
const mockSetPublishMenuOpen = vi.fn()
|
||||
const mockUseKeyPress = vi.fn()
|
||||
|
||||
let isPublishMenuOpen = false
|
||||
let isPending = false
|
||||
let shortcutHandler: ((event: KeyboardEvent) => void) | undefined
|
||||
|
||||
vi.mock('ahooks', () => ({
|
||||
useKeyPress: (...args: Parameters<typeof mockUseKeyPress>) => mockUseKeyPress(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-snippet-workflows', () => ({
|
||||
usePublishSnippetWorkflowMutation: () => ({
|
||||
mutateAsync: mockMutateAsync,
|
||||
isPending,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: {
|
||||
isPublishMenuOpen: boolean
|
||||
setPublishMenuOpen: typeof mockSetPublishMenuOpen
|
||||
}) => unknown) => selector({
|
||||
isPublishMenuOpen,
|
||||
setPublishMenuOpen: mockSetPublishMenuOpen,
|
||||
}),
|
||||
}))
|
||||
|
||||
describe('useSnippetPublish', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
isPublishMenuOpen = false
|
||||
isPending = false
|
||||
shortcutHandler = undefined
|
||||
mockMutateAsync.mockResolvedValue(undefined)
|
||||
mockUseKeyPress.mockImplementation((_key, handler) => {
|
||||
shortcutHandler = handler
|
||||
})
|
||||
})
|
||||
|
||||
describe('Publish action', () => {
|
||||
it('should publish the snippet, close the menu, and show success feedback', async () => {
|
||||
const { result } = renderHook(() => useSnippetPublish({
|
||||
snippetId: 'snippet-1',
|
||||
section: 'orchestrate',
|
||||
}))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handlePublish()
|
||||
})
|
||||
|
||||
expect(mockMutateAsync).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
})
|
||||
expect(mockSetPublishMenuOpen).toHaveBeenCalledWith(false)
|
||||
expect(toast.success).toHaveBeenCalledWith('snippet.publishSuccess')
|
||||
})
|
||||
|
||||
it('should surface publish errors through toast feedback', async () => {
|
||||
mockMutateAsync.mockRejectedValue(new Error('publish failed'))
|
||||
|
||||
const { result } = renderHook(() => useSnippetPublish({
|
||||
snippetId: 'snippet-1',
|
||||
section: 'orchestrate',
|
||||
}))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handlePublish()
|
||||
})
|
||||
|
||||
expect(toast.error).toHaveBeenCalledWith('publish failed')
|
||||
expect(mockSetPublishMenuOpen).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Keyboard shortcut', () => {
|
||||
it('should trigger publish on ctrl+shift+p in the orchestrate section', async () => {
|
||||
renderHook(() => useSnippetPublish({
|
||||
snippetId: 'snippet-1',
|
||||
section: 'orchestrate',
|
||||
}))
|
||||
|
||||
const event = new KeyboardEvent('keydown')
|
||||
const preventDefault = vi.spyOn(event, 'preventDefault')
|
||||
|
||||
act(() => {
|
||||
shortcutHandler?.(event)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockMutateAsync).toHaveBeenCalledWith({
|
||||
params: { snippetId: 'snippet-1' },
|
||||
})
|
||||
})
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should ignore the shortcut outside the orchestrate section', () => {
|
||||
renderHook(() => useSnippetPublish({
|
||||
snippetId: 'snippet-1',
|
||||
section: 'evaluation',
|
||||
}))
|
||||
|
||||
const event = new KeyboardEvent('keydown')
|
||||
const preventDefault = vi.spyOn(event, 'preventDefault')
|
||||
|
||||
act(() => {
|
||||
shortcutHandler?.(event)
|
||||
})
|
||||
|
||||
expect(mockMutateAsync).not.toHaveBeenCalled()
|
||||
expect(preventDefault).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { SnippetInputField } from '@/models/snippet'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { useNodesSyncDraft } from '../../hooks/use-nodes-sync-draft'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
|
||||
type UseSnippetInputFieldActionsOptions = {
|
||||
snippetId: string
|
||||
initialFields: SnippetInputField[]
|
||||
}
|
||||
|
||||
export const useSnippetInputFieldActions = ({
|
||||
snippetId,
|
||||
initialFields,
|
||||
}: UseSnippetInputFieldActionsOptions) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const [fields, setFields] = useState<SnippetInputField[]>(initialFields)
|
||||
const { syncInputFieldsDraft } = useNodesSyncDraft(snippetId)
|
||||
const {
|
||||
editingField,
|
||||
isEditorOpen,
|
||||
isInputPanelOpen,
|
||||
closeEditor,
|
||||
openEditor,
|
||||
setInputPanelOpen,
|
||||
toggleInputPanel,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
editingField: state.editingField,
|
||||
isEditorOpen: state.isEditorOpen,
|
||||
isInputPanelOpen: state.isInputPanelOpen,
|
||||
closeEditor: state.closeEditor,
|
||||
openEditor: state.openEditor,
|
||||
setInputPanelOpen: state.setInputPanelOpen,
|
||||
toggleInputPanel: state.toggleInputPanel,
|
||||
})))
|
||||
|
||||
const handleSortChange = useCallback((newFields: SnippetInputField[]) => {
|
||||
setFields(newFields)
|
||||
}, [])
|
||||
|
||||
const handleRemoveField = useCallback((index: number) => {
|
||||
const nextFields = fields.filter((_, currentIndex) => currentIndex !== index)
|
||||
setFields(nextFields)
|
||||
void syncInputFieldsDraft(nextFields, {
|
||||
onRefresh: setFields,
|
||||
})
|
||||
}, [fields, syncInputFieldsDraft])
|
||||
|
||||
const handleSubmitField = useCallback((field: SnippetInputField) => {
|
||||
const originalVariable = editingField?.variable
|
||||
const duplicated = fields.some(item => item.variable === field.variable && item.variable !== originalVariable)
|
||||
|
||||
if (duplicated) {
|
||||
toast.error(t('inputFieldPanel.error.variableDuplicate', { ns: 'datasetPipeline' }))
|
||||
return
|
||||
}
|
||||
|
||||
const nextFields = originalVariable
|
||||
? fields.map(item => item.variable === originalVariable ? field : item)
|
||||
: [...fields, field]
|
||||
|
||||
setFields(nextFields)
|
||||
void syncInputFieldsDraft(nextFields, {
|
||||
onRefresh: setFields,
|
||||
})
|
||||
closeEditor()
|
||||
}, [closeEditor, editingField?.variable, fields, syncInputFieldsDraft, t])
|
||||
|
||||
const handleToggleInputPanel = useCallback(() => {
|
||||
if (isInputPanelOpen)
|
||||
closeEditor()
|
||||
toggleInputPanel()
|
||||
}, [closeEditor, isInputPanelOpen, toggleInputPanel])
|
||||
|
||||
const handleCloseInputPanel = useCallback(() => {
|
||||
closeEditor()
|
||||
setInputPanelOpen(false)
|
||||
}, [closeEditor, setInputPanelOpen])
|
||||
|
||||
return {
|
||||
editingField,
|
||||
fields,
|
||||
isEditorOpen,
|
||||
isInputPanelOpen,
|
||||
openEditor,
|
||||
closeEditor,
|
||||
handleCloseInputPanel,
|
||||
handleRemoveField,
|
||||
handleSortChange,
|
||||
handleSubmitField,
|
||||
handleToggleInputPanel,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { SnippetSection } from '@/models/snippet'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import { usePublishSnippetWorkflowMutation } from '@/service/use-snippet-workflows'
|
||||
import { useSnippetDetailStore } from '../../store'
|
||||
|
||||
type UseSnippetPublishOptions = {
|
||||
snippetId: string
|
||||
section: SnippetSection
|
||||
}
|
||||
|
||||
export const useSnippetPublish = ({
|
||||
snippetId,
|
||||
section,
|
||||
}: UseSnippetPublishOptions) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const publishSnippetMutation = usePublishSnippetWorkflowMutation(snippetId)
|
||||
const {
|
||||
isPublishMenuOpen,
|
||||
setPublishMenuOpen,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
isPublishMenuOpen: state.isPublishMenuOpen,
|
||||
setPublishMenuOpen: state.setPublishMenuOpen,
|
||||
})))
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
try {
|
||||
await publishSnippetMutation.mutateAsync({
|
||||
params: { snippetId },
|
||||
})
|
||||
setPublishMenuOpen(false)
|
||||
toast.success(t('publishSuccess'))
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('publishFailed'))
|
||||
}
|
||||
}, [publishSnippetMutation, setPublishMenuOpen, snippetId, t])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (event) => {
|
||||
if (section !== 'orchestrate' || publishSnippetMutation.isPending)
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
void handlePublish()
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
return {
|
||||
handlePublish,
|
||||
isPublishMenuOpen,
|
||||
isPublishing: publishSnippetMutation.isPending,
|
||||
setPublishMenuOpen,
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { NavIcon } from '@/app/components/app-sidebar/nav-link'
|
||||
import type { WorkflowProps } from '@/app/components/workflow'
|
||||
import type { SnippetDetailPayload, SnippetInputField, SnippetSection } from '@/models/snippet'
|
||||
import type { SnippetDetailPayload, SnippetSection } from '@/models/snippet'
|
||||
import {
|
||||
RiFlaskFill,
|
||||
RiFlaskLine,
|
||||
@@ -10,32 +10,25 @@ import {
|
||||
RiTerminalWindowLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
useKeyPress,
|
||||
} from 'ahooks'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import AppSideBar from '@/app/components/app-sidebar'
|
||||
import NavLink from '@/app/components/app-sidebar/nav-link'
|
||||
import SnippetInfo from '@/app/components/app-sidebar/snippet-info'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import Evaluation from '@/app/components/evaluation'
|
||||
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||
import { useAvailableNodesMetaData } from '@/app/components/workflow-app/hooks'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { usePublishSnippetWorkflowMutation } from '@/service/use-snippet-workflows'
|
||||
import { useConfigsMap } from '../hooks/use-configs-map'
|
||||
import { useNodesSyncDraft } from '../hooks/use-nodes-sync-draft'
|
||||
import { useSnippetRefreshDraft } from '../hooks/use-snippet-refresh-draft'
|
||||
import { useSnippetDetailStore } from '../store'
|
||||
import { useSnippetInputFieldActions } from './hooks/use-snippet-input-field-actions'
|
||||
import { useSnippetPublish } from './hooks/use-snippet-publish'
|
||||
import SnippetChildren from './snippet-children'
|
||||
|
||||
type SnippetMainProps = {
|
||||
@@ -66,11 +59,8 @@ const SnippetMain = ({
|
||||
const { graph, snippet, uiMeta } = payload
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const [fields, setFields] = useState<SnippetInputField[]>(payload.inputFields)
|
||||
const publishSnippetMutation = usePublishSnippetWorkflowMutation(snippetId)
|
||||
const {
|
||||
doSyncWorkflowDraft,
|
||||
syncInputFieldsDraft,
|
||||
syncWorkflowDraftWhenPageClose,
|
||||
} = useNodesSyncDraft(snippetId)
|
||||
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
|
||||
@@ -95,29 +85,32 @@ const SnippetMain = ({
|
||||
}
|
||||
}, [workflowAvailableNodesMetaData])
|
||||
const setAppSidebarExpand = useAppStore(state => state.setAppSidebarExpand)
|
||||
const reset = useSnippetDetailStore(state => state.reset)
|
||||
const {
|
||||
editingField,
|
||||
fields,
|
||||
isEditorOpen,
|
||||
isInputPanelOpen,
|
||||
isPublishMenuOpen,
|
||||
closeEditor,
|
||||
openEditor,
|
||||
reset,
|
||||
setInputPanelOpen,
|
||||
closeEditor,
|
||||
handleCloseInputPanel,
|
||||
handleRemoveField,
|
||||
handleSortChange,
|
||||
handleSubmitField,
|
||||
handleToggleInputPanel,
|
||||
} = useSnippetInputFieldActions({
|
||||
snippetId,
|
||||
initialFields: payload.inputFields,
|
||||
})
|
||||
const {
|
||||
handlePublish,
|
||||
isPublishMenuOpen,
|
||||
isPublishing,
|
||||
setPublishMenuOpen,
|
||||
toggleInputPanel,
|
||||
} = useSnippetDetailStore(useShallow(state => ({
|
||||
editingField: state.editingField,
|
||||
isEditorOpen: state.isEditorOpen,
|
||||
isInputPanelOpen: state.isInputPanelOpen,
|
||||
isPublishMenuOpen: state.isPublishMenuOpen,
|
||||
closeEditor: state.closeEditor,
|
||||
openEditor: state.openEditor,
|
||||
reset: state.reset,
|
||||
setInputPanelOpen: state.setInputPanelOpen,
|
||||
setPublishMenuOpen: state.setPublishMenuOpen,
|
||||
toggleInputPanel: state.toggleInputPanel,
|
||||
})))
|
||||
} = useSnippetPublish({
|
||||
snippetId,
|
||||
section,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
reset()
|
||||
@@ -129,71 +122,6 @@ const SnippetMain = ({
|
||||
setAppSidebarExpand(isMobile ? mode : localeMode)
|
||||
}, [isMobile, setAppSidebarExpand])
|
||||
|
||||
const handleSortChange = (newFields: SnippetInputField[]) => {
|
||||
setFields(newFields)
|
||||
}
|
||||
|
||||
const handleRemoveField = (index: number) => {
|
||||
const nextFields = fields.filter((_, currentIndex) => currentIndex !== index)
|
||||
setFields(nextFields)
|
||||
void syncInputFieldsDraft(nextFields, {
|
||||
onRefresh: setFields,
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmitField = (field: SnippetInputField) => {
|
||||
const originalVariable = editingField?.variable
|
||||
const duplicated = fields.some(item => item.variable === field.variable && item.variable !== originalVariable)
|
||||
|
||||
if (duplicated) {
|
||||
toast.error(t('inputFieldPanel.error.variableDuplicate', { ns: 'datasetPipeline' }))
|
||||
return
|
||||
}
|
||||
|
||||
const nextFields = originalVariable
|
||||
? fields.map(item => item.variable === originalVariable ? field : item)
|
||||
: [...fields, field]
|
||||
|
||||
setFields(nextFields)
|
||||
void syncInputFieldsDraft(nextFields, {
|
||||
onRefresh: setFields,
|
||||
})
|
||||
|
||||
closeEditor()
|
||||
}
|
||||
|
||||
const handleToggleInputPanel = () => {
|
||||
if (isInputPanelOpen)
|
||||
closeEditor()
|
||||
toggleInputPanel()
|
||||
}
|
||||
|
||||
const handleCloseInputPanel = () => {
|
||||
closeEditor()
|
||||
setInputPanelOpen(false)
|
||||
}
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
try {
|
||||
await publishSnippetMutation.mutateAsync({
|
||||
params: { snippetId },
|
||||
})
|
||||
setPublishMenuOpen(false)
|
||||
toast.success(t('publishSuccess'))
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('publishFailed'))
|
||||
}
|
||||
}, [publishSnippetMutation, setPublishMenuOpen, snippetId, t])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
|
||||
if (section !== 'orchestrate' || publishSnippetMutation.isPending)
|
||||
return
|
||||
|
||||
e.preventDefault()
|
||||
void handlePublish()
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
const hooksStore = useMemo(() => {
|
||||
return {
|
||||
doSyncWorkflowDraft,
|
||||
@@ -250,7 +178,7 @@ const SnippetMain = ({
|
||||
isEditorOpen={isEditorOpen}
|
||||
isInputPanelOpen={isInputPanelOpen}
|
||||
isPublishMenuOpen={isPublishMenuOpen}
|
||||
isPublishing={publishSnippetMutation.isPending}
|
||||
isPublishing={isPublishing}
|
||||
onToggleInputPanel={handleToggleInputPanel}
|
||||
onPublishMenuOpenChange={setPublishMenuOpen}
|
||||
onCloseInputPanel={handleCloseInputPanel}
|
||||
|
||||
Reference in New Issue
Block a user