diff --git a/packages/assets/styles/utilities.scss b/packages/assets/styles/utilities.scss index ee896bb9f..9df39b2f7 100644 --- a/packages/assets/styles/utilities.scss +++ b/packages/assets/styles/utilities.scss @@ -389,7 +389,7 @@ } .shadow-lg { - box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; + box-shadow: rgba(16, 24, 40, 0.03) 0px 4px 6px -2px, rgba(16, 24, 40, 0.08) 0px 12px 16px -4px !important; } .shadow-canvas { @@ -734,8 +734,8 @@ height: 1.25rem !important; } -.h-5 { - height: 1.25rem !important; +.h-6 { + height: 1.5rem !important; } .h-7 { @@ -12816,6 +12816,10 @@ background-color: var(--primary-hover-light); } +.hover\:color-primary:hover { + color: var(--color-primary) +} + .break-all { word-break: break-all; } diff --git a/packages/dag/package.json b/packages/dag/package.json index df98790a3..84a07bfbc 100644 --- a/packages/dag/package.json +++ b/packages/dag/package.json @@ -27,7 +27,7 @@ "@tap/i18n": "workspace:*", "@tap/shared": "workspace:*", "@vue-flow/background": "^1.3.2", - "@vue-flow/core": "^1.47.0", + "@vue-flow/core": "^1.48.2", "@vueuse/core": "catalog:", "axios": "catalog:", "core-js": "catalog:", diff --git a/packages/dag/src/Canvas.vue b/packages/dag/src/Canvas.vue index 71b16aff4..d657c7df4 100644 --- a/packages/dag/src/Canvas.vue +++ b/packages/dag/src/Canvas.vue @@ -74,7 +74,6 @@ const dag = inject('dag') const nodesPanelExpanded = inject>('nodesPanelExpanded', ref(true)) const needsFitView = inject>('needsFitView', ref(true)) // Bottom bar dynamic positioning -const RIGHT_PANEL_WIDTH = 600 const PANEL_MARGIN = 12 // Observe actual width of the left panel slot content @@ -126,9 +125,33 @@ function observeBottomBar() { bottomBarResizeObserver.observe(bottomBarRef.value) } +// Observe actual width of the right panel +const rightPanelRef = + useTemplateRef>('rightPanel') +const rightPanelWidth = ref(0) +let rightPanelResizeObserver: ResizeObserver | null = null + +function observeRightPanel() { + const el = rightPanelRef.value?.$el as HTMLElement | undefined + if (!el) return + rightPanelResizeObserver?.disconnect() + rightPanelResizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + rightPanelWidth.value = entry.contentRect.width + } + }) + rightPanelResizeObserver.observe(el) +} + +const rightPanelExpanded = computed(() => { + return dataflowStore.selectedNode || dataflowStore.showSettings +}) + const { layout, fitViewWithOffset } = useLayout({ nodesPanelExpanded, nodesPanelWidth: leftPanelWidth, + rightPanelExpanded, + rightPanelWidth, bottomPanelHeight: bottomBarHeight, }) @@ -232,6 +255,9 @@ onMounted(() => { } } + // Start observing the right panel width + observeRightPanel() + // Start observing the bottom bar height observeBottomBar() @@ -247,6 +273,7 @@ onUnmounted(() => { dataflowStore.unregisterVueFlowUpdateCallback() leftPanelResizeObserver?.disconnect() leftPanelMutationObserver?.disconnect() + rightPanelResizeObserver?.disconnect() bottomBarResizeObserver?.disconnect() }) @@ -256,8 +283,8 @@ const bottomBarStyle = computed(() => { ? `${leftPanelWidth.value + 20}px` : `${PANEL_MARGIN}px` const right = - dataflowStore.selectedNode || dataflowStore.showSettings - ? `${RIGHT_PANEL_WIDTH + 20}px` + rightPanelWidth.value > 0 + ? `${rightPanelWidth.value + 20}px` : `${PANEL_MARGIN}px` return { left, right } }) @@ -630,10 +657,47 @@ function clearTextSelection() { window.getSelection()?.removeAllRanges() } +/** + * Returns true if any visible node is entirely outside the current viewport. + * Uses the full container width (right panel excluded – it's closing after deselect). + */ +function hasNodesOutsideView(): boolean { + const visibleNodes = getNodes.value.filter((n) => !n.hidden) + if (!visibleNodes.length) return false + + const container = document.querySelector('#node-canvas') as HTMLElement | null + if (!container) return false + + const { x: vx, y: vy, zoom } = viewport.value + const cw = container.clientWidth + const ch = container.clientHeight + const leftOffset = nodesPanelExpanded.value ? leftPanelWidth.value : 0 + const bottomOffset = Math.max(0, bottomBarHeight.value - 44) + + return visibleNodes.some((node) => { + const w = node.dimensions?.width ?? 150 + const h = node.dimensions?.height ?? 50 + const sl = node.position.x * zoom + vx + const st = node.position.y * zoom + vy + const sr = sl + w * zoom + const sb = st + h * zoom + // Trigger if node is not fully contained within the visible area + return sl < leftOffset || sr > cw || st < 0 || sb > ch - bottomOffset + }) +} + function onPaneClick(event: MouseEvent) { + const readyFit = rightPanelExpanded.value const pos = screenToFlowCoordinate({ x: event.clientX, y: event.clientY }) dataflowStore.lastClickPosition = [pos.x, pos.y] dataflowStore.selectNode(null) + + if (readyFit && hasNodesOutsideView()) { + fitViewWithOffset({ + duration: 300, + maxZoom: 1, + }) + } } function onSelectionEnd(event: MouseEvent) { @@ -707,14 +771,6 @@ function handleLayoutGraph() { }) } -// const hasInit = ref(false) -// function onInitialized() { -// if (hasInit.value) return -// console.log('fitViewWithOffset', bottomBarHeight.value) -// fitViewWithOffset({ duration: 0, maxZoom: 1 }) -// hasInit.value = true -// } - provide('popoverTarget', popoverTarget) provide('showPopover', showPopover) provide('popoverTargetKey', popoverTargetKey) @@ -724,6 +780,7 @@ provide('onCreateConnection', onCreateConnection) provide('onDeleteConnection', onDeleteConnection) provide('onMoveNodePosition', onMoveNodePosition) provide('handleDisableNode', handleDisableNode) +provide('fitViewWithOffset', fitViewWithOffset) // Expose helper-line updaters so that NodesPanel drag can show guide lines provide( @@ -751,11 +808,10 @@ provide('clearDragHelperLines', () => { function locateNode(nodeId: string) { const node = vueFlow.findNode(nodeId) if (!node) return - vueFlow.fitView({ + fitViewWithOffset({ nodes: [nodeId], duration: 300, - maxZoom: 1, - padding: 0.5, + maxZoom: viewport.value.zoom, }) } @@ -766,11 +822,10 @@ function ensureNodesVisible(nodeIds: string[]) { if (!nodeIds.length) return const existingIds = nodeIds.filter((id) => vueFlow.findNode(id)) if (!existingIds.length) return - vueFlow.fitView({ + fitViewWithOffset({ nodes: existingIds, - duration: 200, - maxZoom: viewport.value.zoom, // 不放大,保持当前缩放 - padding: 0.2, + duration: 300, + maxZoom: viewport.value.zoom, }) } @@ -818,7 +873,7 @@ defineExpose({ - + diff --git a/packages/dag/src/EditorView.vue b/packages/dag/src/EditorView.vue index 675093bf0..67dd7ec67 100644 --- a/packages/dag/src/EditorView.vue +++ b/packages/dag/src/EditorView.vue @@ -484,6 +484,11 @@ async function checkMaterializedView() { }, 120) } +function locateSelectedNode() { + const node = dataflowStore.selectedNode as any + if (node?.id) canvasRef.value?.locateNode(node.id) +} + provide('dag', dag) provide('nodesPanelExpanded', nodesPanelExpanded) provide('buttonShowMap', buttonShowMap) @@ -554,6 +559,17 @@ provide('isSyncTask', isSyncTask)