Add Litegraph multi-select & group nesting (#1416)

* Allow nested groups

Pass all selected items (new litegraph feature) instead of just selected nodes.

* Allow nested groups - context menus

* Update litegraph

* Update litegraph (Select all / Delete selected)

* Add playwright test

* nit

* Update test expectations [skip ci]

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
filtered
2024-11-04 10:08:42 +11:00
committed by GitHub
parent 0a784d9236
commit 3ba776e6ca
10 changed files with 267 additions and 77 deletions

View File

@@ -0,0 +1,227 @@
{
"last_node_id": 3,
"last_link_id": 0,
"nodes": [
{
"id": 2,
"type": "KSampler",
"pos": {
"0": 420,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": {
"0": 820,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 1,
"type": "KSampler",
"pos": {
"0": 30,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [
{
"id": 0,
"title": "Group",
"bounding": [
406.9701232910156,
59.079444885253906,
335,
345.6000061035156
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
},
{
"id": 3,
"title": "Group Parent",
"bounding": [
796.9703979492188,
14.796443939208984,
355,
399.20001220703125
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
},
{
"id": 2,
"title": "Group Child",
"bounding": [
806.9703979492188,
58.39643096923828,
335,
345.6000061035156
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,6 +1,16 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Item Interaction', () => {
test('Can select/delete all items', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('mixed_graph_items')
await comfyPage.canvas.press('Control+a')
await expect(comfyPage.canvas).toHaveScreenshot('selected-all.png')
await comfyPage.canvas.press('Delete')
await expect(comfyPage.canvas).toHaveScreenshot('deleted-all.png')
})
})
test.describe('Node Interaction', () => {
test('Can enter prompt', async ({ comfyPage }) => {
const textBox = comfyPage.widgetTextBox
@@ -12,7 +22,7 @@ test.describe('Node Interaction', () => {
})
test.describe('Node Selection', () => {
const multiSelectModifiers = ['Control', 'Shift', 'Meta']
const multiSelectModifiers = ['Control', 'Shift', 'Meta'] as const
multiSelectModifiers.forEach((modifier) => {
test(`Can add multiple nodes to selection using ${modifier}+Click`, async ({

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

8
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.3.31",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/litegraph": "^0.8.15",
"@comfyorg/litegraph": "^0.8.17",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
"axios": "^1.7.4",
@@ -1911,9 +1911,9 @@
"dev": true
},
"node_modules/@comfyorg/litegraph": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.15.tgz",
"integrity": "sha512-Wq3QxgBNs93cR7gN8aboKbsvmVoWmj9kIxuI1wm1xZUMEI3PfpwGVhRLjABxk5AMtxFOOMiQIcJvbYTAoW+itQ==",
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.17.tgz",
"integrity": "sha512-koNvj7Bei1MWELlLC9OuvGb2TGTzfqxExYbl/m0bK6taoLk82Xu3mnHz2T9iuxZMih+jwnriI6nNjCFvu1jTfA==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {

View File

@@ -70,7 +70,7 @@
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/litegraph": "^0.8.15",
"@comfyorg/litegraph": "^0.8.17",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
"axios": "^1.7.4",

View File

@@ -1,66 +1,17 @@
// @ts-strict-ignore
import { LGraphGroup } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { LGraphCanvas } from '@comfyorg/litegraph'
import type { Positionable } from '@comfyorg/litegraph/dist/interfaces'
import type { LGraphNode } from '@comfyorg/litegraph'
function setNodeMode(node, mode) {
function setNodeMode(node: LGraphNode, mode: number) {
node.mode = mode
node.graph.change()
node.graph?.change()
}
function addNodesToGroup(group, nodes = []) {
var x1, y1, x2, y2
var nx1, ny1, nx2, ny2
var node
x1 = y1 = x2 = y2 = -1
nx1 = ny1 = nx2 = ny2 = -1
for (var n of [group.nodes, nodes]) {
for (var i in n) {
node = n[i]
nx1 = node.pos[0]
ny1 = node.pos[1]
nx2 = node.pos[0] + node.size[0]
ny2 = node.pos[1] + node.size[1]
if (node.type != 'Reroute') {
ny1 -= LiteGraph.NODE_TITLE_HEIGHT
}
if (node.flags?.collapsed) {
ny2 = ny1 + LiteGraph.NODE_TITLE_HEIGHT
if (node?._collapsed_width) {
nx2 = nx1 + Math.round(node._collapsed_width)
}
}
if (x1 == -1 || nx1 < x1) {
x1 = nx1
}
if (y1 == -1 || ny1 < y1) {
y1 = ny1
}
if (x2 == -1 || nx2 > x2) {
x2 = nx2
}
if (y2 == -1 || ny2 > y2) {
y2 = ny2
}
}
}
var padding = 10
y1 = y1 - Math.round(group.font_size * 1.4)
group.pos = [x1 - padding, y1 - padding]
group.size = [x2 - x1 + padding * 2, y2 - y1 + padding * 2]
function addNodesToGroup(group: LGraphGroup, items: Iterable<Positionable>) {
group.resizeTo([...group.children, ...items])
}
app.registerExtension({
@@ -68,7 +19,9 @@ app.registerExtension({
setup() {
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
// graph_mouse
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
LGraphCanvas.prototype.getCanvasMenuOptions = function (
this: LGraphCanvas
) {
const options = orig.apply(this, arguments)
const group = this.graph.getGroupOnPos(
this.graph_mouse[0],
@@ -77,11 +30,11 @@ app.registerExtension({
if (!group) {
options.push({
content: 'Add Group For Selected Nodes',
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
disabled: !this.selectedItems?.size,
callback: () => {
const group = new LGraphGroup()
addNodesToGroup(group, this.selected_nodes)
app.canvas.graph.add(group)
addNodesToGroup(group, this.selectedItems)
this.graph.add(group)
this.graph.change()
}
})
@@ -95,9 +48,9 @@ app.registerExtension({
options.push({
content: 'Add Selected Nodes To Group',
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
disabled: !this.selectedItems?.size,
callback: () => {
addNodesToGroup(group, this.selected_nodes)
addNodesToGroup(group, this.selectedItems)
this.graph.change()
}
})
@@ -122,7 +75,8 @@ app.registerExtension({
options.push({
content: 'Fit Group To Nodes',
callback: () => {
addNodesToGroup(group)
group.recomputeInsideNodes()
group.resizeTo(group.children)
this.graph.change()
}
})

View File

@@ -361,14 +361,13 @@ export const useCommandStore = defineStore('command', () => {
label: 'Group Selected Nodes',
versionAdded: '1.3.7',
function: () => {
if (
!app.canvas.selected_nodes ||
Object.keys(app.canvas.selected_nodes).length === 0
) {
const { canvas } = app
if (!canvas.selectedItems?.size) {
useToastStore().add({
severity: 'error',
summary: 'No nodes selected',
detail: 'Please select nodes to group',
summary: 'Nothing to group',
detail:
'Please select the nodes (or other groups) to create a group for',
life: 3000
})
return
@@ -377,8 +376,8 @@ export const useCommandStore = defineStore('command', () => {
const padding = useSettingStore().get(
'Comfy.GroupSelectedNodes.Padding'
)
group.addNodes(Object.values(app.canvas.selected_nodes), padding)
app.canvas.graph.add(group)
group.resizeTo(canvas.selectedItems, padding)
canvas.graph.add(group)
useTitleEditorStore().titleEditorTarget = group
}
},