mirror of https://github.com/langgenius/dify.git
feat: add scroll to selected node button in workflow header (#24030)
Co-authored-by: zhangxuhe1 <xuhezhang6@gmail.com>
This commit is contained in:
parent
ae25f90f34
commit
f214eeb7b1
|
|
@ -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' />
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Alle Probleme wurden gelöst',
|
||||
change: 'Ändern',
|
||||
optional: '(optional)',
|
||||
moveToThisNode: 'Bewege zu diesem Knoten',
|
||||
selectNextStep: 'Nächsten Schritt auswählen',
|
||||
addNextStep: 'Fügen Sie den nächsten Schritt in diesem Arbeitsablauf hinzu.',
|
||||
organizeBlocks: 'Knoten organisieren',
|
||||
changeBlock: 'Knoten ändern',
|
||||
maximize: 'Maximiere die Leinwand',
|
||||
minimize: 'Vollbildmodus beenden',
|
||||
scrollToSelectedNode: 'Zum ausgewählten Knoten scrollen',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ const translation = {
|
|||
addNextStep: 'Add the next step in this workflow',
|
||||
selectNextStep: 'Select Next Step',
|
||||
runThisStep: 'Run this step',
|
||||
moveToThisNode: 'Move to this node',
|
||||
checklist: 'Checklist',
|
||||
checklistTip: 'Make sure all issues are resolved before publishing',
|
||||
checklistResolved: 'All issues are resolved',
|
||||
|
|
@ -329,6 +328,7 @@ const translation = {
|
|||
optional: '(optional)',
|
||||
maximize: 'Maximize Canvas',
|
||||
minimize: 'Exit Full Screen',
|
||||
scrollToSelectedNode: 'Scroll to selected node',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Se resolvieron todos los problemas',
|
||||
change: 'Cambiar',
|
||||
optional: '(opcional)',
|
||||
moveToThisNode: 'Mueve a este nodo',
|
||||
organizeBlocks: 'Organizar nodos',
|
||||
addNextStep: 'Agrega el siguiente paso en este flujo de trabajo',
|
||||
changeBlock: 'Cambiar Nodo',
|
||||
selectNextStep: 'Seleccionar siguiente paso',
|
||||
maximize: 'Maximizar Canvas',
|
||||
minimize: 'Salir de pantalla completa',
|
||||
scrollToSelectedNode: 'Desplácese hasta el nodo seleccionado',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'تمام مسائل حل شدهاند',
|
||||
change: 'تغییر',
|
||||
optional: '(اختیاری)',
|
||||
moveToThisNode: 'به این گره بروید',
|
||||
selectNextStep: 'گام بعدی را انتخاب کنید',
|
||||
changeBlock: 'تغییر گره',
|
||||
organizeBlocks: 'گرهها را سازماندهی کنید',
|
||||
addNextStep: 'مرحله بعدی را به این فرآیند اضافه کنید',
|
||||
minimize: 'خروج از حالت تمام صفحه',
|
||||
maximize: 'بیشینهسازی بوم',
|
||||
scrollToSelectedNode: 'به گره انتخاب شده بروید',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Tous les problèmes ont été résolus',
|
||||
change: 'Modifier',
|
||||
optional: '(facultatif)',
|
||||
moveToThisNode: 'Déplacer vers ce nœud',
|
||||
organizeBlocks: 'Organiser les nœuds',
|
||||
addNextStep: 'Ajoutez la prochaine étape dans ce flux de travail',
|
||||
selectNextStep: 'Sélectionner la prochaine étape',
|
||||
changeBlock: 'Changer de nœud',
|
||||
maximize: 'Maximiser le Canvas',
|
||||
minimize: 'Sortir du mode plein écran',
|
||||
scrollToSelectedNode: 'Faites défiler jusqu’au nœud sélectionné',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -327,13 +327,13 @@ const translation = {
|
|||
checklistResolved: 'सभी समस्याएं हल हो गई हैं',
|
||||
change: 'बदलें',
|
||||
optional: '(वैकल्पिक)',
|
||||
moveToThisNode: 'इस नोड पर जाएं',
|
||||
changeBlock: 'नोड बदलें',
|
||||
addNextStep: 'इस कार्यप्रवाह में अगला कदम जोड़ें',
|
||||
selectNextStep: 'अगला कदम चुनें',
|
||||
organizeBlocks: 'नोड्स का आयोजन करें',
|
||||
minimize: 'पूर्ण स्क्रीन से बाहर निकलें',
|
||||
maximize: 'कैनवास का अधिकतम लाभ उठाएँ',
|
||||
scrollToSelectedNode: 'चुने गए नोड पर स्क्रॉल करें',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -330,13 +330,13 @@ const translation = {
|
|||
checklistResolved: 'Tutti i problemi sono risolti',
|
||||
change: 'Cambia',
|
||||
optional: '(opzionale)',
|
||||
moveToThisNode: 'Sposta a questo nodo',
|
||||
changeBlock: 'Cambia Nodo',
|
||||
selectNextStep: 'Seleziona il prossimo passo',
|
||||
organizeBlocks: 'Organizzare i nodi',
|
||||
addNextStep: 'Aggiungi il prossimo passo in questo flusso di lavoro',
|
||||
minimize: 'Esci dalla modalità schermo intero',
|
||||
maximize: 'Massimizza Canvas',
|
||||
scrollToSelectedNode: 'Scorri fino al nodo selezionato',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -326,9 +326,9 @@ const translation = {
|
|||
organizeBlocks: 'ノード整理',
|
||||
change: '変更',
|
||||
optional: '(任意)',
|
||||
moveToThisNode: 'このノードに移動する',
|
||||
maximize: 'キャンバスを最大化する',
|
||||
minimize: '全画面を終了する',
|
||||
scrollToSelectedNode: '選択したノードまでスクロール',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -336,13 +336,13 @@ const translation = {
|
|||
checklistResolved: '모든 문제가 해결되었습니다',
|
||||
change: '변경',
|
||||
optional: '(선택사항)',
|
||||
moveToThisNode: '이 노드로 이동',
|
||||
organizeBlocks: '노드 정리하기',
|
||||
selectNextStep: '다음 단계 선택',
|
||||
changeBlock: '노드 변경',
|
||||
addNextStep: '이 워크플로우에 다음 단계를 추가하세요.',
|
||||
minimize: '전체 화면 종료',
|
||||
maximize: '캔버스 전체 화면',
|
||||
scrollToSelectedNode: '선택한 노드로 스크롤',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Wszystkie problemy zostały rozwiązane',
|
||||
change: 'Zmień',
|
||||
optional: '(opcjonalne)',
|
||||
moveToThisNode: 'Przenieś do tego węzła',
|
||||
selectNextStep: 'Wybierz następny krok',
|
||||
addNextStep: 'Dodaj następny krok w tym procesie roboczym',
|
||||
changeBlock: 'Zmień węzeł',
|
||||
organizeBlocks: 'Organizuj węzły',
|
||||
minimize: 'Wyjdź z trybu pełnoekranowego',
|
||||
maximize: 'Maksymalizuj płótno',
|
||||
scrollToSelectedNode: 'Przewiń do wybranego węzła',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Todos os problemas foram resolvidos',
|
||||
change: 'Mudar',
|
||||
optional: '(opcional)',
|
||||
moveToThisNode: 'Mova-se para este nó',
|
||||
changeBlock: 'Mudar Nó',
|
||||
addNextStep: 'Adicione o próximo passo neste fluxo de trabalho',
|
||||
organizeBlocks: 'Organizar nós',
|
||||
selectNextStep: 'Selecione o próximo passo',
|
||||
maximize: 'Maximize Canvas',
|
||||
minimize: 'Sair do Modo Tela Cheia',
|
||||
scrollToSelectedNode: 'Role até o nó selecionado',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Toate problemele au fost rezolvate',
|
||||
change: 'Schimbă',
|
||||
optional: '(opțional)',
|
||||
moveToThisNode: 'Mutați la acest nod',
|
||||
organizeBlocks: 'Organizează nodurile',
|
||||
addNextStep: 'Adăugați următorul pas în acest flux de lucru',
|
||||
changeBlock: 'Schimbă nodul',
|
||||
selectNextStep: 'Selectați Pasul Următor',
|
||||
maximize: 'Maximize Canvas',
|
||||
minimize: 'Iesi din modul pe tot ecranul',
|
||||
scrollToSelectedNode: 'Derulați la nodul selectat',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Все проблемы решены',
|
||||
change: 'Изменить',
|
||||
optional: '(необязательно)',
|
||||
moveToThisNode: 'Перейдите к этому узлу',
|
||||
selectNextStep: 'Выберите следующий шаг',
|
||||
organizeBlocks: 'Организовать узлы',
|
||||
addNextStep: 'Добавьте следующий шаг в этот рабочий процесс',
|
||||
changeBlock: 'Изменить узел',
|
||||
minimize: 'Выйти из полноэкранного режима',
|
||||
maximize: 'Максимизировать холст',
|
||||
scrollToSelectedNode: 'Прокрутите до выбранного узла',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -318,7 +318,6 @@ const translation = {
|
|||
runThisStep: 'Izvedi ta korak',
|
||||
changeBlock: 'Spremeni vozlišče',
|
||||
addNextStep: 'Dodajte naslednji korak v ta delovni potek',
|
||||
moveToThisNode: 'Premakni se na to vozlišče',
|
||||
checklistTip: 'Prepričajte se, da so vse težave rešene, preden objavite.',
|
||||
selectNextStep: 'Izberi naslednji korak',
|
||||
helpLink: 'Pomočna povezava',
|
||||
|
|
@ -329,6 +328,7 @@ const translation = {
|
|||
minimize: 'Izhod iz celotnega zaslona',
|
||||
maximize: 'Maksimiziraj platno',
|
||||
optional: '(neobvezno)',
|
||||
scrollToSelectedNode: 'Pomaknite se do izbranega vozlišča',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'ปัญหาทั้งหมดได้รับการแก้ไขแล้ว',
|
||||
change: 'เปลี่ยน',
|
||||
optional: '(ไม่บังคับ)',
|
||||
moveToThisNode: 'ย้ายไปที่โหนดนี้',
|
||||
organizeBlocks: 'จัดระเบียบโหนด',
|
||||
addNextStep: 'เพิ่มขั้นตอนถัดไปในกระบวนการทำงานนี้',
|
||||
changeBlock: 'เปลี่ยนโหนด',
|
||||
selectNextStep: 'เลือกขั้นตอนถัดไป',
|
||||
minimize: 'ออกจากโหมดเต็มหน้าจอ',
|
||||
maximize: 'เพิ่มประสิทธิภาพผ้าใบ',
|
||||
scrollToSelectedNode: 'เลื่อนไปยังโหนดที่เลือก',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Tüm sorunlar çözüldü',
|
||||
change: 'Değiştir',
|
||||
optional: '(isteğe bağlı)',
|
||||
moveToThisNode: 'Bu düğüme geç',
|
||||
changeBlock: 'Düğümü Değiştir',
|
||||
addNextStep: 'Bu iş akışına bir sonraki adımı ekleyin',
|
||||
organizeBlocks: 'Düğümleri düzenle',
|
||||
selectNextStep: 'Sonraki Adımı Seç',
|
||||
minimize: 'Tam Ekrandan Çık',
|
||||
maximize: 'Kanvası Maksimize Et',
|
||||
scrollToSelectedNode: 'Seçili düğüme kaydırma',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Всі проблеми вирішені',
|
||||
change: 'Змінити',
|
||||
optional: '(необов\'язково)',
|
||||
moveToThisNode: 'Перемістіть до цього вузла',
|
||||
organizeBlocks: 'Організуйте вузли',
|
||||
changeBlock: 'Змінити вузол',
|
||||
selectNextStep: 'Виберіть наступний крок',
|
||||
addNextStep: 'Додайте наступний крок у цей робочий процес',
|
||||
minimize: 'Вийти з повноекранного режиму',
|
||||
maximize: 'Максимізувати полотно',
|
||||
scrollToSelectedNode: 'Прокрутіть до вибраного вузла',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -315,13 +315,13 @@ const translation = {
|
|||
checklistResolved: 'Tất cả các vấn đề đã được giải quyết',
|
||||
change: 'Thay đổi',
|
||||
optional: '(tùy chọn)',
|
||||
moveToThisNode: 'Di chuyển đến nút này',
|
||||
changeBlock: 'Thay đổi Node',
|
||||
selectNextStep: 'Chọn bước tiếp theo',
|
||||
organizeBlocks: 'Tổ chức các nút',
|
||||
addNextStep: 'Thêm bước tiếp theo trong quy trình này',
|
||||
maximize: 'Tối đa hóa Canvas',
|
||||
minimize: 'Thoát chế độ toàn màn hình',
|
||||
scrollToSelectedNode: 'Cuộn đến nút đã chọn',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -326,9 +326,9 @@ const translation = {
|
|||
organizeBlocks: '整理节点',
|
||||
change: '更改',
|
||||
optional: '(选填)',
|
||||
moveToThisNode: '定位至此节点',
|
||||
maximize: '最大化画布',
|
||||
minimize: '退出最大化',
|
||||
scrollToSelectedNode: '滚动至选中节点',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
|
|
@ -319,9 +319,9 @@ const translation = {
|
|||
organizeBlocks: '整理節點',
|
||||
change: '更改',
|
||||
optional: '(選擇性)',
|
||||
moveToThisNode: '定位至此節點',
|
||||
minimize: '退出全螢幕',
|
||||
maximize: '最大化畫布',
|
||||
scrollToSelectedNode: '捲動至選取的節點',
|
||||
},
|
||||
nodes: {
|
||||
common: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue