mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-09-12 12:37:01 +00:00
Merge branch 'master' into image-cache
This commit is contained in:
@@ -174,6 +174,213 @@ const colorPalettes = {
|
||||
"tr-odd-bg-color": "#073642",
|
||||
}
|
||||
},
|
||||
},
|
||||
"arc": {
|
||||
"id": "arc",
|
||||
"name": "Arc",
|
||||
"colors": {
|
||||
"node_slot": {
|
||||
"BOOLEAN": "",
|
||||
"CLIP": "#eacb8b",
|
||||
"CLIP_VISION": "#A8DADC",
|
||||
"CLIP_VISION_OUTPUT": "#ad7452",
|
||||
"CONDITIONING": "#cf876f",
|
||||
"CONTROL_NET": "#00d78d",
|
||||
"CONTROL_NET_WEIGHTS": "",
|
||||
"FLOAT": "",
|
||||
"GLIGEN": "",
|
||||
"IMAGE": "#80a1c0",
|
||||
"IMAGEUPLOAD": "",
|
||||
"INT": "",
|
||||
"LATENT": "#b38ead",
|
||||
"LATENT_KEYFRAME": "",
|
||||
"MASK": "#a3bd8d",
|
||||
"MODEL": "#8978a7",
|
||||
"SAMPLER": "",
|
||||
"SIGMAS": "",
|
||||
"STRING": "",
|
||||
"STYLE_MODEL": "#C2FFAE",
|
||||
"T2I_ADAPTER_WEIGHTS": "",
|
||||
"TAESD": "#DCC274",
|
||||
"TIMESTEP_KEYFRAME": "",
|
||||
"UPSCALE_MODEL": "",
|
||||
"VAE": "#be616b"
|
||||
},
|
||||
"litegraph_base": {
|
||||
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVR4nO3YMUoDARgF4RfxBqZI6/0vZqFn0MYtrLIQMFN8U6V4LAtD+Jm9XG/v30OGl2e/AP7yevz4+vx45nvgF/+QGITEICQGITEIiUFIjNNC3q43u3/YnRJyPOzeQ+0e220nhRzReC8e7R7bbdvl+Jal1Bs46jEIiUFIDEJiEBKDkBhKPbZT6qHdptRTu02p53DUYxASg5AYhMQgJAYhMZR6bKfUQ7tNqad2m1LP4ajHICQGITEIiUFIDEJiKPXYTqmHdptST+02pZ7DUY9BSAxCYhASg5AYhMRQ6rGdUg/tNqWe2m1KPYejHoOQGITEICQGITEIiaHUYzulHtptSj2125R6Dkc9BiExCIlBSAxCYhASQ6nHdko9tNuUemq3KfUcjnoMQmIQEoOQGITEICSGUo/tlHpotyn11G5T6jkc9RiExCAkBiExCIlBSAylHtsp9dBuU+qp3abUczjqMQiJQUgMQmIQEoOQGITE+AHFISNQrFTGuwAAAABJRU5ErkJggg==",
|
||||
"CLEAR_BACKGROUND_COLOR": "#2b2f38",
|
||||
"NODE_TITLE_COLOR": "#b2b7bd",
|
||||
"NODE_SELECTED_TITLE_COLOR": "#FFF",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#AAA",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "#2b2f38",
|
||||
"NODE_DEFAULT_BGCOLOR": "#242730",
|
||||
"NODE_DEFAULT_BOXCOLOR": "#6e7581",
|
||||
"NODE_DEFAULT_SHAPE": "box",
|
||||
"NODE_BOX_OUTLINE_COLOR": "#FFF",
|
||||
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)",
|
||||
"DEFAULT_GROUP_FONT": 22,
|
||||
"WIDGET_BGCOLOR": "#2b2f38",
|
||||
"WIDGET_OUTLINE_COLOR": "#6e7581",
|
||||
"WIDGET_TEXT_COLOR": "#DDD",
|
||||
"WIDGET_SECONDARY_TEXT_COLOR": "#b2b7bd",
|
||||
"LINK_COLOR": "#9A9",
|
||||
"EVENT_LINK_COLOR": "#A86",
|
||||
"CONNECTING_LINK_COLOR": "#AFA"
|
||||
},
|
||||
"comfy_base": {
|
||||
"fg-color": "#fff",
|
||||
"bg-color": "#2b2f38",
|
||||
"comfy-menu-bg": "#242730",
|
||||
"comfy-input-bg": "#2b2f38",
|
||||
"input-text": "#ddd",
|
||||
"descrip-text": "#b2b7bd",
|
||||
"drag-text": "#ccc",
|
||||
"error-text": "#ff4444",
|
||||
"border-color": "#6e7581",
|
||||
"tr-even-bg-color": "#2b2f38",
|
||||
"tr-odd-bg-color": "#242730"
|
||||
}
|
||||
},
|
||||
},
|
||||
"nord": {
|
||||
"id": "nord",
|
||||
"name": "Nord",
|
||||
"colors": {
|
||||
"node_slot": {
|
||||
"BOOLEAN": "",
|
||||
"CLIP": "#eacb8b",
|
||||
"CLIP_VISION": "#A8DADC",
|
||||
"CLIP_VISION_OUTPUT": "#ad7452",
|
||||
"CONDITIONING": "#cf876f",
|
||||
"CONTROL_NET": "#00d78d",
|
||||
"CONTROL_NET_WEIGHTS": "",
|
||||
"FLOAT": "",
|
||||
"GLIGEN": "",
|
||||
"IMAGE": "#80a1c0",
|
||||
"IMAGEUPLOAD": "",
|
||||
"INT": "",
|
||||
"LATENT": "#b38ead",
|
||||
"LATENT_KEYFRAME": "",
|
||||
"MASK": "#a3bd8d",
|
||||
"MODEL": "#8978a7",
|
||||
"SAMPLER": "",
|
||||
"SIGMAS": "",
|
||||
"STRING": "",
|
||||
"STYLE_MODEL": "#C2FFAE",
|
||||
"T2I_ADAPTER_WEIGHTS": "",
|
||||
"TAESD": "#DCC274",
|
||||
"TIMESTEP_KEYFRAME": "",
|
||||
"UPSCALE_MODEL": "",
|
||||
"VAE": "#be616b"
|
||||
},
|
||||
"litegraph_base": {
|
||||
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFu2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjUwNDFhMmZjLTEzNzQtMTk0ZC1hZWY4LTYxMzM1MTVmNjUwMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDQxYTJmYy0xMzc0LTE5NGQtYWVmOC02MTMzNTE1ZjY1MDAiIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz73jWg/AAAAyUlEQVR42u3WKwoAIBRFQRdiMb1idv9Lsxn9gEFw4Dbb8JCTojbbXEJwjJVL2HKwYMGCBQuWLbDmjr+9zrBGjHl1WVcvy2DBggULFizTWQpewSt4HzwsgwULFiwFr7MUvMtS8D54WLBgGSxYCl7BK3iXZbBgwYIFC5bpLAWv4BW8Dx6WwYIFC5aC11kK3mUpeB88LFiwDBYsBa/gFbzLMliwYMGCBct0loJX8AreBw/LYMGCBUvB6ywF77IUvA8eFixYBgsWrNfWAZPltufdad+1AAAAAElFTkSuQmCC",
|
||||
"CLEAR_BACKGROUND_COLOR": "#212732",
|
||||
"NODE_TITLE_COLOR": "#999",
|
||||
"NODE_SELECTED_TITLE_COLOR": "#e5eaf0",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#bcc2c8",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "#2e3440",
|
||||
"NODE_DEFAULT_BGCOLOR": "#161b22",
|
||||
"NODE_DEFAULT_BOXCOLOR": "#545d70",
|
||||
"NODE_DEFAULT_SHAPE": "box",
|
||||
"NODE_BOX_OUTLINE_COLOR": "#e5eaf0",
|
||||
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)",
|
||||
"DEFAULT_GROUP_FONT": 24,
|
||||
"WIDGET_BGCOLOR": "#2e3440",
|
||||
"WIDGET_OUTLINE_COLOR": "#545d70",
|
||||
"WIDGET_TEXT_COLOR": "#bcc2c8",
|
||||
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
|
||||
"LINK_COLOR": "#9A9",
|
||||
"EVENT_LINK_COLOR": "#A86",
|
||||
"CONNECTING_LINK_COLOR": "#AFA"
|
||||
},
|
||||
"comfy_base": {
|
||||
"fg-color": "#e5eaf0",
|
||||
"bg-color": "#2e3440",
|
||||
"comfy-menu-bg": "#161b22",
|
||||
"comfy-input-bg": "#2e3440",
|
||||
"input-text": "#bcc2c8",
|
||||
"descrip-text": "#999",
|
||||
"drag-text": "#ccc",
|
||||
"error-text": "#ff4444",
|
||||
"border-color": "#545d70",
|
||||
"tr-even-bg-color": "#2e3440",
|
||||
"tr-odd-bg-color": "#161b22"
|
||||
}
|
||||
},
|
||||
},
|
||||
"github": {
|
||||
"id": "github",
|
||||
"name": "Github",
|
||||
"colors": {
|
||||
"node_slot": {
|
||||
"BOOLEAN": "",
|
||||
"CLIP": "#eacb8b",
|
||||
"CLIP_VISION": "#A8DADC",
|
||||
"CLIP_VISION_OUTPUT": "#ad7452",
|
||||
"CONDITIONING": "#cf876f",
|
||||
"CONTROL_NET": "#00d78d",
|
||||
"CONTROL_NET_WEIGHTS": "",
|
||||
"FLOAT": "",
|
||||
"GLIGEN": "",
|
||||
"IMAGE": "#80a1c0",
|
||||
"IMAGEUPLOAD": "",
|
||||
"INT": "",
|
||||
"LATENT": "#b38ead",
|
||||
"LATENT_KEYFRAME": "",
|
||||
"MASK": "#a3bd8d",
|
||||
"MODEL": "#8978a7",
|
||||
"SAMPLER": "",
|
||||
"SIGMAS": "",
|
||||
"STRING": "",
|
||||
"STYLE_MODEL": "#C2FFAE",
|
||||
"T2I_ADAPTER_WEIGHTS": "",
|
||||
"TAESD": "#DCC274",
|
||||
"TIMESTEP_KEYFRAME": "",
|
||||
"UPSCALE_MODEL": "",
|
||||
"VAE": "#be616b"
|
||||
},
|
||||
"litegraph_base": {
|
||||
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==",
|
||||
"CLEAR_BACKGROUND_COLOR": "#040506",
|
||||
"NODE_TITLE_COLOR": "#999",
|
||||
"NODE_SELECTED_TITLE_COLOR": "#e5eaf0",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#bcc2c8",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "#161b22",
|
||||
"NODE_DEFAULT_BGCOLOR": "#13171d",
|
||||
"NODE_DEFAULT_BOXCOLOR": "#30363d",
|
||||
"NODE_DEFAULT_SHAPE": "box",
|
||||
"NODE_BOX_OUTLINE_COLOR": "#e5eaf0",
|
||||
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)",
|
||||
"DEFAULT_GROUP_FONT": 24,
|
||||
"WIDGET_BGCOLOR": "#161b22",
|
||||
"WIDGET_OUTLINE_COLOR": "#30363d",
|
||||
"WIDGET_TEXT_COLOR": "#bcc2c8",
|
||||
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
|
||||
"LINK_COLOR": "#9A9",
|
||||
"EVENT_LINK_COLOR": "#A86",
|
||||
"CONNECTING_LINK_COLOR": "#AFA"
|
||||
},
|
||||
"comfy_base": {
|
||||
"fg-color": "#e5eaf0",
|
||||
"bg-color": "#161b22",
|
||||
"comfy-menu-bg": "#13171d",
|
||||
"comfy-input-bg": "#161b22",
|
||||
"input-text": "#bcc2c8",
|
||||
"descrip-text": "#999",
|
||||
"drag-text": "#ccc",
|
||||
"error-text": "#ff4444",
|
||||
"border-color": "#30363d",
|
||||
"tr-even-bg-color": "#161b22",
|
||||
"tr-odd-bg-color": "#13171d"
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
1053
web/extensions/core/groupNode.js
Normal file
1053
web/extensions/core/groupNode.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||
import { GroupNodeConfig, GroupNodeHandler } from "./groupNode.js";
|
||||
|
||||
// Adds the ability to save and add multiple nodes as a template
|
||||
// To save:
|
||||
@@ -34,7 +35,7 @@ class ManageTemplates extends ComfyDialog {
|
||||
type: "file",
|
||||
accept: ".json",
|
||||
multiple: true,
|
||||
style: {display: "none"},
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
onchange: () => this.importAll(),
|
||||
});
|
||||
@@ -109,13 +110,13 @@ class ManageTemplates extends ComfyDialog {
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.stringify({templates: this.templates}, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], {type: "application/json"});
|
||||
const json = JSON.stringify({ templates: this.templates }, null, 2); // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = $el("a", {
|
||||
href: url,
|
||||
download: "node_templates.json",
|
||||
style: {display: "none"},
|
||||
style: { display: "none" },
|
||||
parent: document.body,
|
||||
});
|
||||
a.click();
|
||||
@@ -291,11 +292,11 @@ app.registerExtension({
|
||||
setup() {
|
||||
const manage = new ManageTemplates();
|
||||
|
||||
const clipboardAction = (cb) => {
|
||||
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");
|
||||
cb();
|
||||
await cb();
|
||||
localStorage.setItem("litegrapheditor_clipboard", old);
|
||||
};
|
||||
|
||||
@@ -309,13 +310,31 @@ app.registerExtension({
|
||||
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
|
||||
callback: () => {
|
||||
const name = prompt("Enter name");
|
||||
if (!name || !name.trim()) return;
|
||||
if (!name?.trim()) return;
|
||||
|
||||
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;
|
||||
|
||||
let groupData = GroupNodeHandler.getGroupData(node);
|
||||
if (groupData) {
|
||||
groupData = groupData.nodeData;
|
||||
if (!data.groupNodes) {
|
||||
data.groupNodes = {};
|
||||
}
|
||||
data.groupNodes[nodeData.name] = groupData;
|
||||
data.nodes[i].type = nodeData.name;
|
||||
}
|
||||
}
|
||||
|
||||
manage.templates.push({
|
||||
name,
|
||||
data: localStorage.getItem("litegrapheditor_clipboard"),
|
||||
data: JSON.stringify(data),
|
||||
});
|
||||
manage.store();
|
||||
});
|
||||
@@ -323,15 +342,19 @@ app.registerExtension({
|
||||
});
|
||||
|
||||
// Map each template to a menu item
|
||||
const subItems = manage.templates.map((t) => ({
|
||||
content: t.name,
|
||||
callback: () => {
|
||||
clipboardAction(() => {
|
||||
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
||||
app.canvas.pasteFromClipboard();
|
||||
});
|
||||
},
|
||||
}));
|
||||
const subItems = manage.templates.map((t) => {
|
||||
return {
|
||||
content: t.name,
|
||||
callback: () => {
|
||||
clipboardAction(async () => {
|
||||
const data = JSON.parse(t.data);
|
||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {});
|
||||
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
||||
app.canvas.pasteFromClipboard();
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
subItems.push(null, {
|
||||
content: "Manage",
|
||||
|
150
web/extensions/core/undoRedo.js
Normal file
150
web/extensions/core/undoRedo.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const MAX_HISTORY = 50;
|
||||
|
||||
let undo = [];
|
||||
let redo = [];
|
||||
let activeState = null;
|
||||
let isOurLoad = false;
|
||||
function checkState() {
|
||||
const currentState = app.graph.serialize();
|
||||
if (!graphEqual(activeState, currentState)) {
|
||||
undo.push(activeState);
|
||||
if (undo.length > MAX_HISTORY) {
|
||||
undo.shift();
|
||||
}
|
||||
activeState = clone(currentState);
|
||||
redo.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const loadGraphData = app.loadGraphData;
|
||||
app.loadGraphData = async function () {
|
||||
const v = await loadGraphData.apply(this, arguments);
|
||||
if (isOurLoad) {
|
||||
isOurLoad = false;
|
||||
} else {
|
||||
checkState();
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
function clone(obj) {
|
||||
try {
|
||||
if (typeof structuredClone !== "undefined") {
|
||||
return structuredClone(obj);
|
||||
}
|
||||
} catch (error) {
|
||||
// structuredClone is stricter than using JSON.parse/stringify so fallback to that
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function graphEqual(a, b, root = true) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (typeof a == "object" && a && typeof b == "object" && b) {
|
||||
const keys = Object.getOwnPropertyNames(a);
|
||||
|
||||
if (keys.length != Object.getOwnPropertyNames(b).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys) {
|
||||
let av = a[key];
|
||||
let bv = b[key];
|
||||
if (root && key === "nodes") {
|
||||
// Nodes need to be sorted as the order changes when selecting nodes
|
||||
av = [...av].sort((a, b) => a.id - b.id);
|
||||
bv = [...bv].sort((a, b) => a.id - b.id);
|
||||
}
|
||||
if (!graphEqual(av, bv, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const undoRedo = async (e) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.key === "y") {
|
||||
const prevState = redo.pop();
|
||||
if (prevState) {
|
||||
undo.push(activeState);
|
||||
isOurLoad = true;
|
||||
await app.loadGraphData(prevState);
|
||||
activeState = prevState;
|
||||
}
|
||||
return true;
|
||||
} else if (e.key === "z") {
|
||||
const prevState = undo.pop();
|
||||
if (prevState) {
|
||||
redo.push(activeState);
|
||||
isOurLoad = true;
|
||||
await app.loadGraphData(prevState);
|
||||
activeState = prevState;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bindInput = (activeEl) => {
|
||||
if (activeEl?.tagName !== "CANVAS" && activeEl?.tagName !== "BODY") {
|
||||
for (const evt of ["change", "input", "blur"]) {
|
||||
if (`on${evt}` in activeEl) {
|
||||
const listener = () => {
|
||||
checkState();
|
||||
activeEl.removeEventListener(evt, listener);
|
||||
};
|
||||
activeEl.addEventListener(evt, listener);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
"keydown",
|
||||
(e) => {
|
||||
requestAnimationFrame(async () => {
|
||||
const activeEl = document.activeElement;
|
||||
if (activeEl?.tagName === "INPUT" || activeEl?.type === "textarea") {
|
||||
// Ignore events on inputs, they have their native history
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a ctrl+z ctrl+y
|
||||
if (await undoRedo(e)) return;
|
||||
|
||||
// If our active element is some type of input then handle changes after they're done
|
||||
if (bindInput(activeEl)) return;
|
||||
checkState();
|
||||
});
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Handle clicking DOM elements (e.g. widgets)
|
||||
window.addEventListener("mouseup", () => {
|
||||
checkState();
|
||||
});
|
||||
|
||||
// Handle litegraph clicks
|
||||
const processMouseUp = LGraphCanvas.prototype.processMouseUp;
|
||||
LGraphCanvas.prototype.processMouseUp = function (e) {
|
||||
const v = processMouseUp.apply(this, arguments);
|
||||
checkState();
|
||||
return v;
|
||||
};
|
||||
const processMouseDown = LGraphCanvas.prototype.processMouseDown;
|
||||
LGraphCanvas.prototype.processMouseDown = function (e) {
|
||||
const v = processMouseDown.apply(this, arguments);
|
||||
checkState();
|
||||
return v;
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { ComfyWidgets, addValueControlWidget } from "../../scripts/widgets.js";
|
||||
import { ComfyWidgets, addValueControlWidgets } from "../../scripts/widgets.js";
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const CONVERTED_TYPE = "converted-widget";
|
||||
@@ -121,6 +121,110 @@ function isValidCombo(combo, obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function mergeIfValid(output, config2, forceUpdate, recreateWidget, config1) {
|
||||
if (!config1) {
|
||||
config1 = output.widget[CONFIG] ?? output.widget[GET_CONFIG]();
|
||||
}
|
||||
|
||||
if (config1[0] instanceof Array) {
|
||||
if (!isValidCombo(config1[0], config2[0])) return false;
|
||||
} else if (config1[0] !== config2[0]) {
|
||||
// Types dont match
|
||||
console.log(`connection rejected: types dont match`, config1[0], config2[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
const keys = new Set([...Object.keys(config1[1] ?? {}), ...Object.keys(config2[1] ?? {})]);
|
||||
|
||||
let customConfig;
|
||||
const getCustomConfig = () => {
|
||||
if (!customConfig) {
|
||||
if (typeof structuredClone === "undefined") {
|
||||
customConfig = JSON.parse(JSON.stringify(config1[1] ?? {}));
|
||||
} else {
|
||||
customConfig = structuredClone(config1[1] ?? {});
|
||||
}
|
||||
}
|
||||
return customConfig;
|
||||
};
|
||||
|
||||
const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
|
||||
for (const k of keys.values()) {
|
||||
if (k !== "default" && k !== "forceInput" && k !== "defaultInput") {
|
||||
let v1 = config1[1][k];
|
||||
let v2 = config2[1]?.[k];
|
||||
|
||||
if (v1 === v2 || (!v1 && !v2)) continue;
|
||||
|
||||
if (isNumber) {
|
||||
if (k === "min") {
|
||||
const theirMax = config2[1]?.["max"];
|
||||
if (theirMax != null && v1 > theirMax) {
|
||||
console.log("connection rejected: min > max", v1, theirMax);
|
||||
return false;
|
||||
}
|
||||
getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.max(v1, v2);
|
||||
continue;
|
||||
} else if (k === "max") {
|
||||
const theirMin = config2[1]?.["min"];
|
||||
if (theirMin != null && v1 < theirMin) {
|
||||
console.log("connection rejected: max < min", v1, theirMin);
|
||||
return false;
|
||||
}
|
||||
getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.min(v1, v2);
|
||||
continue;
|
||||
} else if (k === "step") {
|
||||
let step;
|
||||
if (v1 == null) {
|
||||
// No current step
|
||||
step = v2;
|
||||
} else if (v2 == null) {
|
||||
// No new step
|
||||
step = v1;
|
||||
} else {
|
||||
if (v1 < v2) {
|
||||
// Ensure v1 is larger for the mod
|
||||
const a = v2;
|
||||
v2 = v1;
|
||||
v1 = a;
|
||||
}
|
||||
if (v1 % v2) {
|
||||
console.log("connection rejected: steps not divisible", "current:", v1, "new:", v2);
|
||||
return false;
|
||||
}
|
||||
|
||||
step = v1;
|
||||
}
|
||||
|
||||
getCustomConfig()[k] = step;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`connection rejected: config ${k} values dont match`, v1, v2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (customConfig || forceUpdate) {
|
||||
if (customConfig) {
|
||||
output.widget[CONFIG] = [config1[0], customConfig];
|
||||
}
|
||||
|
||||
const widget = recreateWidget?.call(this);
|
||||
// When deleting a node this can be null
|
||||
if (widget) {
|
||||
const min = widget.options.min;
|
||||
const max = widget.options.max;
|
||||
if (min != null && widget.value < min) widget.value = min;
|
||||
if (max != null && widget.value > max) widget.value = max;
|
||||
widget.callback(widget.value);
|
||||
}
|
||||
}
|
||||
|
||||
return { customConfig };
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.WidgetInputs",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
@@ -308,7 +412,7 @@ app.registerExtension({
|
||||
this.isVirtualNode = true;
|
||||
}
|
||||
|
||||
applyToGraph() {
|
||||
applyToGraph(extraLinks = []) {
|
||||
if (!this.outputs[0].links?.length) return;
|
||||
|
||||
function get_links(node) {
|
||||
@@ -325,10 +429,9 @@ app.registerExtension({
|
||||
return links;
|
||||
}
|
||||
|
||||
let links = get_links(this);
|
||||
let links = [...get_links(this).map((l) => app.graph.links[l]), ...extraLinks];
|
||||
// For each output link copy our value over the original widget value
|
||||
for (const l of links) {
|
||||
const linkInfo = app.graph.links[l];
|
||||
for (const linkInfo of links) {
|
||||
const node = this.graph.getNodeById(linkInfo.target_id);
|
||||
const input = node.inputs[linkInfo.target_slot];
|
||||
const widgetName = input.widget.name;
|
||||
@@ -405,7 +508,12 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
if (this.outputs[slot].links?.length) {
|
||||
return this.#isValidConnection(input);
|
||||
const valid = this.#isValidConnection(input);
|
||||
if (valid) {
|
||||
// On connect of additional outputs, copy our value to their widget
|
||||
this.applyToGraph([{ target_id: target_node.id, target_slot }]);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,12 +570,16 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.type === "number" || widget.type === "combo") {
|
||||
if (!inputData?.[1]?.control_after_generate && (widget.type === "number" || widget.type === "combo")) {
|
||||
let control_value = this.widgets_values?.[1];
|
||||
if (!control_value) {
|
||||
control_value = "fixed";
|
||||
}
|
||||
addValueControlWidget(this, widget, control_value);
|
||||
addValueControlWidgets(this, widget, control_value, undefined, inputData);
|
||||
let filter = this.widgets_values?.[2];
|
||||
if(filter && this.widgets.length === 3) {
|
||||
this.widgets[2].value = filter;
|
||||
}
|
||||
}
|
||||
|
||||
// When our value changes, update other widgets to reflect our changes
|
||||
@@ -503,6 +615,7 @@ app.registerExtension({
|
||||
this.#removeWidgets();
|
||||
this.#onFirstConnection(true);
|
||||
for (let i = 0; i < this.widgets?.length; i++) this.widgets[i].value = values[i];
|
||||
return this.widgets[0];
|
||||
}
|
||||
|
||||
#mergeWidgetConfig() {
|
||||
@@ -543,108 +656,8 @@ app.registerExtension({
|
||||
#isValidConnection(input, forceUpdate) {
|
||||
// Only allow connections where the configs match
|
||||
const output = this.outputs[0];
|
||||
const config1 = output.widget[CONFIG] ?? output.widget[GET_CONFIG]();
|
||||
const config2 = input.widget[GET_CONFIG]();
|
||||
|
||||
if (config1[0] instanceof Array) {
|
||||
if (!isValidCombo(config1[0], config2[0])) return false;
|
||||
} else if (config1[0] !== config2[0]) {
|
||||
// Types dont match
|
||||
console.log(`connection rejected: types dont match`, config1[0], config2[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
const keys = new Set([...Object.keys(config1[1] ?? {}), ...Object.keys(config2[1] ?? {})]);
|
||||
|
||||
let customConfig;
|
||||
const getCustomConfig = () => {
|
||||
if (!customConfig) {
|
||||
if (typeof structuredClone === "undefined") {
|
||||
customConfig = JSON.parse(JSON.stringify(config1[1] ?? {}));
|
||||
} else {
|
||||
customConfig = structuredClone(config1[1] ?? {});
|
||||
}
|
||||
}
|
||||
return customConfig;
|
||||
};
|
||||
|
||||
const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
|
||||
for (const k of keys.values()) {
|
||||
if (k !== "default" && k !== "forceInput" && k !== "defaultInput") {
|
||||
let v1 = config1[1][k];
|
||||
let v2 = config2[1][k];
|
||||
|
||||
if (v1 === v2 || (!v1 && !v2)) continue;
|
||||
|
||||
if (isNumber) {
|
||||
if (k === "min") {
|
||||
const theirMax = config2[1]["max"];
|
||||
if (theirMax != null && v1 > theirMax) {
|
||||
console.log("connection rejected: min > max", v1, theirMax);
|
||||
return false;
|
||||
}
|
||||
getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.max(v1, v2);
|
||||
continue;
|
||||
} else if (k === "max") {
|
||||
const theirMin = config2[1]["min"];
|
||||
if (theirMin != null && v1 < theirMin) {
|
||||
console.log("connection rejected: max < min", v1, theirMin);
|
||||
return false;
|
||||
}
|
||||
getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.min(v1, v2);
|
||||
continue;
|
||||
} else if (k === "step") {
|
||||
let step;
|
||||
if (v1 == null) {
|
||||
// No current step
|
||||
step = v2;
|
||||
} else if (v2 == null) {
|
||||
// No new step
|
||||
step = v1;
|
||||
} else {
|
||||
if (v1 < v2) {
|
||||
// Ensure v1 is larger for the mod
|
||||
const a = v2;
|
||||
v2 = v1;
|
||||
v1 = a;
|
||||
}
|
||||
if (v1 % v2) {
|
||||
console.log("connection rejected: steps not divisible", "current:", v1, "new:", v2);
|
||||
return false;
|
||||
}
|
||||
|
||||
step = v1;
|
||||
}
|
||||
|
||||
getCustomConfig()[k] = step;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`connection rejected: config ${k} values dont match`, v1, v2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (customConfig || forceUpdate) {
|
||||
if (customConfig) {
|
||||
output.widget[CONFIG] = [config1[0], customConfig];
|
||||
}
|
||||
|
||||
this.#recreateWidget();
|
||||
|
||||
const widget = this.widgets[0];
|
||||
// When deleting a node this can be null
|
||||
if (widget) {
|
||||
const min = widget.options.min;
|
||||
const max = widget.options.max;
|
||||
if (min != null && widget.value < min) widget.value = min;
|
||||
if (max != null && widget.value > max) widget.value = max;
|
||||
widget.callback(widget.value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return !!mergeIfValid.call(this, output, config2, forceUpdate, this.#recreateWidget);
|
||||
}
|
||||
|
||||
#removeWidgets() {
|
||||
|
Reference in New Issue
Block a user