[feat] Restore group node conversion menu with deprecated label (#4967)

## Summary
- Partially reverts commit c84218d6 to restore group node functionality
in context menus
- Adds "(Deprecated)" label to indicate the feature is deprecated
- Fixes TypeError when right-clicking on group nodes
- Re-enables tests that were disabled when the feature was removed

## Changes
1. **Restored context menu options** - Added back "Convert to Group Node
(Deprecated)" and "Manage Group Nodes" menu items
2. **Fixed null reference error** - Added null-safe operator to prevent
errors when right-clicking group nodes
3. **Re-enabled tests** - Restored 7 tests that were disabled in commit
586f8824

## Test plan
- [x] Right-click on canvas → verify "Convert to Group Node
(Deprecated)" appears
- [x] Right-click on nodes → verify the same menu option appears
- [x] Select multiple nodes and use the menu option → verify conversion
works
- [x] Right-click on group nodes → verify no errors occur
- [x] Run browser tests → verify all re-enabled tests pass

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-4967-feat-Restore-group-node-conversion-menu-with-deprecated-label-24e6d73d36508149a6f2dbef47223e94)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-08-13 15:13:18 -07:00
committed by GitHub
parent db1b81b7ff
commit 4a3bd39650
10 changed files with 65 additions and 11 deletions

View File

@@ -17,11 +17,11 @@ test.describe('Group Node', () => {
await libraryTab.open()
})
test.skip('Is added to node library sidebar', async ({ comfyPage }) => {
test('Is added to node library sidebar', async ({ comfyPage }) => {
expect(await libraryTab.getFolder('group nodes').count()).toBe(1)
})
test.skip('Can be added to canvas using node library sidebar', async ({
test('Can be added to canvas using node library sidebar', async ({
comfyPage
}) => {
const initialNodeCount = await comfyPage.getGraphNodesCount()
@@ -34,7 +34,7 @@ test.describe('Group Node', () => {
expect(await comfyPage.getGraphNodesCount()).toBe(initialNodeCount + 1)
})
test.skip('Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
test('Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
await libraryTab.getFolder(groupNodeCategory).click()
await libraryTab
.getNode(groupNodeName)
@@ -61,7 +61,7 @@ test.describe('Group Node', () => {
).toHaveLength(0)
})
test.skip('Displays preview on bookmark hover', async ({ comfyPage }) => {
test('Displays preview on bookmark hover', async ({ comfyPage }) => {
await libraryTab.getFolder(groupNodeCategory).click()
await libraryTab
.getNode(groupNodeName)
@@ -95,7 +95,7 @@ test.describe('Group Node', () => {
)
})
test.skip('Displays tooltip on title hover', async ({ comfyPage }) => {
test('Displays tooltip on title hover', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.EnableTooltips', true)
await comfyPage.convertAllNodesToGroupNode('Group Node')
await comfyPage.page.mouse.move(47, 173)
@@ -104,7 +104,7 @@ test.describe('Group Node', () => {
await expect(comfyPage.page.locator('.node-tooltip')).toBeVisible()
})
test.skip('Manage group opens with the correct group selected', async ({
test('Manage group opens with the correct group selected', async ({
comfyPage
}) => {
const makeGroup = async (name, type1, type2) => {
@@ -165,7 +165,7 @@ test.describe('Group Node', () => {
expect(visibleInputCount).toBe(2)
})
test.skip('Reconnects inputs after configuration changed via manage dialog save', async ({
test('Reconnects inputs after configuration changed via manage dialog save', async ({
comfyPage
}) => {
const expectSingleNode = async (type: string) => {

View File

@@ -24,11 +24,11 @@ test.describe('Canvas Right Click Menu', () => {
await expect(comfyPage.canvas).toHaveScreenshot('add-group-group-added.png')
})
test.skip('Can convert to group node', async ({ comfyPage }) => {
test('Can convert to group node', async ({ comfyPage }) => {
await comfyPage.select2Nodes()
await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png')
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Convert to Group Node')
await comfyPage.clickContextMenuItem('Convert to Group Node (Deprecated)')
await comfyPage.promptDialogInput.fill('GroupNode2CLIP')
await comfyPage.page.keyboard.press('Enter')
await comfyPage.promptDialogInput.waitFor({ state: 'hidden' })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -3,6 +3,7 @@ import { type NodeId } from '@/lib/litegraph/src/LGraphNode'
import {
type ExecutableLGraphNode,
type ExecutionId,
LGraphCanvas,
LGraphNode,
LiteGraph,
SubgraphNode
@@ -1172,8 +1173,7 @@ export class GroupNodeHandler {
// @ts-expect-error fixme ts strict error
getExtraMenuOptions?.apply(this, arguments)
// @ts-expect-error fixme ts strict error
let optionIndex = options.findIndex((o) => o.content === 'Outputs')
let optionIndex = options.findIndex((o) => o?.content === 'Outputs')
if (optionIndex === -1) optionIndex = options.length
else optionIndex++
options.splice(
@@ -1634,6 +1634,57 @@ export class GroupNodeHandler {
}
}
function addConvertToGroupOptions() {
// @ts-expect-error fixme ts strict error
function addConvertOption(options, index) {
const selected = Object.values(app.canvas.selected_nodes ?? {})
const disabled =
selected.length < 2 ||
selected.find((n) => GroupNodeHandler.isGroupNode(n))
options.splice(index, null, {
content: `Convert to Group Node (Deprecated)`,
disabled,
callback: convertSelectedNodesToGroupNode
})
}
// @ts-expect-error fixme ts strict error
function addManageOption(options, index) {
const groups = app.graph.extra?.groupNodes
const disabled = !groups || !Object.keys(groups).length
options.splice(index, null, {
content: `Manage Group Nodes`,
disabled,
callback: () => manageGroupNodes()
})
}
// Add to canvas
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
// @ts-expect-error fixme ts strict error
const options = getCanvasMenuOptions.apply(this, arguments)
const index = options.findIndex((o) => o?.content === 'Add Group')
const insertAt = index === -1 ? options.length - 1 : index + 2
addConvertOption(options, insertAt)
addManageOption(options, insertAt + 1)
return options
}
// Add to nodes
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
// @ts-expect-error fixme ts strict error
const options = getNodeMenuOptions.apply(this, arguments)
if (!GroupNodeHandler.isGroupNode(node)) {
const index = options.findIndex((o) => o?.content === 'Properties')
const insertAt = index === -1 ? options.length - 1 : index
addConvertOption(options, insertAt)
}
return options
}
}
const replaceLegacySeparators = (nodes: ComfyNode[]): void => {
for (const node of nodes) {
if (typeof node.type === 'string' && node.type.startsWith('workflow/')) {
@@ -1729,6 +1780,9 @@ const ext: ComfyExtension = {
}
}
],
setup() {
addConvertToGroupOptions()
},
async beforeConfigureGraph(
graphData: ComfyWorkflowJSON,
missingNodeTypes: string[]