mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
fix(web): snippet graph view port
This commit is contained in:
parent
bcd87ddc58
commit
f5112928b3
@ -1,6 +1,7 @@
|
||||
import type { WorkflowProps } from '@/app/components/workflow'
|
||||
import type { SnippetDetailPayload, SnippetInputField } from '@/models/snippet'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWorkflowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import SnippetMain from '../snippet-main'
|
||||
|
||||
@ -191,7 +192,7 @@ const payload: SnippetDetailPayload = {
|
||||
}
|
||||
|
||||
const renderSnippetMain = () => {
|
||||
return render(
|
||||
return renderWorkflowComponent(
|
||||
<SnippetMain
|
||||
payload={payload}
|
||||
snippetId="snippet-1"
|
||||
@ -206,7 +207,7 @@ describe('SnippetMain', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSyncInputFieldsDraft.mockResolvedValue(undefined)
|
||||
mockPublishSnippetMutateAsync.mockResolvedValue(undefined)
|
||||
mockPublishSnippetMutateAsync.mockResolvedValue({ created_at: 1_744_000_000 })
|
||||
capturedHooksStore = undefined
|
||||
snippetDetailStoreState = {
|
||||
editingField: null,
|
||||
|
||||
@ -300,7 +300,14 @@ describe('SelectionContextmenu', () => {
|
||||
createEdge({ id: 'e2', source: 'n2', target: 'n3' }),
|
||||
]
|
||||
|
||||
const { store } = renderSelectionMenu({ nodes, edges })
|
||||
const { store } = renderSelectionMenu({
|
||||
nodes,
|
||||
edges,
|
||||
initialStoreState: {
|
||||
workflowCanvasWidth: 800,
|
||||
workflowCanvasHeight: 600,
|
||||
},
|
||||
})
|
||||
|
||||
act(() => {
|
||||
store.setState({ selectionMenu: { clientX: 120, clientY: 120 } })
|
||||
@ -348,7 +355,7 @@ describe('SelectionContextmenu', () => {
|
||||
selected: false,
|
||||
}),
|
||||
],
|
||||
viewport: { x: 0, y: 0, zoom: 1 },
|
||||
viewport: { x: 300, y: 255, zoom: 1 },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useReactFlowStore, useStoreApi } from 'reactflow'
|
||||
import { getNodesBounds, useStore as useReactFlowStore, useStoreApi } from 'reactflow'
|
||||
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
|
||||
import { useSnippetAndEvaluationPlanAccess } from '@/hooks/use-snippet-and-evaluation-plan-access'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -67,6 +67,7 @@ type ActionMenuItem = {
|
||||
}
|
||||
|
||||
const DEFAULT_SNIPPET_VIEWPORT: SnippetCanvasData['viewport'] = { x: 0, y: 0, zoom: 1 }
|
||||
const SNIPPET_VIEWPORT_PADDING = 100
|
||||
|
||||
const alignMenuItems: AlignMenuItem[] = [
|
||||
{ alignType: AlignType.Left, icon: 'i-ri-align-item-left-line', translationKey: 'operator.alignLeft' },
|
||||
@ -228,6 +229,10 @@ const getSelectedSnippetGraph = (
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
selectedNodes: Node[],
|
||||
canvasSize?: {
|
||||
width?: number
|
||||
height?: number
|
||||
},
|
||||
): SnippetCanvasData => {
|
||||
const includedNodeIds = new Set(selectedNodes.map(node => node.id))
|
||||
|
||||
@ -257,41 +262,74 @@ const getSelectedSnippetGraph = (
|
||||
const minRootX = rootNodes.length ? Math.min(...rootNodes.map(node => node.position.x)) : 0
|
||||
const minRootY = rootNodes.length ? Math.min(...rootNodes.map(node => node.position.y)) : 0
|
||||
|
||||
return {
|
||||
nodes: nodes
|
||||
.filter(node => includedNodeIds.has(node.id))
|
||||
.map((node) => {
|
||||
const isRootNode = !node.parentId || !includedNodeIds.has(node.parentId)
|
||||
const nextPosition = isRootNode
|
||||
? { x: node.position.x - minRootX, y: node.position.y - minRootY }
|
||||
: node.position
|
||||
const snippetNodes = nodes
|
||||
.filter(node => includedNodeIds.has(node.id))
|
||||
.map((node) => {
|
||||
const isRootNode = !node.parentId || !includedNodeIds.has(node.parentId)
|
||||
const nextPosition = isRootNode
|
||||
? { x: node.position.x - minRootX, y: node.position.y - minRootY }
|
||||
: node.position
|
||||
|
||||
return {
|
||||
...node,
|
||||
position: nextPosition,
|
||||
positionAbsolute: node.positionAbsolute
|
||||
? (isRootNode
|
||||
? {
|
||||
x: node.positionAbsolute.x - minRootX,
|
||||
y: node.positionAbsolute.y - minRootY,
|
||||
}
|
||||
: node.positionAbsolute)
|
||||
: undefined,
|
||||
selected: false,
|
||||
data: {
|
||||
...node.data,
|
||||
selected: false,
|
||||
_children: node.data._children?.filter(child => includedNodeIds.has(child.nodeId)),
|
||||
},
|
||||
}
|
||||
}),
|
||||
edges: edges
|
||||
.filter(edge => includedNodeIds.has(edge.source) && includedNodeIds.has(edge.target))
|
||||
.map(edge => ({
|
||||
...edge,
|
||||
return {
|
||||
...node,
|
||||
position: nextPosition,
|
||||
positionAbsolute: node.positionAbsolute
|
||||
? (isRootNode
|
||||
? {
|
||||
x: node.positionAbsolute.x - minRootX,
|
||||
y: node.positionAbsolute.y - minRootY,
|
||||
}
|
||||
: node.positionAbsolute)
|
||||
: undefined,
|
||||
selected: false,
|
||||
})),
|
||||
viewport: DEFAULT_SNIPPET_VIEWPORT,
|
||||
data: {
|
||||
...node.data,
|
||||
selected: false,
|
||||
_children: node.data._children?.filter(child => includedNodeIds.has(child.nodeId)),
|
||||
},
|
||||
}
|
||||
})
|
||||
const snippetEdges = edges
|
||||
.filter(edge => includedNodeIds.has(edge.source) && includedNodeIds.has(edge.target))
|
||||
.map(edge => ({
|
||||
...edge,
|
||||
selected: false,
|
||||
}))
|
||||
|
||||
const viewportWidth = canvasSize?.width
|
||||
const viewportHeight = canvasSize?.height
|
||||
const hasCanvasSize = !!viewportWidth && !!viewportHeight
|
||||
|
||||
const viewport = (() => {
|
||||
if (!hasCanvasSize || !snippetNodes.length)
|
||||
return DEFAULT_SNIPPET_VIEWPORT
|
||||
|
||||
const bounds = getNodesBounds(snippetNodes)
|
||||
const paddedWidth = bounds.width + SNIPPET_VIEWPORT_PADDING
|
||||
const paddedHeight = bounds.height + SNIPPET_VIEWPORT_PADDING
|
||||
const zoom = Math.min(
|
||||
viewportWidth / paddedWidth,
|
||||
viewportHeight / paddedHeight,
|
||||
1,
|
||||
)
|
||||
|
||||
if (!Number.isFinite(zoom) || zoom <= 0)
|
||||
return DEFAULT_SNIPPET_VIEWPORT
|
||||
|
||||
const centerX = bounds.x + bounds.width / 2
|
||||
const centerY = bounds.y + bounds.height / 2
|
||||
|
||||
return {
|
||||
x: viewportWidth / 2 - centerX * zoom,
|
||||
y: viewportHeight / 2 - centerY * zoom,
|
||||
zoom,
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
nodes: snippetNodes,
|
||||
edges: snippetEdges,
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,11 +394,18 @@ const SelectionContextmenu = () => {
|
||||
|
||||
const nodes = store.getState().getNodes()
|
||||
const { edges } = store.getState()
|
||||
const {
|
||||
workflowCanvasWidth,
|
||||
workflowCanvasHeight,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setSelectedGraphSnapshot(getSelectedSnippetGraph(nodes, edges, selectedNodes))
|
||||
setSelectedGraphSnapshot(getSelectedSnippetGraph(nodes, edges, selectedNodes, {
|
||||
width: workflowCanvasWidth,
|
||||
height: workflowCanvasHeight,
|
||||
}))
|
||||
setIsCreateSnippetDialogOpen(true)
|
||||
handleSelectionContextmenuCancel()
|
||||
}, [canAccessSnippetsAndEvaluation, handleSelectionContextmenuCancel, isAddToSnippetDisabled, selectedNodes, store])
|
||||
}, [canAccessSnippetsAndEvaluation, handleSelectionContextmenuCancel, isAddToSnippetDisabled, selectedNodes, store, workflowStore])
|
||||
|
||||
const handleCloseCreateSnippetDialog = useCallback(() => {
|
||||
setIsCreateSnippetDialogOpen(false)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user