Skip to content

Commit 7f99e15

Browse files
benceruleanlugithub-actionschristian-byrneactions-user
authored andcommitted
Implement drop-on-canvas + linkconnectoradapter consolidation (#5898)
Implements droponcanvas functionality and a linkconnectoradapter refactor. - Drop on canvas (Shift and default) integrated via LinkConnector ‘dropped-on-canvas’ with proper CanvasPointerEvent. - LinkConnector adapter: now wraps the live canvas linkConnector (no duplicate state); added dropOnCanvas() helper. - Tests: Playwright scenarios for Shift-drop context menu/searchbox, pinned endpoint, type prefilter, and post-selection auto-connect (browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts). There are some followup PRs that will fix/refactor some more noncritical things, like the terrible slotid, the number/string nodeid confusion, etc. #5780 (snapping) <-- #5898 (drop on canvas + linkconnectoradapter refactor) <-- #5903 (fix reroute snapping) --------- Co-authored-by: github-actions <[email protected]> Co-authored-by: Christian Byrne <[email protected]> Co-authored-by: GitHub Action <[email protected]>
1 parent 53be9f2 commit 7f99e15

24 files changed

+719
-339
lines changed

browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,4 +788,171 @@ test.describe('Vue Node Link Interaction', () => {
788788
targetSlot: 2
789789
})
790790
})
791+
792+
test.describe('Release actions (Shift-drop)', () => {
793+
test('Context menu opens and endpoint is pinned on Shift-drop', async ({
794+
comfyPage,
795+
comfyMouse
796+
}) => {
797+
await comfyPage.setSetting(
798+
'Comfy.LinkRelease.ActionShift',
799+
'context menu'
800+
)
801+
802+
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
803+
expect(samplerNode).toBeTruthy()
804+
805+
const outputCenter = await getSlotCenter(
806+
comfyPage.page,
807+
samplerNode.id,
808+
0,
809+
false
810+
)
811+
812+
const dropPos = { x: outputCenter.x + 180, y: outputCenter.y - 140 }
813+
814+
await comfyMouse.move(outputCenter)
815+
await comfyPage.page.keyboard.down('Shift')
816+
try {
817+
await comfyMouse.drag(dropPos)
818+
await comfyMouse.drop()
819+
} finally {
820+
await comfyPage.page.keyboard.up('Shift').catch(() => {})
821+
}
822+
823+
// Context menu should be visible
824+
const contextMenu = comfyPage.page.locator('.litecontextmenu')
825+
await expect(contextMenu).toBeVisible()
826+
827+
// Pinned endpoint should not change with mouse movement while menu is open
828+
const before = await comfyPage.page.evaluate(() => {
829+
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
830+
return Array.isArray(snap) ? [snap[0], snap[1]] : null
831+
})
832+
expect(before).not.toBeNull()
833+
834+
// Move mouse elsewhere and verify snap position is unchanged
835+
await comfyMouse.move({ x: dropPos.x + 160, y: dropPos.y + 100 })
836+
const after = await comfyPage.page.evaluate(() => {
837+
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
838+
return Array.isArray(snap) ? [snap[0], snap[1]] : null
839+
})
840+
expect(after).toEqual(before)
841+
})
842+
843+
test('Context menu -> Search pre-filters by link type and connects after selection', async ({
844+
comfyPage,
845+
comfyMouse
846+
}) => {
847+
await comfyPage.setSetting(
848+
'Comfy.LinkRelease.ActionShift',
849+
'context menu'
850+
)
851+
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
852+
853+
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
854+
expect(samplerNode).toBeTruthy()
855+
856+
const outputCenter = await getSlotCenter(
857+
comfyPage.page,
858+
samplerNode.id,
859+
0,
860+
false
861+
)
862+
const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 120 }
863+
864+
await comfyMouse.move(outputCenter)
865+
await comfyPage.page.keyboard.down('Shift')
866+
try {
867+
await comfyMouse.drag(dropPos)
868+
await comfyMouse.drop()
869+
} finally {
870+
await comfyPage.page.keyboard.up('Shift').catch(() => {})
871+
}
872+
873+
// Open Search from the context menu
874+
await comfyPage.clickContextMenuItem('Search')
875+
876+
// Search box opens with prefilled type filter based on link type (LATENT)
877+
await expect(comfyPage.searchBox.input).toBeVisible()
878+
const chips = comfyPage.searchBox.filterChips
879+
// Ensure at least one filter chip exists and it matches the link type
880+
const chipCount = await chips.count()
881+
expect(chipCount).toBeGreaterThan(0)
882+
await expect(chips.first()).toContainText('LATENT')
883+
884+
// Choose a compatible node and verify it auto-connects
885+
await comfyPage.searchBox.fillAndSelectFirstNode('VAEDecode')
886+
await comfyPage.nextFrame()
887+
888+
// KSampler output should now have an outgoing link
889+
const samplerOutput = await samplerNode.getOutput(0)
890+
expect(await samplerOutput.getLinkCount()).toBe(1)
891+
892+
// One of the VAEDecode nodes should have an incoming link on input[0]
893+
const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode')
894+
let linked = false
895+
for (const vae of vaeNodes) {
896+
const details = await getInputLinkDetails(comfyPage.page, vae.id, 0)
897+
if (details) {
898+
expect(details.originId).toBe(samplerNode.id)
899+
linked = true
900+
break
901+
}
902+
}
903+
expect(linked).toBe(true)
904+
})
905+
906+
test('Search box opens on Shift-drop and connects after selection', async ({
907+
comfyPage,
908+
comfyMouse
909+
}) => {
910+
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
911+
912+
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
913+
expect(samplerNode).toBeTruthy()
914+
915+
const outputCenter = await getSlotCenter(
916+
comfyPage.page,
917+
samplerNode.id,
918+
0,
919+
false
920+
)
921+
const dropPos = { x: outputCenter.x + 140, y: outputCenter.y - 100 }
922+
923+
await comfyMouse.move(outputCenter)
924+
await comfyPage.page.keyboard.down('Shift')
925+
try {
926+
await comfyMouse.drag(dropPos)
927+
await comfyMouse.drop()
928+
} finally {
929+
await comfyPage.page.keyboard.up('Shift').catch(() => {})
930+
}
931+
932+
// Search box should open directly
933+
await expect(comfyPage.searchBox.input).toBeVisible()
934+
await expect(comfyPage.searchBox.filterChips.first()).toContainText(
935+
'LATENT'
936+
)
937+
938+
// Select a compatible node and verify connection
939+
await comfyPage.searchBox.fillAndSelectFirstNode('VAEDecode')
940+
await comfyPage.nextFrame()
941+
942+
const samplerOutput = await samplerNode.getOutput(0)
943+
expect(await samplerOutput.getLinkCount()).toBe(1)
944+
945+
const vaeNodes = await comfyPage.getNodeRefsByType('VAEDecode')
946+
let linked = false
947+
for (const vae of vaeNodes) {
948+
const details = await getInputLinkDetails(comfyPage.page, vae.id, 0)
949+
if (details) {
950+
expect(details.originId).toBe(samplerNode.id)
951+
linked = true
952+
break
953+
}
954+
}
955+
expect(linked).toBe(true)
956+
})
957+
})
791958
})
-893 Bytes
Loading
-1.2 KB
Loading
-931 Bytes
Loading
-966 Bytes
Loading
Loading
Loading
-787 Bytes
Loading
-967 Bytes
Loading

src/components/graph/GraphCanvas.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
113113
import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'
114114
import { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'
115115
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
116-
import { attachSlotLinkPreviewRenderer } from '@/renderer/core/canvas/links/slotLinkPreviewRenderer'
117116
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
118117
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
119118
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
@@ -401,7 +400,6 @@ onMounted(async () => {
401400
402401
// @ts-expect-error fixme ts strict error
403402
await comfyApp.setup(canvasRef.value)
404-
attachSlotLinkPreviewRenderer(comfyApp.canvas)
405403
canvasStore.canvas = comfyApp.canvas
406404
canvasStore.canvas.render_canvas_border = false
407405
workspaceStore.spinner = false

0 commit comments

Comments
 (0)