Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/assets/styles/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -734,8 +734,8 @@
height: 1.25rem !important;
}

.h-5 {
height: 1.25rem !important;
.h-6 {
height: 1.5rem !important;
}

.h-7 {
Expand Down Expand Up @@ -12816,6 +12816,10 @@
background-color: var(--primary-hover-light);
}

.hover\:color-primary:hover {
color: var(--color-primary)
}

.break-all {
word-break: break-all;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dag/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:",
Expand Down
93 changes: 74 additions & 19 deletions packages/dag/src/Canvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const dag = inject('dag')
const nodesPanelExpanded = inject<Ref<boolean>>('nodesPanelExpanded', ref(true))
const needsFitView = inject<Ref<boolean>>('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
Expand Down Expand Up @@ -126,9 +125,33 @@ function observeBottomBar() {
bottomBarResizeObserver.observe(bottomBarRef.value)
}

// Observe actual width of the right panel
const rightPanelRef =
useTemplateRef<InstanceType<typeof RightPanel>>('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,
})

Expand Down Expand Up @@ -232,6 +255,9 @@ onMounted(() => {
}
}

// Start observing the right panel width
observeRightPanel()

// Start observing the bottom bar height
observeBottomBar()

Expand All @@ -247,6 +273,7 @@ onUnmounted(() => {
dataflowStore.unregisterVueFlowUpdateCallback()
leftPanelResizeObserver?.disconnect()
leftPanelMutationObserver?.disconnect()
rightPanelResizeObserver?.disconnect()
bottomBarResizeObserver?.disconnect()
})

Expand All @@ -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 }
})
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -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,
})
}

Expand All @@ -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,
})
}

Expand Down Expand Up @@ -818,7 +873,7 @@ defineExpose({
</slot>
</div>

<RightPanel />
<RightPanel ref="rightPanel" />

<!-- Empty state overlay -->
<Transition name="empty-state">
Expand Down
29 changes: 29 additions & 0 deletions packages/dag/src/EditorView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -554,6 +559,17 @@ provide('isSyncTask', isSyncTask)
</div>
<div class="w-100 h-0 position-absolute header z-10 flex align-center px-3">
<div class="flex-1" />

<Transition name="locate-node-fade">
<div
v-if="dataflowStore.selectedNode"
class="position-absolute start-50 top-50 translate-middle flex h-6 text-xs cursor-pointer items-center justify-center rounded-lg px-3 shadow-lg hover:color-primary bg-overlay fw-sub font-color-light"
@click="locateSelectedNode"
>
{{ $t('packages_dag_locate_selected_node') }}
</div>
</Transition>

<TaskOperations
v-if="dataflow.id"
ref="taskOperationsRef"
Expand Down Expand Up @@ -607,6 +623,19 @@ provide('isSyncTask', isSyncTask)
.header {
top: 28px;
}

.locate-node-fade-enter-active,
.locate-node-fade-leave-active {
transition:
opacity 0.15s ease,
transform 0.15s ease;
}

.locate-node-fade-enter-from,
.locate-node-fade-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(-4px);
}
:deep(.btn-shadow) {
box-shadow:
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
Expand Down
6 changes: 4 additions & 2 deletions packages/dag/src/components/elements/CanvasNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,15 @@ const canBeTarget = computed(() => {

<template #extra>
<NodeSourceHandle
v-if="canBeSource"
v-show="canBeSource"
:connectable="canBeSource"
v-bind="$attrs"
:node="props.data"
class="canvas-node-handle z-1"
/>
<NodeTargetHandle
v-if="canBeTarget"
v-show="canBeTarget"
:connectable="canBeTarget"
v-bind="$attrs"
:node="props.data"
class="canvas-node-handle z-1"
Expand Down
69 changes: 53 additions & 16 deletions packages/dag/src/components/elements/NodeToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,59 @@ const handleToggleDisable = () => {
class="node-toolbar-actions rounded-lg p-0.5 cursor-pointer"
style="--btn-space: 2px"
>
<el-button text size="xs" @click.stop="handlePreview?.(node.id)">
<template #icon>
<i-lucide-play />
</template>
</el-button>
<el-button text size="xs" @click.stop="handleToggleDisable">
<template #icon>
<i-lucide-power-off v-if="node.disabled" />
<i-lucide-power v-else />
</template>
</el-button>
<el-button text type="danger" size="xs" @click="handleDelete">
<template #icon>
<i-lucide-trash-2 />
</template>
</el-button>
<el-tooltip
effect="light"
placement="top"
:show-arrow="false"
:show-after="300"
:hide-after="0"
:enterable="false"
:offset="4"
:content="$t('public_button_preview')"
>
<el-button text size="xs" @click.stop="handlePreview?.(node.id)">
<template #icon>
<i-lucide-play />
</template>
</el-button>
</el-tooltip>
<el-tooltip
effect="light"
placement="top"
:show-arrow="false"
:show-after="300"
:hide-after="0"
:enterable="false"
:offset="4"
:content="
node.disabled
? $t('public_button_enable')
: $t('public_button_disable')
"
>
<el-button text size="xs" @click.stop="handleToggleDisable">
<template #icon>
<i-lucide-power-off v-if="node.disabled" />
<i-lucide-power v-else />
</template>
</el-button>
</el-tooltip>
<el-tooltip
effect="light"
placement="top"
:show-arrow="false"
:show-after="300"
:hide-after="0"
:enterable="false"
:offset="4"
:content="$t('public_button_delete')"
>
<el-button text type="danger" size="xs" @click="handleDelete">
<template #icon>
<i-lucide-trash-2 />
</template>
</el-button>
</el-tooltip>
</div>
</div>
</template>
Expand Down
Loading
Loading