mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 09:19:43 +00:00
Contextmenu extension migration (#5993)
This pull request refactors how context menu items are contributed by extensions in the LiteGraph-based canvas. The legacy monkey-patching approach for adding context menu options is replaced by a new, explicit API (`getCanvasMenuItems` and `getNodeMenuItems`) for extensions. A compatibility layer is added to support legacy extensions and warn developers about deprecated usage. The changes improve maintainability, extension interoperability, and migration to the new context menu system. ### Context Menu System Refactor * Introduced a new API for extensions to contribute context menu items via `getCanvasMenuItems` and `getNodeMenuItems` methods, replacing legacy monkey-patching of `LGraphCanvas.prototype.getCanvasMenuOptions`. Major extension files (`groupNode.ts`, `groupOptions.ts`, `nodeTemplates.ts`) now use this new API. [[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917L1779-R1771) [[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL232-R239) [[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL447-R458) * Added a compatibility layer (`legacyMenuCompat` in `contextMenuCompat.ts`) to detect and warn when legacy monkey-patching is used, and to extract legacy-added menu items for backward compatibility. [[1]](diffhunk://#diff-2b724cb107c04e290369fb927e2ae9fad03be9e617a7d4de2487deab89d0d018R2-R45) [[2]](diffhunk://#diff-d3a8284ec16ae3f9512e33abe44ae653ed1aa45c9926485ef6270cc8d2b94ae6R1-R115) ### Extension Migration * Refactored core extensions (`groupNode`, `groupOptions`, and `nodeTemplates`) to implement the new context menu API, moving menu item logic out of monkey-patched methods and into explicit extension methods. [[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917L1633-L1683) [[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL19-R77) [[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL366-R373) ### Type and Import Cleanup * Updated imports for context menu types (`IContextMenuValue`) across affected files for consistency with the new API. [[1]](diffhunk://#diff-b29f141b89433027e7bb7cde57fad84f9e97ffbe5c58040d3e0fdb7905022917R4-L7) [[2]](diffhunk://#diff-91169f3a27ff8974d5c8fc3346bd99c07bdfb5399984484630125fdd647ff02fL1-R11) [[3]](diffhunk://#diff-04c18583d2dbfc013888e4c02fd432c250acbcecdef82bf7f6d9fd888e632a6eL2-R6) [[4]](diffhunk://#diff-bde0dce9fe2403685d27b0e94a938c3d72824d02d01d1fd6167a0dddc6e585ddR10) ### Backward Compatibility and Migration Guidance * The compatibility layer logs a deprecation warning to the console when legacy monkey-patching is detected, helping developers migrate to the new API. --- These changes collectively modernize the context menu extension mechanism, improve code clarity, and provide a migration path for legacy extensions. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5993-Contextmenu-extension-migration-2876d73d3650813fae07c1141679637a) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
committed by
GitHub
parent
6afdb9529d
commit
b3da6cf1b4
@@ -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
|
||||
@@ -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/')) {
|
||||
@@ -1718,6 +1667,9 @@ async function convertSelectedNodesToGroupNode() {
|
||||
return await GroupNodeHandler.fromNodes(nodes)
|
||||
}
|
||||
|
||||
const convertDisabled = (selected: LGraphNode[]) =>
|
||||
selected.length < 2 || !!selected.find((n) => GroupNodeHandler.isGroupNode(n))
|
||||
|
||||
function ungroupSelectedGroupNodes() {
|
||||
const nodes = Object.values(app.canvas.selected_nodes ?? {})
|
||||
for (const node of nodes) {
|
||||
@@ -1776,8 +1728,46 @@ const ext: ComfyExtension = {
|
||||
}
|
||||
}
|
||||
],
|
||||
setup() {
|
||||
addConvertToGroupOptions()
|
||||
|
||||
getCanvasMenuItems(canvas): IContextMenuValue[] {
|
||||
const items: IContextMenuValue[] = []
|
||||
const selected = Object.values(canvas.selected_nodes ?? {})
|
||||
const convertEnabled = !convertDisabled(selected)
|
||||
|
||||
items.push({
|
||||
content: `Convert to Group Node (Deprecated)`,
|
||||
disabled: !convertEnabled,
|
||||
// @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 convertEnabled = !convertDisabled(selected)
|
||||
|
||||
return [
|
||||
{
|
||||
content: `Convert to Group Node (Deprecated)`,
|
||||
disabled: !convertEnabled,
|
||||
// @ts-expect-error fixme ts strict error - async callback
|
||||
callback: () => convertSelectedNodesToGroupNode()
|
||||
}
|
||||
]
|
||||
},
|
||||
async beforeConfigureGraph(
|
||||
graphData: ComfyWorkflowJSON,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { Positionable } from '@/lib/litegraph/src/interfaces'
|
||||
import { LGraphGroup } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
IContextMenuValue,
|
||||
Positionable
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import {
|
||||
LGraphCanvas,
|
||||
LGraphGroup,
|
||||
type LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
|
||||
@@ -16,220 +22,218 @@ function addNodesToGroup(group: LGraphGroup, items: Iterable<Positionable>) {
|
||||
group.resizeTo([...group.children, ...items], padding)
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
const ext: ComfyExtension = {
|
||||
name: 'Comfy.GroupOptions',
|
||||
setup() {
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||
// graph_mouse
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (
|
||||
this: LGraphCanvas
|
||||
) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const options = orig.apply(this, arguments)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const group = this.graph.getGroupOnPos(
|
||||
this.graph_mouse[0],
|
||||
this.graph_mouse[1]
|
||||
)
|
||||
if (!group) {
|
||||
if (this.selectedItems.size > 0) {
|
||||
options.push({
|
||||
content: 'Add Group For Selected Nodes',
|
||||
callback: () => {
|
||||
const group = new LGraphGroup()
|
||||
addNodesToGroup(group, this.selectedItems)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.add(group)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.change()
|
||||
|
||||
group.recomputeInsideNodes()
|
||||
getCanvasMenuItems(canvas: LGraphCanvas): IContextMenuValue[] {
|
||||
const items: IContextMenuValue[] = []
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const group = canvas.graph.getGroupOnPos(
|
||||
canvas.graph_mouse[0],
|
||||
canvas.graph_mouse[1]
|
||||
)
|
||||
|
||||
if (!group) {
|
||||
if (canvas.selectedItems.size > 0) {
|
||||
items.push({
|
||||
content: 'Add Group For Selected Nodes',
|
||||
callback: () => {
|
||||
const group = new LGraphGroup()
|
||||
addNodesToGroup(group, canvas.selectedItems)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvas.graph.add(group)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvas.graph.change()
|
||||
|
||||
group.recomputeInsideNodes()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
// Group nodes aren't recomputed until the group is moved, this ensures the nodes are up-to-date
|
||||
group.recomputeInsideNodes()
|
||||
const nodesInGroup = group.nodes
|
||||
|
||||
items.push({
|
||||
content: 'Add Selected Nodes To Group',
|
||||
disabled: !canvas.selectedItems?.size,
|
||||
callback: () => {
|
||||
addNodesToGroup(group, canvas.selectedItems)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvas.graph.change()
|
||||
}
|
||||
})
|
||||
|
||||
// No nodes in group, return default options
|
||||
if (nodesInGroup.length === 0) {
|
||||
return items
|
||||
} else {
|
||||
// Add a separator between the default options and the group options
|
||||
// @ts-expect-error fixme ts strict error
|
||||
items.push(null)
|
||||
}
|
||||
|
||||
// Check if all nodes are the same mode
|
||||
let allNodesAreSameMode = true
|
||||
for (let i = 1; i < nodesInGroup.length; i++) {
|
||||
if (nodesInGroup[i].mode !== nodesInGroup[0].mode) {
|
||||
allNodesAreSameMode = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
content: 'Fit Group To Nodes',
|
||||
callback: () => {
|
||||
group.recomputeInsideNodes()
|
||||
const padding = useSettingStore().get(
|
||||
'Comfy.GroupSelectedNodes.Padding'
|
||||
)
|
||||
group.resizeTo(group.children, padding)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvas.graph.change()
|
||||
}
|
||||
})
|
||||
|
||||
items.push({
|
||||
content: 'Select Nodes',
|
||||
callback: () => {
|
||||
canvas.selectNodes(nodesInGroup)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvas.graph.change()
|
||||
canvas.canvas.focus()
|
||||
}
|
||||
})
|
||||
|
||||
// Modes
|
||||
// 0: Always
|
||||
// 1: On Event
|
||||
// 2: Never
|
||||
// 3: On Trigger
|
||||
// 4: Bypass
|
||||
// If all nodes are the same mode, add a menu option to change the mode
|
||||
if (allNodesAreSameMode) {
|
||||
const mode = nodesInGroup[0].mode
|
||||
switch (mode) {
|
||||
case 0:
|
||||
// All nodes are always, option to disable, and bypass
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
// All nodes are never, option to enable, and bypass
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case 4:
|
||||
// All nodes are bypass, option to enable, and disable
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
// All nodes are On Trigger or On Event(Or other?), option to disable, set to always, or bypass
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Group nodes aren't recomputed until the group is moved, this ensures the nodes are up-to-date
|
||||
group.recomputeInsideNodes()
|
||||
const nodesInGroup = group.nodes
|
||||
|
||||
options.push({
|
||||
content: 'Add Selected Nodes To Group',
|
||||
disabled: !this.selectedItems?.size,
|
||||
callback: () => {
|
||||
addNodesToGroup(group, this.selectedItems)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.change()
|
||||
}
|
||||
})
|
||||
|
||||
// No nodes in group, return default options
|
||||
if (nodesInGroup.length === 0) {
|
||||
return options
|
||||
} else {
|
||||
// Add a separator between the default options and the group options
|
||||
// @ts-expect-error fixme ts strict error
|
||||
options.push(null)
|
||||
}
|
||||
|
||||
// Check if all nodes are the same mode
|
||||
let allNodesAreSameMode = true
|
||||
for (let i = 1; i < nodesInGroup.length; i++) {
|
||||
if (nodesInGroup[i].mode !== nodesInGroup[0].mode) {
|
||||
allNodesAreSameMode = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
options.push({
|
||||
content: 'Fit Group To Nodes',
|
||||
} else {
|
||||
// Nodes are not all the same mode, add a menu option to change the mode to always, never, or bypass
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
group.recomputeInsideNodes()
|
||||
const padding = useSettingStore().get(
|
||||
'Comfy.GroupSelectedNodes.Padding'
|
||||
)
|
||||
group.resizeTo(group.children, padding)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.change()
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
options.push({
|
||||
content: 'Select Nodes',
|
||||
items.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
this.selectNodes(nodesInGroup)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.change()
|
||||
this.canvas.focus()
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Modes
|
||||
// 0: Always
|
||||
// 1: On Event
|
||||
// 2: Never
|
||||
// 3: On Trigger
|
||||
// 4: Bypass
|
||||
// If all nodes are the same mode, add a menu option to change the mode
|
||||
if (allNodesAreSameMode) {
|
||||
const mode = nodesInGroup[0].mode
|
||||
switch (mode) {
|
||||
case 0:
|
||||
// All nodes are always, option to disable, and bypass
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
// All nodes are never, option to enable, and bypass
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
case 4:
|
||||
// All nodes are bypass, option to enable, and disable
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
// All nodes are On Trigger or On Event(Or other?), option to disable, set to always, or bypass
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
break
|
||||
items.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Nodes are not all the same mode, add a menu option to change the mode to always, never, or bypass
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Always',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Set Group Nodes to Never',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 2)
|
||||
}
|
||||
}
|
||||
})
|
||||
options.push({
|
||||
content: 'Bypass Group Nodes',
|
||||
callback: () => {
|
||||
for (const node of nodesInGroup) {
|
||||
setNodeMode(node, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension(ext)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { downloadBlob } from '@/base/common/downloadUtil'
|
||||
import { t } from '@/i18n'
|
||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { deserialiseAndCreate } from '@/utils/vintageClipboard'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
@@ -328,110 +330,107 @@ class ManageTemplates extends ComfyDialog {
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
const manage = new ManageTemplates()
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const clipboardAction = async (cb) => {
|
||||
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
||||
// Restore it after we've run our callback
|
||||
const old = localStorage.getItem('litegrapheditor_clipboard')
|
||||
await cb()
|
||||
// @ts-expect-error fixme ts strict error
|
||||
localStorage.setItem('litegrapheditor_clipboard', old)
|
||||
}
|
||||
|
||||
const ext: ComfyExtension = {
|
||||
name: id,
|
||||
setup() {
|
||||
const manage = new ManageTemplates()
|
||||
|
||||
getCanvasMenuItems(_canvas: LGraphCanvas): IContextMenuValue[] {
|
||||
const items: IContextMenuValue[] = []
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const clipboardAction = async (cb) => {
|
||||
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
||||
// Restore it after we've run our callback
|
||||
const old = localStorage.getItem('litegrapheditor_clipboard')
|
||||
await cb()
|
||||
// @ts-expect-error fixme ts strict error
|
||||
localStorage.setItem('litegrapheditor_clipboard', old)
|
||||
}
|
||||
items.push(null)
|
||||
items.push({
|
||||
content: `Save Selected as Template`,
|
||||
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
|
||||
callback: async () => {
|
||||
const name = await useDialogService().prompt({
|
||||
title: t('nodeTemplates.saveAsTemplate'),
|
||||
message: t('nodeTemplates.enterName'),
|
||||
defaultValue: ''
|
||||
})
|
||||
if (!name?.trim()) return
|
||||
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const options = orig.apply(this, arguments)
|
||||
clipboardAction(() => {
|
||||
app.canvas.copyToClipboard()
|
||||
let data = localStorage.getItem('litegrapheditor_clipboard')
|
||||
data = JSON.parse(data || '{}')
|
||||
const nodeIds = Object.keys(app.canvas.selected_nodes)
|
||||
for (let i = 0; i < nodeIds.length; i++) {
|
||||
const node = app.graph.getNodeById(nodeIds[i])
|
||||
const nodeData = node?.constructor.nodeData
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
options.push(null)
|
||||
options.push({
|
||||
content: `Save Selected as Template`,
|
||||
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
|
||||
// @ts-expect-error fixme ts strict error
|
||||
callback: async () => {
|
||||
const name = await useDialogService().prompt({
|
||||
title: t('nodeTemplates.saveAsTemplate'),
|
||||
message: t('nodeTemplates.enterName'),
|
||||
defaultValue: ''
|
||||
})
|
||||
if (!name?.trim()) return
|
||||
|
||||
clipboardAction(() => {
|
||||
app.canvas.copyToClipboard()
|
||||
let data = localStorage.getItem('litegrapheditor_clipboard')
|
||||
// @ts-expect-error fixme ts strict error
|
||||
data = JSON.parse(data)
|
||||
const nodeIds = Object.keys(app.canvas.selected_nodes)
|
||||
for (let i = 0; i < nodeIds.length; i++) {
|
||||
const node = app.graph.getNodeById(nodeIds[i])
|
||||
const nodeData = node?.constructor.nodeData
|
||||
|
||||
let groupData = GroupNodeHandler.getGroupData(node)
|
||||
if (groupData) {
|
||||
groupData = groupData.nodeData
|
||||
let groupData = GroupNodeHandler.getGroupData(node)
|
||||
if (groupData) {
|
||||
groupData = groupData.nodeData
|
||||
// @ts-expect-error
|
||||
if (!data.groupNodes) {
|
||||
// @ts-expect-error
|
||||
if (!data.groupNodes) {
|
||||
// @ts-expect-error
|
||||
data.groupNodes = {}
|
||||
}
|
||||
if (nodeData == null) throw new TypeError('nodeData is not set')
|
||||
// @ts-expect-error
|
||||
data.groupNodes[nodeData.name] = groupData
|
||||
// @ts-expect-error
|
||||
data.nodes[i].type = nodeData.name
|
||||
data.groupNodes = {}
|
||||
}
|
||||
if (nodeData == null) throw new TypeError('nodeData is not set')
|
||||
// @ts-expect-error
|
||||
data.groupNodes[nodeData.name] = groupData
|
||||
// @ts-expect-error
|
||||
data.nodes[i].type = nodeData.name
|
||||
}
|
||||
}
|
||||
|
||||
manage.templates.push({
|
||||
name,
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
manage.store()
|
||||
manage.templates.push({
|
||||
name,
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
manage.store()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Map each template to a menu item
|
||||
const subItems = manage.templates.map((t) => {
|
||||
return {
|
||||
content: t.name,
|
||||
callback: () => {
|
||||
clipboardAction(async () => {
|
||||
const data = JSON.parse(t.data)
|
||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {})
|
||||
|
||||
// Check for old clipboard format
|
||||
if (!data.reroutes) {
|
||||
deserialiseAndCreate(t.data, app.canvas)
|
||||
} else {
|
||||
localStorage.setItem('litegrapheditor_clipboard', t.data)
|
||||
app.canvas.pasteFromClipboard()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Map each template to a menu item
|
||||
const subItems = manage.templates.map((t) => {
|
||||
return {
|
||||
content: t.name,
|
||||
callback: () => {
|
||||
clipboardAction(async () => {
|
||||
const data = JSON.parse(t.data)
|
||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {})
|
||||
// @ts-expect-error fixme ts strict error
|
||||
subItems.push(null, {
|
||||
content: 'Manage',
|
||||
callback: () => manage.show()
|
||||
})
|
||||
|
||||
// Check for old clipboard format
|
||||
if (!data.reroutes) {
|
||||
deserialiseAndCreate(t.data, app.canvas)
|
||||
} else {
|
||||
localStorage.setItem('litegrapheditor_clipboard', t.data)
|
||||
app.canvas.pasteFromClipboard()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
content: 'Node Templates',
|
||||
submenu: {
|
||||
options: subItems
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
subItems.push(null, {
|
||||
content: 'Manage',
|
||||
callback: () => manage.show()
|
||||
})
|
||||
|
||||
options.push({
|
||||
content: 'Node Templates',
|
||||
submenu: {
|
||||
options: subItems
|
||||
}
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
return items
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension(ext)
|
||||
|
||||
Reference in New Issue
Block a user