mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
feat: add scroll to selected node button in workflow header (#24030)
Co-authored-by: zhangxuhe1 <xuhezhang6@gmail.com>
This commit is contained in:
@ -17,6 +17,7 @@ import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import EnvButton from './env-button'
|
||||
import VersionHistoryButton from './version-history-button'
|
||||
import ScrollToSelectedNodeButton from './scroll-to-selected-node-button'
|
||||
|
||||
export type HeaderInNormalProps = {
|
||||
components?: {
|
||||
@ -53,10 +54,13 @@ const HeaderInNormal = ({
|
||||
}, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex w-full items-center justify-between'>
|
||||
<div>
|
||||
<EditingTitle />
|
||||
</div>
|
||||
<div>
|
||||
<ScrollToSelectedNodeButton />
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
{components?.left}
|
||||
<EnvButton disabled={nodesReadOnly} />
|
||||
@ -65,7 +69,7 @@ const HeaderInNormal = ({
|
||||
{components?.middle}
|
||||
<VersionHistoryButton onClick={onStartRestoring} />
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import type { FC } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { CommonNodeType } from '../types'
|
||||
import { scrollToWorkflowNode } from '../utils/node-navigation'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const ScrollToSelectedNodeButton: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const selectedNode = nodes.find(node => node.data.selected)
|
||||
|
||||
const handleScrollToSelectedNode = useCallback(() => {
|
||||
if (!selectedNode) return
|
||||
scrollToWorkflowNode(selectedNode.id)
|
||||
}, [selectedNode])
|
||||
|
||||
if (!selectedNode)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'system-xs-medium flex h-6 cursor-pointer items-center justify-center whitespace-nowrap rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-3 text-text-tertiary shadow-lg backdrop-blur-sm transition-colors duration-200 hover:text-text-accent',
|
||||
)}
|
||||
onClick={handleScrollToSelectedNode}
|
||||
>
|
||||
{t('workflow.panel.scrollToSelectedNode')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScrollToSelectedNodeButton
|
||||
@ -1,63 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { RiCrosshairLine } from '@remixicon/react'
|
||||
import { useReactFlow, useStore } from 'reactflow'
|
||||
import TooltipPlus from '@/app/components/base/tooltip'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow-app/hooks'
|
||||
|
||||
type NodePositionProps = {
|
||||
nodeId: string
|
||||
}
|
||||
const NodePosition = ({
|
||||
nodeId,
|
||||
}: NodePositionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const reactflow = useReactFlow()
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
nodePosition,
|
||||
nodeWidth,
|
||||
nodeHeight,
|
||||
} = useStore(useShallow((s) => {
|
||||
const nodes = s.getNodes()
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
|
||||
return {
|
||||
nodePosition: currentNode.position,
|
||||
nodeWidth: currentNode.width,
|
||||
nodeHeight: currentNode.height,
|
||||
}
|
||||
}))
|
||||
const transform = useStore(s => s.transform)
|
||||
|
||||
if (!nodePosition || !nodeWidth || !nodeHeight) return null
|
||||
|
||||
const workflowContainer = document.getElementById('workflow-container')
|
||||
const zoom = transform[2]
|
||||
|
||||
const { clientWidth, clientHeight } = workflowContainer!
|
||||
const { setViewport } = reactflow
|
||||
|
||||
return (
|
||||
<TooltipPlus
|
||||
popupContent={t('workflow.panel.moveToThisNode')}
|
||||
>
|
||||
<div
|
||||
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - nodeWidth * zoom) / 2 - nodePosition.x * zoom,
|
||||
y: (clientHeight - nodeHeight * zoom) / 2 - nodePosition.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
doSyncWorkflowDraft()
|
||||
}}
|
||||
>
|
||||
<RiCrosshairLine className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodePosition)
|
||||
@ -19,7 +19,6 @@ import { useShallow } from 'zustand/react/shallow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NextStep from '../next-step'
|
||||
import PanelOperator from '../panel-operator'
|
||||
import NodePosition from '@/app/components/workflow/nodes/_base/components/node-position'
|
||||
import HelpLink from '../help-link'
|
||||
import {
|
||||
DescriptionInput,
|
||||
@ -362,7 +361,6 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
<NodePosition nodeId={id}></NodePosition>
|
||||
<HelpLink nodeType={data.type} />
|
||||
<PanelOperator id={id} data={data} showHelpLink={false} />
|
||||
<div className='mx-3 h-3.5 w-[1px] bg-divider-regular' />
|
||||
|
||||
Reference in New Issue
Block a user