diff --git a/browser_tests/assets/reroute/single-native-reroute-default-workflow.json b/browser_tests/assets/reroute/single-native-reroute-default-workflow.json new file mode 100644 index 000000000..93f7f734a --- /dev/null +++ b/browser_tests/assets/reroute/single-native-reroute-default-workflow.json @@ -0,0 +1,259 @@ +{ + "id": "dec788c2-9829-4a5d-a1ee-d6f0a678b42a", + "revision": 0, + "last_node_id": 9, + "last_link_id": 9, + "nodes": [ + { + "id": 7, + "type": "CLIPTextEncode", + "pos": [413, 389], + "size": [425.27801513671875, 180.6060791015625], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 5 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [6] + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": ["text, watermark"] + }, + { + "id": 6, + "type": "CLIPTextEncode", + "pos": [415, 186], + "size": [422.84503173828125, 164.31304931640625], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 3 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "slot_index": 0, + "links": [4] + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "beautiful scenery nature glass bottle landscape, , purple galaxy bottle," + ] + }, + { + "id": 5, + "type": "EmptyLatentImage", + "pos": [473, 609], + "size": [315, 106], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [2] + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [512, 512, 1] + }, + { + "id": 3, + "type": "KSampler", + "pos": [863, 186], + "size": [315, 262], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 1 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 4 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 6 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 2 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "slot_index": 0, + "links": [7] + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 156680208700286, + "randomize", + 20, + 8, + "euler", + "normal", + 1 + ] + }, + { + "id": 8, + "type": "VAEDecode", + "pos": [1209, 188], + "size": [210, 46], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 7 + }, + { + "name": "vae", + "type": "VAE", + "link": 8 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [9] + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 9, + "type": "SaveImage", + "pos": [1451, 189], + "size": [210, 58], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 9 + } + ], + "outputs": [], + "properties": {}, + "widgets_values": ["ComfyUI"] + }, + { + "id": 4, + "type": "CheckpointLoaderSimple", + "pos": [26, 474], + "size": [315, 98], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "slot_index": 0, + "links": [1] + }, + { + "name": "CLIP", + "type": "CLIP", + "slot_index": 1, + "links": [3, 5] + }, + { + "name": "VAE", + "type": "VAE", + "slot_index": 2, + "links": [8] + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"] + } + ], + "links": [ + [1, 4, 0, 3, 0, "MODEL"], + [2, 5, 0, 3, 3, "LATENT"], + [3, 4, 1, 6, 0, "CLIP"], + [4, 6, 0, 3, 1, "CONDITIONING"], + [5, 4, 1, 7, 0, "CLIP"], + [6, 7, 0, 3, 2, "CONDITIONING"], + [7, 3, 0, 8, 0, "LATENT"], + [8, 4, 2, 8, 1, "VAE"], + [9, 8, 0, 9, 0, "IMAGE"] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "offset": [0, 0], + "scale": 1 + }, + "reroutes": [ + { + "id": 1, + "pos": [372.66668701171875, 415.33331298828125], + "linkIds": [3] + } + ], + "linkExtensions": [ + { + "id": 3, + "parentId": 1 + } + ], + "frontendVersion": "1.26.1" + }, + "version": 0.4 +} diff --git a/browser_tests/tests/rerouteNode.spec.ts b/browser_tests/tests/rerouteNode.spec.ts index 4c7065e3f..981f62a07 100644 --- a/browser_tests/tests/rerouteNode.spec.ts +++ b/browser_tests/tests/rerouteNode.spec.ts @@ -100,4 +100,29 @@ test.describe('LiteGraph Native Reroute Node', () => { 'native_reroute_context_menu.png' ) }) + + test('Can delete link that is connected to two reroutes', async ({ + comfyPage + }) => { + // https://github.com/Comfy-Org/ComfyUI_frontend/issues/4695 + await comfyPage.loadWorkflow( + 'reroute/single-native-reroute-default-workflow' + ) + + // To find the clickable midpoint button, we use the hardcoded value from the browser logs + // since the link is a bezier curve and not a straight line. + const middlePoint = { x: 359.4188232421875, y: 468.7716979980469 } + + // Click the middle point of the link to open the context menu. + await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y) + + // Click the "Delete" context menu option. + await comfyPage.page + .locator('.litecontextmenu .litemenu-entry', { hasText: 'Delete' }) + .click() + + await expect(comfyPage.canvas).toHaveScreenshot( + 'native_reroute_delete_from_midpoint_context_menu.png' + ) + }) }) diff --git a/browser_tests/tests/rerouteNode.spec.ts-snapshots/native-reroute-delete-from-midpoint-context-menu-chromium-linux.png b/browser_tests/tests/rerouteNode.spec.ts-snapshots/native-reroute-delete-from-midpoint-context-menu-chromium-linux.png new file mode 100644 index 000000000..40fda2b55 Binary files /dev/null and b/browser_tests/tests/rerouteNode.spec.ts-snapshots/native-reroute-delete-from-midpoint-context-menu-chromium-linux.png differ diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 14aac3f24..27f0f43b8 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -6234,9 +6234,17 @@ export class LGraphCanvas break } - case 'Delete': - graph.removeLink(segment.id) + case 'Delete': { + // segment can be a Reroute object, in which case segment.id is the reroute id + const linkId = + segment instanceof Reroute + ? segment.linkIds.values().next().value + : segment.id + if (linkId !== undefined) { + graph.removeLink(linkId) + } break + } default: } }