Skip to content
Open
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
54 changes: 53 additions & 1 deletion src/composables/useContextMenuTranslation.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
import { st, te } from '@/i18n'
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
import type {
IContextMenuOptions,
IContextMenuValue,
INodeInputSlot,
IWidget
} from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { normalizeI18nKey } from '@/utils/formatUtil'

/**
* Add translation for litegraph context menu.
*/
export const useContextMenuTranslation = () => {
// Install compatibility layer BEFORE any extensions load
legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions')

const f = LGraphCanvas.prototype.getCanvasMenuOptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional naming change for clarity?

Suggested change
const f = LGraphCanvas.prototype.getCanvasMenuOptions
const { getCanvasMenuOptions } = LGraphCanvas.prototype

const getCanvasCenterMenuOptions = function (
this: LGraphCanvas,
...args: Parameters<typeof f>
) {
const res = f.apply(this, args) as ReturnType<typeof f>
const res: IContextMenuValue<string>[] = f.apply(this, args)

// Add items from new extension API
const newApiItems = app.collectCanvasMenuItems(
this
) as IContextMenuValue<string>[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) as IContextMenuValue<string>[]
)

for (const item of newApiItems) {
res.push(item)
}

// Add legacy monkey-patched items
const legacyItems = legacyMenuCompat.extractLegacyItems(
'getCanvasMenuOptions',
this,
...args
) as ReturnType<typeof f>
for (const item of legacyItems) {
res.push(item)
}

// Translate all items
for (const item of res) {
if (item?.content) {
item.content = st(`contextMenu.${item.content}`, item.content)
Expand All @@ -28,6 +53,33 @@ export const useContextMenuTranslation = () => {

LGraphCanvas.prototype.getCanvasMenuOptions = getCanvasCenterMenuOptions

legacyMenuCompat.registerWrapper(
'getCanvasMenuOptions',
getCanvasCenterMenuOptions,
f,
LGraphCanvas.prototype
)

// Wrap getNodeMenuOptions to add new API items
const nodeMenuFn = LGraphCanvas.prototype.getNodeMenuOptions
const getNodeMenuOptionsWithExtensions = function (
this: LGraphCanvas,
...args: Parameters<typeof nodeMenuFn>
) {
const res = nodeMenuFn.apply(this, args)

// Add items from new extension API
const node = args[0]
const newApiItems = app.collectNodeMenuItems(node)
for (const item of newApiItems) {
res.push(item as (typeof res)[number])
Comment on lines +69 to +75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const res = nodeMenuFn.apply(this, args)
// Add items from new extension API
const node = args[0]
const newApiItems = app.collectNodeMenuItems(node)
for (const item of newApiItems) {
res.push(item as (typeof res)[number])
const res: IContextMenuValue[] = nodeMenuFn.apply(this, args)
// Add items from new extension API
const node = args[0]
const newApiItems = app.collectNodeMenuItems(
for (const item of newApiItems) {
res.push(item)

}

return res
}

LGraphCanvas.prototype.getNodeMenuOptions = getNodeMenuOptionsWithExtensions

function translateMenus(
values: readonly (IContextMenuValue | string | null)[] | undefined,
options: IContextMenuOptions
Expand Down
99 changes: 45 additions & 54 deletions src/extensions/core/groupNode.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
import { t } from '@/i18n'
import { type NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
import {
type ExecutableLGraphNode,
type ExecutionId,
LGraphCanvas,
LGraphNode,
LiteGraph,
SubgraphNode
Expand Down Expand Up @@ -1630,57 +1630,6 @@ 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/')) {
Expand Down Expand Up @@ -1776,8 +1725,50 @@ const ext: ComfyExtension = {
}
}
],
setup() {
addConvertToGroupOptions()

getCanvasMenuItems(canvas): IContextMenuValue[] {
const items: IContextMenuValue[] = []
const selected = Object.values(canvas.selected_nodes ?? {})
const convertDisabled =
selected.length < 2 ||
!!selected.find((n) => GroupNodeHandler.isGroupNode(n))
Comment on lines +1732 to +1734
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation: This logic is in two places, but with different names.


items.push({
content: `Convert to Group Node (Deprecated)`,
disabled: convertDisabled,
// @ts-expect-error fixme ts strict error - async callback
callback: () => convertSelectedNodesToGroupNode()
})

const groups = canvas.graph?.extra?.groupNodes
const manageDisabled = !groups || !Object.keys(groups).length
items.push({
content: `Manage Group Nodes`,
disabled: manageDisabled,
callback: () => manageGroupNodes()
})

return items
},

getNodeMenuItems(node): IContextMenuValue[] {
if (GroupNodeHandler.isGroupNode(node)) {
return []
}

const selected = Object.values(app.canvas.selected_nodes ?? {})
const disabled =
selected.length < 2 ||
!!selected.find((n) => GroupNodeHandler.isGroupNode(n))

return [
{
content: `Convert to Group Node (Deprecated)`,
disabled,
// @ts-expect-error fixme ts strict error - async callback
callback: () => convertSelectedNodesToGroupNode()
}
]
},
async beforeConfigureGraph(
graphData: ComfyWorkflowJSON,
Expand Down
Loading
Loading