Merge pull request #71 from space-nuko/subgraph-templates3
Subgraph templates
This commit is contained in:
Submodule litegraph updated: a1bf4cb511...8ca6cca777
5588
notebooks/ComfyBox_Colab.ipynb
Normal file
5588
notebooks/ComfyBox_Colab.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
@@ -72,6 +72,7 @@
|
|||||||
"@litegraph-ts/tsconfig": "workspace:*",
|
"@litegraph-ts/tsconfig": "workspace:*",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
|
"@types/dompurify": "^3.0.2",
|
||||||
"@zerodevx/svelte-json-view": "^1.0.5",
|
"@zerodevx/svelte-json-view": "^1.0.5",
|
||||||
"canvas-to-svg": "^1.0.3",
|
"canvas-to-svg": "^1.0.3",
|
||||||
"cm6-theme-basic-dark": "^0.2.0",
|
"cm6-theme-basic-dark": "^0.2.0",
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"csv": "^6.3.0",
|
"csv": "^6.3.0",
|
||||||
"csv-parse": "^5.3.10",
|
"csv-parse": "^5.3.10",
|
||||||
|
"dompurify": "^3.0.3",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"framework7": "^8.0.3",
|
"framework7": "^8.0.3",
|
||||||
"framework7-svelte": "^8.0.3",
|
"framework7-svelte": "^8.0.3",
|
||||||
@@ -88,8 +90,8 @@
|
|||||||
"radix-icons-svelte": "^1.2.1",
|
"radix-icons-svelte": "^1.2.1",
|
||||||
"style-mod": "^4.0.3",
|
"style-mod": "^4.0.3",
|
||||||
"svelte-bootstrap-icons": "^2.3.1",
|
"svelte-bootstrap-icons": "^2.3.1",
|
||||||
"svelte-codemirror-editor": "^1.1.0",
|
|
||||||
"svelte-feather-icons": "^4.0.0",
|
"svelte-feather-icons": "^4.0.0",
|
||||||
|
"svelte-floating-ui": "^1.5.2",
|
||||||
"svelte-preprocess": "^5.0.3",
|
"svelte-preprocess": "^5.0.3",
|
||||||
"svelte-select": "^5.5.3",
|
"svelte-select": "^5.5.3",
|
||||||
"svelte-splitpanes": "^0.7.13",
|
"svelte-splitpanes": "^0.7.13",
|
||||||
|
|||||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@@ -94,6 +94,9 @@ importers:
|
|||||||
'@tsconfig/svelte':
|
'@tsconfig/svelte':
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
|
'@types/dompurify':
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2
|
||||||
'@zerodevx/svelte-json-view':
|
'@zerodevx/svelte-json-view':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5(svelte@3.58.0)
|
version: 1.0.5(svelte@3.58.0)
|
||||||
@@ -115,6 +118,9 @@ importers:
|
|||||||
csv-parse:
|
csv-parse:
|
||||||
specifier: ^5.3.10
|
specifier: ^5.3.10
|
||||||
version: 5.3.10
|
version: 5.3.10
|
||||||
|
dompurify:
|
||||||
|
specifier: ^3.0.3
|
||||||
|
version: 3.0.3
|
||||||
events:
|
events:
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
@@ -142,12 +148,12 @@ importers:
|
|||||||
svelte-bootstrap-icons:
|
svelte-bootstrap-icons:
|
||||||
specifier: ^2.3.1
|
specifier: ^2.3.1
|
||||||
version: 2.3.1
|
version: 2.3.1
|
||||||
svelte-codemirror-editor:
|
|
||||||
specifier: ^1.1.0
|
|
||||||
version: 1.1.0(codemirror@6.0.1)
|
|
||||||
svelte-feather-icons:
|
svelte-feather-icons:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
|
svelte-floating-ui:
|
||||||
|
specifier: ^1.5.2
|
||||||
|
version: 1.5.2
|
||||||
svelte-preprocess:
|
svelte-preprocess:
|
||||||
specifier: ^5.0.3
|
specifier: ^5.0.3
|
||||||
version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3)
|
version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3)
|
||||||
@@ -3235,6 +3241,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==}
|
resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/dompurify@3.0.2:
|
||||||
|
resolution: {integrity: sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/trusted-types': 2.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/estree@1.0.1:
|
/@types/estree@1.0.1:
|
||||||
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
||||||
|
|
||||||
@@ -3318,6 +3330,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/trusted-types@2.0.3:
|
||||||
|
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/uuid@9.0.1:
|
/@types/uuid@9.0.1:
|
||||||
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
|
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4562,6 +4578,10 @@ packages:
|
|||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dompurify@3.0.3:
|
||||||
|
resolution: {integrity: sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/domutils@2.8.0:
|
/domutils@2.8.0:
|
||||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8097,14 +8117,6 @@ packages:
|
|||||||
- sugarss
|
- sugarss
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/svelte-codemirror-editor@1.1.0(codemirror@6.0.1):
|
|
||||||
resolution: {integrity: sha512-wFdMIsZds5qzn3x2NbFUxDVU6Cn3rwFdq0035ypaFVgzTjJ90bnPm6IbrFA4OJz1ngIyfbIuPAPDjm7rJIr0gg==}
|
|
||||||
peerDependencies:
|
|
||||||
codemirror: ^6.0.0
|
|
||||||
dependencies:
|
|
||||||
codemirror: 6.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/svelte-dnd-action@0.9.22(svelte@3.58.0):
|
/svelte-dnd-action@0.9.22(svelte@3.58.0):
|
||||||
resolution: {integrity: sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==}
|
resolution: {integrity: sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -8126,6 +8138,13 @@ packages:
|
|||||||
'@floating-ui/dom': 1.2.8
|
'@floating-ui/dom': 1.2.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/svelte-floating-ui@1.5.2:
|
||||||
|
resolution: {integrity: sha512-nV50eno74CEsFfFJ6iyN/oNYDEOck1TZjGV3lmJksVRbiiUAVF6bHspyAhR7GZ7c/4qbRWp9UyX24J+UXdEpag==}
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.2.6
|
||||||
|
'@floating-ui/dom': 1.2.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
/svelte-hmr@0.15.1(svelte@3.58.0):
|
/svelte-hmr@0.15.1(svelte@3.58.0):
|
||||||
resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==}
|
resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==}
|
||||||
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
||||||
|
|||||||
7
public/config.json
Normal file
7
public/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"comfyUIHostname": "localhost",
|
||||||
|
"comfyUIPort": 8188,
|
||||||
|
"alwaysStripUserState": false,
|
||||||
|
"promptForWorkflowName": false,
|
||||||
|
"confirmWhenUnloadingUnsavedChanges": true
|
||||||
|
}
|
||||||
BIN
public/image/graph-bg.png
Normal file
BIN
public/image/graph-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 350 B |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
"comfyBoxWorkflow": true,
|
"comfyBoxWorkflow": true,
|
||||||
"createdBy": "ComfyBox",
|
"createdBy": "ComfyBox",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"commitHash": "test",
|
"commitHash": "574d3170a4e829df366dc12c3aaa049121052d8f\n",
|
||||||
"workflow": {
|
"workflow": {
|
||||||
"last_node_id": 0,
|
"last_node_id": 0,
|
||||||
"last_link_id": 0,
|
"last_link_id": 0,
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
"widgets_values": [],
|
"widgets_values": [],
|
||||||
"color": "#223",
|
"color": "#223",
|
||||||
"bgColor": "#335",
|
"bgColor": "#335",
|
||||||
"comfyValue": "A",
|
"comfyValue": null,
|
||||||
"shownOutputProperties": {},
|
"shownOutputProperties": {},
|
||||||
"saveUserState": false
|
"saveUserState": false
|
||||||
},
|
},
|
||||||
@@ -391,7 +391,20 @@
|
|||||||
"title": "UI.Gallery",
|
"title": "UI.Gallery",
|
||||||
"properties": {
|
"properties": {
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"defaultValue": null,
|
"defaultValue": [
|
||||||
|
{
|
||||||
|
"isComfyBoxImageMetadata": true,
|
||||||
|
"comfyUIFile": {
|
||||||
|
"filename": "ComfyUI_04712_.png",
|
||||||
|
"subfolder": "",
|
||||||
|
"type": "output"
|
||||||
|
},
|
||||||
|
"name": "File",
|
||||||
|
"tags": [],
|
||||||
|
"width": 6656,
|
||||||
|
"height": 4096
|
||||||
|
}
|
||||||
|
],
|
||||||
"index": 0,
|
"index": 0,
|
||||||
"updateMode": "replace",
|
"updateMode": "replace",
|
||||||
"autoSelectOnUpdate": true
|
"autoSelectOnUpdate": true
|
||||||
@@ -399,7 +412,20 @@
|
|||||||
"widgets_values": [],
|
"widgets_values": [],
|
||||||
"color": "#223",
|
"color": "#223",
|
||||||
"bgColor": "#335",
|
"bgColor": "#335",
|
||||||
"comfyValue": [],
|
"comfyValue": [
|
||||||
|
{
|
||||||
|
"isComfyBoxImageMetadata": true,
|
||||||
|
"comfyUIFile": {
|
||||||
|
"filename": "ComfyUI_04712_.png",
|
||||||
|
"subfolder": "",
|
||||||
|
"type": "output"
|
||||||
|
},
|
||||||
|
"name": "File",
|
||||||
|
"tags": [],
|
||||||
|
"width": 6656,
|
||||||
|
"height": 4096
|
||||||
|
}
|
||||||
|
],
|
||||||
"shownOutputProperties": {},
|
"shownOutputProperties": {},
|
||||||
"saveUserState": false
|
"saveUserState": false
|
||||||
},
|
},
|
||||||
@@ -502,13 +528,39 @@
|
|||||||
],
|
],
|
||||||
"title": "UI.ImageUpload",
|
"title": "UI.ImageUpload",
|
||||||
"properties": {
|
"properties": {
|
||||||
"defaultValue": null,
|
"defaultValue": [
|
||||||
|
{
|
||||||
|
"isComfyBoxImageMetadata": true,
|
||||||
|
"comfyUIFile": {
|
||||||
|
"filename": "ComfyUI_05835_.png",
|
||||||
|
"type": "output",
|
||||||
|
"subfolder": ""
|
||||||
|
},
|
||||||
|
"name": "File",
|
||||||
|
"tags": [],
|
||||||
|
"width": 640,
|
||||||
|
"height": 768
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": []
|
"tags": []
|
||||||
},
|
},
|
||||||
"widgets_values": [],
|
"widgets_values": [],
|
||||||
"color": "#223",
|
"color": "#223",
|
||||||
"bgColor": "#335",
|
"bgColor": "#335",
|
||||||
"comfyValue": [],
|
"comfyValue": [
|
||||||
|
{
|
||||||
|
"isComfyBoxImageMetadata": true,
|
||||||
|
"comfyUIFile": {
|
||||||
|
"filename": "ComfyUI_05835_.png",
|
||||||
|
"type": "output",
|
||||||
|
"subfolder": ""
|
||||||
|
},
|
||||||
|
"name": "File",
|
||||||
|
"tags": [],
|
||||||
|
"width": 640,
|
||||||
|
"height": 768
|
||||||
|
}
|
||||||
|
],
|
||||||
"shownOutputProperties": {},
|
"shownOutputProperties": {},
|
||||||
"saveUserState": false
|
"saveUserState": false
|
||||||
},
|
},
|
||||||
@@ -654,7 +706,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
@@ -679,7 +732,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
@@ -705,7 +759,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
@@ -732,7 +787,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [],
|
"children": [],
|
||||||
@@ -744,7 +800,7 @@
|
|||||||
"id": "2f0371e8-559e-4a58-a5d1-0a50117675fc",
|
"id": "2f0371e8-559e-4a58-a5d1-0a50117675fc",
|
||||||
"nodeId": "578edfae-2767-4b23-9a3b-6edd8ccad1dd",
|
"nodeId": "578edfae-2767-4b23-9a3b-6edd8ccad1dd",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"title": "model_name",
|
"title": "Modal Name",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
@@ -756,7 +812,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [],
|
"children": [],
|
||||||
@@ -779,7 +836,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
@@ -793,7 +851,7 @@
|
|||||||
"id": "db12215f-fd9c-463f-9fbd-26b79e994e0e",
|
"id": "db12215f-fd9c-463f-9fbd-26b79e994e0e",
|
||||||
"nodeId": "59feacdf-de02-4b1e-b8ba-e219ba5126b7",
|
"nodeId": "59feacdf-de02-4b1e-b8ba-e219ba5126b7",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"title": "filename_prefix",
|
"title": "Filename Prefix",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
@@ -805,7 +863,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [],
|
"children": [],
|
||||||
@@ -828,7 +887,8 @@
|
|||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
@@ -849,12 +909,13 @@
|
|||||||
"classes": "",
|
"classes": "",
|
||||||
"style": "",
|
"style": "",
|
||||||
"nodeDisabledState": "hidden",
|
"nodeDisabledState": "hidden",
|
||||||
"variant": "gallery",
|
"variant": "image",
|
||||||
"containerVariant": "hidden",
|
"containerVariant": "hidden",
|
||||||
"openOnStartup": false,
|
"openOnStartup": false,
|
||||||
"buttonVariant": "primary",
|
"buttonVariant": "primary",
|
||||||
"buttonSize": "large",
|
"buttonSize": "large",
|
||||||
"tags": []
|
"tags": [],
|
||||||
|
"destroyChildOnCLose": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"children": [],
|
"children": [],
|
||||||
8
src/global.d.ts
vendored
Normal file
8
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
declare type Item = import('svelte-dnd-action').Item;
|
||||||
|
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action').DndEvent<ItemType>;
|
||||||
|
declare namespace svelte.JSX {
|
||||||
|
interface HTMLAttributes<T> {
|
||||||
|
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void;
|
||||||
|
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Subgraph, type LGraphNode, type LLink, type SerializedLGraphNode, type SerializedLLink, LGraph } from "@litegraph-ts/core"
|
import { Subgraph, type LGraphNode, type LLink, type SerializedLGraphNode, type SerializedLLink, LGraph, type NodeID, type UUID, type Vector2 } from "@litegraph-ts/core"
|
||||||
import layoutStates, { isComfyWidgetNode, type ContainerLayout, type SerializedDragEntry, type WidgetLayout, type DragItemID, type WritableLayoutStateStore, type DragItemEntry, type SerializedLayoutState } from "./stores/layoutStates"
|
import layoutStates, { isComfyWidgetNode, type ContainerLayout, type SerializedDragEntry, type WidgetLayout, type DragItemID, type WritableLayoutStateStore, type DragItemEntry, type SerializedLayoutState } from "./stores/layoutStates"
|
||||||
import type { ComfyWidgetNode } from "./nodes/widgets"
|
import type { ComfyWidgetNode } from "./nodes/widgets"
|
||||||
import type ComfyGraphCanvas from "./ComfyGraphCanvas"
|
import type ComfyGraphCanvas from "./ComfyGraphCanvas"
|
||||||
import C2S from "canvas-to-svg";
|
import C2S from "canvas-to-svg";
|
||||||
import { download } from "./utils";
|
import { calcNodesBoundingBox, download } from "./utils";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import uiState from "./stores/uiState";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In ComfyBox a template contains a subset of nodes in the graph and the set of
|
* In ComfyBox a template contains a subset of nodes in the graph and the set of
|
||||||
@@ -11,17 +13,40 @@ import { download } from "./utils";
|
|||||||
*/
|
*/
|
||||||
export type ComfyBoxTemplate = {
|
export type ComfyBoxTemplate = {
|
||||||
version: 1,
|
version: 1,
|
||||||
|
id: UUID,
|
||||||
|
metadata: ComfyBoxTemplateMetadata,
|
||||||
nodes: LGraphNode[],
|
nodes: LGraphNode[],
|
||||||
links: LLink[],
|
links: LLink[],
|
||||||
container?: DragItemEntry
|
container?: DragItemEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedTemplateLink = [NodeID, number, NodeID, number];
|
||||||
|
|
||||||
|
export type ComfyBoxTemplateMetadata = {
|
||||||
|
title: string,
|
||||||
|
author: string,
|
||||||
|
description: string,
|
||||||
|
tags: string[],
|
||||||
|
category: string,
|
||||||
|
createdAt: number
|
||||||
|
|
||||||
|
// TODO required/optional python extensions
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In ComfyBox a template contains a subset of nodes in the graph and the set of
|
* In ComfyBox a template contains a subset of nodes in the graph and the set of
|
||||||
* components they represent in the UI.
|
* components they represent in the UI.
|
||||||
*/
|
*/
|
||||||
export type SerializedComfyBoxTemplate = {
|
export type SerializedComfyBoxTemplate = {
|
||||||
|
isComfyBoxTemplate: true,
|
||||||
version: 1,
|
version: 1,
|
||||||
|
id: UUID,
|
||||||
|
commitHash: string,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Serialized metadata
|
||||||
|
*/
|
||||||
|
metadata: ComfyBoxTemplateMetadata,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Serialized nodes
|
* Serialized nodes
|
||||||
@@ -31,16 +56,29 @@ export type SerializedComfyBoxTemplate = {
|
|||||||
/*
|
/*
|
||||||
* Serialized inner links
|
* Serialized inner links
|
||||||
*/
|
*/
|
||||||
links: SerializedLLink[],
|
links: SerializedTemplateLink[],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Serialized container type drag item
|
* Serialized container type drag item
|
||||||
*/
|
*/
|
||||||
layout?: SerializedLayoutState
|
layout?: SerializedLayoutState
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SVG of the graph ndoes
|
||||||
|
*/
|
||||||
|
svg?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedComfyBoxTemplateData = {
|
function isSerializedComfyBoxTemplate(param: any): param is SerializedComfyBoxTemplate {
|
||||||
comfyBoxTemplate: SerializedComfyBoxTemplate
|
return param && param.isComfyBoxTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TEMPLATE_METADATA = {
|
||||||
|
title: "New Template",
|
||||||
|
author: "Anonymous",
|
||||||
|
description: "A brand-new ComfyBox template",
|
||||||
|
tags: [],
|
||||||
|
category: "general"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComfyBoxTemplateError = {
|
export type ComfyBoxTemplateError = {
|
||||||
@@ -117,7 +155,7 @@ function unescapeXml(safe) {
|
|||||||
|
|
||||||
const TEMPLATE_SVG_PADDING: number = 50;
|
const TEMPLATE_SVG_PADDING: number = 50;
|
||||||
|
|
||||||
function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padding: number): string {
|
function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, padding: number, extraData?: SerializedComfyBoxTemplate | null): string {
|
||||||
// Calculate the min max bounds for the nodes on the graph
|
// Calculate the min max bounds for the nodes on the graph
|
||||||
const bounds = graph._nodes.reduce(
|
const bounds = graph._nodes.reduce(
|
||||||
(p, n) => {
|
(p, n) => {
|
||||||
@@ -146,6 +184,7 @@ function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padd
|
|||||||
const offset = canvas.ds.offset;
|
const offset = canvas.ds.offset;
|
||||||
const show_info = canvas.show_info;
|
const show_info = canvas.show_info;
|
||||||
const background_image = canvas.background_image;
|
const background_image = canvas.background_image;
|
||||||
|
const clear_background = canvas.clear_background;
|
||||||
const render_canvas_border = canvas.render_canvas_border;
|
const render_canvas_border = canvas.render_canvas_border;
|
||||||
const render_subgraph_panels = canvas.render_subgraph_panels
|
const render_subgraph_panels = canvas.render_subgraph_panels
|
||||||
const render_subgraph_stack_header = canvas.render_subgraph_stack_header
|
const render_subgraph_stack_header = canvas.render_subgraph_stack_header
|
||||||
@@ -153,6 +192,7 @@ function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padd
|
|||||||
canvas.openSubgraph(graph)
|
canvas.openSubgraph(graph)
|
||||||
canvas.show_info = false;
|
canvas.show_info = false;
|
||||||
canvas.background_image = null;
|
canvas.background_image = null;
|
||||||
|
canvas.clear_background = false;
|
||||||
canvas.render_canvas_border = false;
|
canvas.render_canvas_border = false;
|
||||||
canvas.render_subgraph_panels = false;
|
canvas.render_subgraph_panels = false;
|
||||||
canvas.render_subgraph_stack_header = false;
|
canvas.render_subgraph_stack_header = false;
|
||||||
@@ -197,12 +237,10 @@ function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padd
|
|||||||
canvas.ds.offset = [-bounds[0], -bounds[1]];
|
canvas.ds.offset = [-bounds[0], -bounds[1]];
|
||||||
canvas.ctx = svgCtx;
|
canvas.ctx = svgCtx;
|
||||||
|
|
||||||
let saving = false;
|
|
||||||
|
|
||||||
// Trigger saving
|
// Trigger saving
|
||||||
saving = true;
|
canvas.isExportingSVG = true;
|
||||||
canvas.draw(true, true);
|
canvas.draw(true, true);
|
||||||
saving = false;
|
canvas.isExportingSVG = false;
|
||||||
|
|
||||||
// Restore original settings
|
// Restore original settings
|
||||||
canvas.closeSubgraph();
|
canvas.closeSubgraph();
|
||||||
@@ -212,6 +250,7 @@ function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padd
|
|||||||
canvas.ds.offset = offset;
|
canvas.ds.offset = offset;
|
||||||
canvas.ctx = ctx;
|
canvas.ctx = ctx;
|
||||||
canvas.show_info = show_info;
|
canvas.show_info = show_info;
|
||||||
|
canvas.clear_background = clear_background;
|
||||||
canvas.background_image = background_image;
|
canvas.background_image = background_image;
|
||||||
canvas.render_canvas_border = render_canvas_border;
|
canvas.render_canvas_border = render_canvas_border;
|
||||||
canvas.render_subgraph_panels = render_subgraph_panels;
|
canvas.render_subgraph_panels = render_subgraph_panels;
|
||||||
@@ -219,15 +258,33 @@ function renderSvg(canvas: ComfyGraphCanvas, graph: LGraph, extraData: any, padd
|
|||||||
|
|
||||||
canvas.draw(true, true);
|
canvas.draw(true, true);
|
||||||
|
|
||||||
// Convert to SVG, embed graph and save
|
let svg = svgCtx.getSerializedSvg(true)
|
||||||
// const json = JSON.stringify(app.graph.serialize());
|
|
||||||
const json = JSON.stringify(extraData);
|
|
||||||
const svg = svgCtx.getSerializedSvg(true).replace("</svg>", `<desc>${escapeXml(json)}</desc></svg>`);
|
|
||||||
|
|
||||||
return svg
|
return svg
|
||||||
}
|
}
|
||||||
|
|
||||||
function pruneDetachedLinks(nodes: SerializedLGraphNode[], links: SerializedLLink[]): [SerializedLGraphNode[], SerializedLLink[]] {
|
export function embedTemplateInSvg(template: SerializedComfyBoxTemplate): string {
|
||||||
|
let oldSvg = template.svg;
|
||||||
|
template.svg = undefined;
|
||||||
|
const json = JSON.stringify(template);
|
||||||
|
const svg = oldSvg.replace("</svg>", `<desc>${escapeXml(json)}</desc></svg>`);
|
||||||
|
template.svg = oldSvg;
|
||||||
|
return svg
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves nodes so their origin is at (0, 0)
|
||||||
|
*/
|
||||||
|
function relocateNodes(nodes: SerializedLGraphNode[]): SerializedLGraphNode[] {
|
||||||
|
let [min_x, min_y, max_x, max_y] = calcNodesBoundingBox(nodes);
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
node.pos = [node.pos[0] - min_x, node.pos[1] - min_y];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneDetachedLinks(nodes: SerializedLGraphNode[], links: SerializedTemplateLink[]): [SerializedLGraphNode[], SerializedTemplateLink[]] {
|
||||||
const nodeIds = new Set(nodes.map(n => n.id));
|
const nodeIds = new Set(nodes.map(n => n.id));
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@@ -247,12 +304,16 @@ function pruneDetachedLinks(nodes: SerializedLGraphNode[], links: SerializedLLin
|
|||||||
}
|
}
|
||||||
|
|
||||||
links = links.filter(l => {
|
links = links.filter(l => {
|
||||||
return nodeIds.has(l[1]) && nodeIds.has(l[3]);
|
return nodeIds.has(l[0]) && nodeIds.has(l[2]);
|
||||||
})
|
})
|
||||||
|
|
||||||
return [nodes, links]
|
return [nodes, links]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convLinkForTemplate(link: LLink): SerializedTemplateLink {
|
||||||
|
return [link.origin_id, link.origin_slot, link.target_id, link.target_slot];
|
||||||
|
}
|
||||||
|
|
||||||
export function serializeTemplate(canvas: ComfyGraphCanvas, template: ComfyBoxTemplate): SerializedComfyBoxTemplate {
|
export function serializeTemplate(canvas: ComfyGraphCanvas, template: ComfyBoxTemplate): SerializedComfyBoxTemplate {
|
||||||
let graph: LGraph
|
let graph: LGraph
|
||||||
if (template.nodes.length === 1 && template.nodes[0].is(Subgraph)) {
|
if (template.nodes.length === 1 && template.nodes[0].is(Subgraph)) {
|
||||||
@@ -266,27 +327,62 @@ export function serializeTemplate(canvas: ComfyGraphCanvas, template: ComfyBoxTe
|
|||||||
if (layoutState == null)
|
if (layoutState == null)
|
||||||
throw "Couldn't find layout for template being serialized!"
|
throw "Couldn't find layout for template being serialized!"
|
||||||
|
|
||||||
|
uiState.update(s => { s.forceSaveUserState = false; return s; });
|
||||||
|
|
||||||
|
const metadata = template.metadata;
|
||||||
let nodes = template.nodes.map(n => n.serialize());
|
let nodes = template.nodes.map(n => n.serialize());
|
||||||
let links = template.links.map(l => l.serialize());
|
let links = template.links.map(convLinkForTemplate);
|
||||||
const layout = layoutState.serializeAtRoot(template.container.dragItem.id);
|
const layout = layoutState.serializeAtRoot(template.container.dragItem.id);
|
||||||
|
|
||||||
|
uiState.update(s => { s.forceSaveUserState = null; return s; });
|
||||||
|
|
||||||
|
nodes = relocateNodes(nodes);
|
||||||
[nodes, links] = pruneDetachedLinks(nodes, links);
|
[nodes, links] = pruneDetachedLinks(nodes, links);
|
||||||
|
|
||||||
let comfyBoxTemplate: SerializedComfyBoxTemplate = {
|
const svg = renderSvg(canvas, graph, TEMPLATE_SVG_PADDING);
|
||||||
|
|
||||||
|
const serTemplate: SerializedComfyBoxTemplate = {
|
||||||
|
isComfyBoxTemplate: true,
|
||||||
version: 1,
|
version: 1,
|
||||||
nodes: nodes,
|
commitHash: __GIT_COMMIT_HASH__,
|
||||||
links: links,
|
id: template.id,
|
||||||
layout: layout
|
metadata,
|
||||||
|
nodes,
|
||||||
|
links,
|
||||||
|
layout,
|
||||||
|
svg
|
||||||
}
|
}
|
||||||
|
|
||||||
let templateData: SerializedComfyBoxTemplateData = {
|
return serTemplate;
|
||||||
comfyBoxTemplate
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const svg = renderSvg(canvas, graph, templateData, TEMPLATE_SVG_PADDING)
|
export function deserializeTemplateFromSVG(file: File): Promise<SerializedComfyBoxTemplate> {
|
||||||
download("workflow.svg", svg, "image/svg+xml");
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async () => {
|
||||||
|
const svg = reader.result as string;
|
||||||
|
let template = null;
|
||||||
|
|
||||||
return comfyBoxTemplate
|
// Extract embedded workflow from desc tags
|
||||||
|
const descEnd = svg.lastIndexOf("</desc>");
|
||||||
|
if (descEnd !== -1) {
|
||||||
|
const descStart = svg.lastIndexOf("<desc>", descEnd);
|
||||||
|
if (descStart !== -1) {
|
||||||
|
const json = svg.substring(descStart + 6, descEnd);
|
||||||
|
template = JSON.parse(unescapeXml(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSerializedComfyBoxTemplate(template)) {
|
||||||
|
reject("Invalid template format!")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
template.svg = svg;
|
||||||
|
resolve(template)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -303,10 +399,15 @@ export function createTemplate(nodes: LGraphNode[]): ComfyBoxTemplateResult {
|
|||||||
const layout = layoutStates.getLayoutByNode(nodes[0])
|
const layout = layoutStates.getLayoutByNode(nodes[0])
|
||||||
if (layout == null) {
|
if (layout == null) {
|
||||||
return {
|
return {
|
||||||
error: "Subgraph not contained in a layout!"
|
error: "Node(s) not contained in a layout!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let title = "New Template";
|
||||||
|
if (nodes.length === 1) {
|
||||||
|
title = nodes[0].title || title;
|
||||||
|
}
|
||||||
|
|
||||||
if (widgetNodes.length > 0) {
|
if (widgetNodes.length > 0) {
|
||||||
// Find the highest-level container that contains all these nodes and
|
// Find the highest-level container that contains all these nodes and
|
||||||
// contains no other widgets
|
// contains no other widgets
|
||||||
@@ -325,6 +426,8 @@ export function createTemplate(nodes: LGraphNode[]): ComfyBoxTemplateResult {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
|
id: uuidv4(),
|
||||||
|
metadata: { ...DEFAULT_TEMPLATE_METADATA, title, createdAt: Date.now() },
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
links: links,
|
links: links,
|
||||||
container: container
|
container: container
|
||||||
@@ -334,6 +437,8 @@ export function createTemplate(nodes: LGraphNode[]): ComfyBoxTemplateResult {
|
|||||||
// No UI to serialize.
|
// No UI to serialize.
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
|
id: uuidv4(),
|
||||||
|
metadata: { ...DEFAULT_TEMPLATE_METADATA, title, createdAt: Date.now() },
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
links: links,
|
links: links,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas, type LGraphRemoveNodeOptions, Subgraph, type LGraphAddNodeMode } from "@litegraph-ts/core";
|
import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas, type LGraphRemoveNodeOptions, Subgraph, type LGraphAddNodeMode, type SerializedLGraphNode, type Vector2, type NodeID, reassignGraphIDs, type GraphIDMapping, type SerializedLGraph } from "@litegraph-ts/core";
|
||||||
import GraphSync from "./GraphSync";
|
import GraphSync from "./GraphSync";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
@@ -11,8 +11,10 @@ import type { ComfyComboNode, ComfyWidgetNode } from "./nodes/widgets";
|
|||||||
import selectionState from "./stores/selectionState";
|
import selectionState from "./stores/selectionState";
|
||||||
import type { WritableLayoutStateStore } from "./stores/layoutStates";
|
import type { WritableLayoutStateStore } from "./stores/layoutStates";
|
||||||
import layoutStates from "./stores/layoutStates";
|
import layoutStates from "./stores/layoutStates";
|
||||||
import type { ComfyWorkflow, WorkflowInstID } from "./stores/workflowState";
|
import type { ComfyBoxWorkflow, WorkflowInstID } from "./stores/workflowState";
|
||||||
import workflowState from "./stores/workflowState";
|
import workflowState from "./stores/workflowState";
|
||||||
|
import type { SerializedComfyBoxTemplate } from "./ComfyBoxTemplate";
|
||||||
|
import { v4 as uuidv4 } from "uuid"
|
||||||
|
|
||||||
type ComfyGraphEvents = {
|
type ComfyGraphEvents = {
|
||||||
configured: (graph: LGraph) => void
|
configured: (graph: LGraph) => void
|
||||||
@@ -30,13 +32,17 @@ export default class ComfyGraph extends LGraph {
|
|||||||
|
|
||||||
workflowID: WorkflowInstID | null = null;
|
workflowID: WorkflowInstID | null = null;
|
||||||
|
|
||||||
get workflow(): ComfyWorkflow | null {
|
get workflow(): ComfyBoxWorkflow | null {
|
||||||
const workflowID = (this.getRootGraph() as ComfyGraph)?.workflowID;
|
const workflowID = (this.getRootGraph() as ComfyGraph)?.workflowID;
|
||||||
if (workflowID == null)
|
if (workflowID == null)
|
||||||
return null;
|
return null;
|
||||||
return workflowState.getWorkflow(workflowID)
|
return workflowState.getWorkflow(workflowID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get layout(): WritableLayoutStateStore | null {
|
||||||
|
return this.workflow?.layout;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(workflowID?: WorkflowInstID) {
|
constructor(workflowID?: WorkflowInstID) {
|
||||||
super();
|
super();
|
||||||
this.workflowID = workflowID;
|
this.workflowID = workflowID;
|
||||||
@@ -217,4 +223,63 @@ export default class ComfyGraph extends LGraph {
|
|||||||
// console.debug("ConnectionChange", node);
|
// console.debug("ConnectionChange", node);
|
||||||
this.eventBus.emit("nodeConnectionChanged", kind, node, slot, targetNode, targetSlot);
|
this.eventBus.emit("nodeConnectionChanged", kind, node, slot, targetNode, targetSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inserts a template.
|
||||||
|
* Layout deserialization must be handled afterwards.
|
||||||
|
* NOTE: Modifies the template in-place, be sure you cloned it beforehand!
|
||||||
|
*/
|
||||||
|
insertTemplate(template: SerializedComfyBoxTemplate, pos: Vector2): Record<NodeID, LGraphNode> {
|
||||||
|
const minPos = [0, 0]
|
||||||
|
|
||||||
|
const templateNodeIDToNewNode: Record<NodeID, LGraphNode> = {}
|
||||||
|
|
||||||
|
var nodes = [];
|
||||||
|
for (var i = 0; i < template.nodes.length; ++i) {
|
||||||
|
var node_data = template.nodes[i];
|
||||||
|
var node = LiteGraph.createNode(node_data.type);
|
||||||
|
|
||||||
|
let mapping: GraphIDMapping = null;
|
||||||
|
if (node_data.type === "graph/subgraph") {
|
||||||
|
mapping = reassignGraphIDs((node_data as any).subgraph as SerializedLGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
const prevNodeId = node_data.id;
|
||||||
|
node_data.id = uuidv4();
|
||||||
|
templateNodeIDToNewNode[prevNodeId] = node
|
||||||
|
|
||||||
|
node.configure(node_data);
|
||||||
|
|
||||||
|
if (mapping) {
|
||||||
|
for (const subnode of (node as Subgraph).subgraph.iterateNodesInOrderRecursive()) {
|
||||||
|
const oldNodeID = mapping.nodeIDs[subnode.id];
|
||||||
|
templateNodeIDToNewNode[oldNodeID] = subnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.pos[0] += pos[0] - minPos[0]; //+= 5;
|
||||||
|
node.pos[1] += pos[1] - minPos[1]; //+= 5;
|
||||||
|
|
||||||
|
this.add(node, { doProcessChange: false, addedBy: "template" as any });
|
||||||
|
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//create links
|
||||||
|
for (var i = 0; i < template.links.length; ++i) {
|
||||||
|
var link_info = template.links[i];
|
||||||
|
var origin_node = templateNodeIDToNewNode[link_info[0]];
|
||||||
|
var target_node = templateNodeIDToNewNode[link_info[2]];
|
||||||
|
if (origin_node && target_node)
|
||||||
|
origin_node.connect(link_info[1], target_node, link_info[3]);
|
||||||
|
else
|
||||||
|
console.error("[ComfyGraphCanvas] nodes missing on template insertion!", link_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.afterChange();
|
||||||
|
|
||||||
|
return templateNodeIDToNewNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { BuiltInSlotShape, LGraphCanvas, LGraphNode, LLink, LiteGraph, NodeMode, Subgraph, TitleMode, type ContextMenuItem, type IContextMenuItem, type NodeID, type Vector2, type Vector4, type MouseEventExt, ContextMenu } from "@litegraph-ts/core";
|
import { BuiltInSlotShape, LGraphCanvas, LGraphNode, LLink, LiteGraph, NodeMode, Subgraph, TitleMode, type ContextMenuItem, type IContextMenuItem, type NodeID, type Vector2, type Vector4, type MouseEventExt, ContextMenu, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
import { get, type Unsubscriber } from "svelte/store";
|
import { get, type Unsubscriber } from "svelte/store";
|
||||||
import type ComfyGraph from "./ComfyGraph";
|
import type ComfyGraph from "./ComfyGraph";
|
||||||
import type ComfyApp from "./components/ComfyApp";
|
import type ComfyApp from "./components/ComfyApp";
|
||||||
import { ComfyReroute } from "./nodes";
|
import { ComfyReroute } from "./nodes";
|
||||||
import layoutStates from "./stores/layoutStates";
|
import layoutStates, { type ContainerLayout } from "./stores/layoutStates";
|
||||||
import queueState from "./stores/queueState";
|
import queueState from "./stores/queueState";
|
||||||
import selectionState from "./stores/selectionState";
|
import selectionState from "./stores/selectionState";
|
||||||
import { createTemplate, type ComfyBoxTemplate, serializeTemplate } from "./ComfyBoxTemplate";
|
import templateState from "./stores/templateState";
|
||||||
|
import { createTemplate, type ComfyBoxTemplate, serializeTemplate, type SerializedComfyBoxTemplate } from "./ComfyBoxTemplate";
|
||||||
import notify from "./notify";
|
import notify from "./notify";
|
||||||
|
import { calcNodesBoundingBox } from "./utils";
|
||||||
|
|
||||||
export type SerializedGraphCanvasState = {
|
export type SerializedGraphCanvasState = {
|
||||||
offset: Vector2,
|
offset: Vector2,
|
||||||
@@ -17,6 +19,7 @@ export type SerializedGraphCanvasState = {
|
|||||||
export default class ComfyGraphCanvas extends LGraphCanvas {
|
export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||||
app: ComfyApp | null;
|
app: ComfyApp | null;
|
||||||
private _unsubscribe: Unsubscriber;
|
private _unsubscribe: Unsubscriber;
|
||||||
|
isExportingSVG: boolean = false;
|
||||||
|
|
||||||
get comfyGraph(): ComfyGraph | null {
|
get comfyGraph(): ComfyGraph | null {
|
||||||
return this.graph as ComfyGraph;
|
return this.graph as ComfyGraph;
|
||||||
@@ -439,19 +442,35 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
this.graph.add(subgraph)
|
this.graph.add(subgraph)
|
||||||
}
|
}
|
||||||
|
|
||||||
override getCanvasMenuOptions(): ContextMenuItem[] {
|
/*
|
||||||
const options = super.getCanvasMenuOptions();
|
* Inserts a ComfyBox template. Logic is similar to pasting from the
|
||||||
|
* clipboard in vanilla litegraph.
|
||||||
|
*/
|
||||||
|
insertTemplate(template: SerializedComfyBoxTemplate, pos: Vector2, container: ContainerLayout, containerIndex: number): [LGraphNode[], IDragItem] {
|
||||||
|
const comfyGraph = this.graph as ComfyGraph;
|
||||||
|
|
||||||
options.push(
|
let [min_x, min_y, max_x, max_y] = calcNodesBoundingBox(template.nodes);
|
||||||
{
|
|
||||||
content: "Convert to Subgraph",
|
|
||||||
has_submenu: false,
|
|
||||||
disabled: Object.keys(this.selected_nodes).length === 0,
|
|
||||||
callback: this.convertToSubgraph.bind(this)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return options
|
const width = max_x - min_x
|
||||||
|
const height = max_y - min_y
|
||||||
|
|
||||||
|
pos[0] -= width / 2
|
||||||
|
pos[1] -= height / 2
|
||||||
|
|
||||||
|
const layout = comfyGraph.layout;
|
||||||
|
if (layout == null) {
|
||||||
|
console.error("[ComfyGraphCanvas] graph has no layout!", comfyGraph)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following operations modify the template in-place, so be sure it's been cloned first
|
||||||
|
const cloned = LiteGraph.cloneObject(template)
|
||||||
|
const nodeMapping = comfyGraph.insertTemplate(cloned, pos);
|
||||||
|
const templateLayoutRoot = layout.insertTemplate(cloned, comfyGraph, nodeMapping, container, containerIndex);
|
||||||
|
|
||||||
|
this.selectNodes(Object.values(nodeMapping).filter(n => n.graph === this.graph));
|
||||||
|
|
||||||
|
return [Object.values(nodeMapping), templateLayoutRoot]
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAsTemplate(_value: IContextMenuItem, _options, mouseEvent, prevMenu, node?: LGraphNode) {
|
saveAsTemplate(_value: IContextMenuItem, _options, mouseEvent, prevMenu, node?: LGraphNode) {
|
||||||
@@ -467,9 +486,35 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
|
|
||||||
const template = result as ComfyBoxTemplate;
|
const template = result as ComfyBoxTemplate;
|
||||||
|
|
||||||
console.warn("TEMPLATEFOUND", template)
|
|
||||||
|
|
||||||
const serialized = serializeTemplate(this, template);
|
const serialized = serializeTemplate(this, template);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (templateState.add(serialized)) {
|
||||||
|
notify("Template saved!", { type: "success" })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notify("Failed to save template: already exists in LocalStorage", { type: "error" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Quota exceeded?
|
||||||
|
notify(`Failed to save template: ${error}`, { type: "error", timeout: 10000 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override getCanvasMenuOptions(): ContextMenuItem[] {
|
||||||
|
const options = super.getCanvasMenuOptions();
|
||||||
|
|
||||||
|
options.push(
|
||||||
|
{
|
||||||
|
content: "Convert to Subgraph",
|
||||||
|
has_submenu: false,
|
||||||
|
disabled: Object.keys(this.selected_nodes).length === 0,
|
||||||
|
callback: this.convertToSubgraph.bind(this)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
override getNodeMenuOptions(node: LGraphNode): ContextMenuItem[] {
|
override getNodeMenuOptions(node: LGraphNode): ContextMenuItem[] {
|
||||||
@@ -510,4 +555,17 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onRenderBackground(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): boolean {
|
||||||
|
if (this.isExportingSVG) {
|
||||||
|
ctx.clearRect(
|
||||||
|
this.visible_area[0],
|
||||||
|
this.visible_area[1],
|
||||||
|
this.visible_area[2],
|
||||||
|
this.visible_area[3]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { parse } from 'csv-parse/browser/esm/sync';
|
import { parse } from 'csv-parse/browser/esm/sync';
|
||||||
import { timeExecutionMs } from './utils';
|
import { timeExecutionMs } from './utils';
|
||||||
import { insertCompletionText, type Completion, type CompletionContext, type CompletionResult, type CompletionSource, type CompletionConfig, autocompletion } from '@codemirror/autocomplete';
|
import { insertCompletionText, type Completion, type CompletionContext, type CompletionResult, type CompletionSource, autocompletion } from '@codemirror/autocomplete';
|
||||||
import { syntaxTree } from '@codemirror/language';
|
import { syntaxTree } from '@codemirror/language';
|
||||||
import type { Extension, TransactionSpec } from '@codemirror/state';
|
import type { Extension } from '@codemirror/state';
|
||||||
import type { EditorView } from '@codemirror/view';
|
import type { EditorView } from '@codemirror/view';
|
||||||
import type { StyleSpec } from "style-mod"
|
import type { StyleSpec } from "style-mod"
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ export const TAG_CATEGORY_COLORS: StyleSpec = Object.values(TAG_CATEGORY_DATA)
|
|||||||
[`.cm-autocompletion-${d.name}`, { color: d.color + " !important" }],
|
[`.cm-autocompletion-${d.name}`, { color: d.color + " !important" }],
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.reduce((dict, el) => (dict[el[0]] = el[1], dict), {})
|
.reduce((dict: StyleSpec, el: [string, any]) => (dict[el[0]] = el[1], dict), {})
|
||||||
|
|
||||||
export type DanbooruTag = {
|
export type DanbooruTag = {
|
||||||
text: string,
|
text: string,
|
||||||
@@ -116,7 +116,6 @@ export default class DanbooruTags {
|
|||||||
})
|
})
|
||||||
|
|
||||||
console.log(`Parsed ${this.tags.length} tags in ${time / 1000}ms.`)
|
console.log(`Parsed ${this.tags.length} tags in ${time / 1000}ms.`)
|
||||||
console.error(this.tags[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete(context: CompletionContext): CompletionResult {
|
autocomplete(context: CompletionContext): CompletionResult {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import { isHidden } from "$lib/widgets/utils";
|
import { isHidden } from "$lib/widgets/utils";
|
||||||
|
import { handleContainerConsider, handleContainerFinalize } from "./utils";
|
||||||
|
|
||||||
export let layoutState: WritableLayoutStateStore;
|
export let layoutState: WritableLayoutStateStore;
|
||||||
export let container: ContainerLayout | null = null;
|
export let container: ContainerLayout | null = null;
|
||||||
@@ -46,14 +47,12 @@
|
|||||||
isOpen = container.isOpen
|
isOpen = container.isOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerConsider(layoutState, container, evt)
|
||||||
// console.log(dragItems);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleFinalize(evt: any) {
|
function handleFinalize(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerFinalize(layoutState, container, evt)
|
||||||
// Ensure dragging is stopped on drag finish
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleClick(e: CustomEvent<boolean>) {
|
function handleClick(e: CustomEvent<boolean>) {
|
||||||
@@ -85,6 +84,7 @@
|
|||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit={edit}
|
class:edit={edit}
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
|
type: "layout",
|
||||||
items: children,
|
items: children,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
centreDraggedOnCursor: true,
|
centreDraggedOnCursor: true,
|
||||||
@@ -219,8 +219,7 @@
|
|||||||
|
|
||||||
.animation-wrapper {
|
.animation-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 100;
|
flex: 1 1 0%;
|
||||||
flex-basis: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-widget:hover {
|
.handle-widget:hover {
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
|
|
||||||
import {fade} from 'svelte/transition';
|
import {fade} from 'svelte/transition';
|
||||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
|
||||||
import {cubicIn} from 'svelte/easing';
|
import {cubicIn} from 'svelte/easing';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { isHidden } from "$lib/widgets/utils";
|
import { isHidden } from "$lib/widgets/utils";
|
||||||
|
import { handleContainerConsider, handleContainerFinalize } from "./utils";
|
||||||
|
|
||||||
export let layoutState: WritableLayoutStateStore;
|
export let layoutState: WritableLayoutStateStore;
|
||||||
export let container: ContainerLayout | null = null;
|
export let container: ContainerLayout | null = null;
|
||||||
@@ -45,14 +45,12 @@
|
|||||||
attrsChanged = null
|
attrsChanged = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerConsider(layoutState, container, evt);
|
||||||
// console.log(dragItems);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleFinalize(evt: any) {
|
function handleFinalize(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerFinalize(layoutState, container, evt);
|
||||||
// Ensure dragging is stopped on drag finish
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function _startDrag(e: MouseEvent | TouchEvent) {
|
function _startDrag(e: MouseEvent | TouchEvent) {
|
||||||
@@ -83,6 +81,7 @@
|
|||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit
|
class:edit
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
|
type: "layout",
|
||||||
items: children,
|
items: children,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
centreDraggedOnCursor: true,
|
centreDraggedOnCursor: true,
|
||||||
@@ -261,7 +260,9 @@
|
|||||||
|
|
||||||
.animation-wrapper {
|
.animation-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-basis: 0%;
|
||||||
flex-grow: 100;
|
flex-grow: 100;
|
||||||
|
flex-shrink: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-hidden {
|
.handle-hidden {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import Sidebar from "./Sidebar.svelte";
|
import Sidebar from "./Sidebar.svelte";
|
||||||
import SidebarItem from "./SidebarItem.svelte";
|
import SidebarItem from "./SidebarItem.svelte";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import ComfyWorkflowsView from "./ComfyWorkflowsView.svelte";
|
import ComfyBoxWorkflowsView from "./ComfyBoxWorkflowsView.svelte";
|
||||||
import GlobalModal from "./GlobalModal.svelte";
|
import GlobalModal from "./GlobalModal.svelte";
|
||||||
|
|
||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<div id="container">
|
<div id="container">
|
||||||
<Sidebar selected="generate">
|
<Sidebar selected="generate">
|
||||||
<SidebarItem id="generate" name="Generate" icon={Image}>
|
<SidebarItem id="generate" name="Generate" icon={Image}>
|
||||||
<ComfyWorkflowsView {app} {uiTheme} />
|
<ComfyBoxWorkflowsView {app} {uiTheme} />
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
<SidebarItem id="settings" name="Settings" icon={Gear}>
|
<SidebarItem id="settings" name="Settings" icon={Gear}>
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import A1111PromptModal from "./modal/A1111PromptModal.svelte";
|
|||||||
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
||||||
import MissingNodeTypesModal from "./modal/MissingNodeTypesModal.svelte";
|
import MissingNodeTypesModal from "./modal/MissingNodeTypesModal.svelte";
|
||||||
import WorkflowLoadErrorModal from "./modal/WorkflowLoadErrorModal.svelte";
|
import WorkflowLoadErrorModal from "./modal/WorkflowLoadErrorModal.svelte";
|
||||||
|
import EditTemplateModal from "./modal/EditTemplateModal.svelte";
|
||||||
|
|
||||||
import * as nodes from "$lib/nodes/index";
|
import * as nodes from "$lib/nodes/index";
|
||||||
|
|
||||||
@@ -21,13 +22,13 @@ import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
|||||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||||
import configState from "$lib/stores/configState";
|
import configState, { type ConfigState } from "$lib/stores/configState";
|
||||||
import layoutStates, { defaultWorkflowAttributes, type SerializedLayoutState } from "$lib/stores/layoutStates";
|
import layoutStates, { defaultWorkflowAttributes, isComfyWidgetNode, type SerializedLayoutState } from "$lib/stores/layoutStates";
|
||||||
import modalState from "$lib/stores/modalState";
|
import modalState from "$lib/stores/modalState";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
import selectionState from "$lib/stores/selectionState";
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import workflowState, { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
import workflowState, { ComfyBoxWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||||
import type { SerializedPromptOutput } from "$lib/utils";
|
import type { SerializedPromptOutput } from "$lib/utils";
|
||||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range } from "$lib/utils";
|
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range } from "$lib/utils";
|
||||||
import { tick } from "svelte";
|
import { tick } from "svelte";
|
||||||
@@ -35,6 +36,8 @@ import { type SvelteComponentDev } from "svelte/internal";
|
|||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import ComfyPromptSerializer, { isActiveBackendNode, UpstreamNodeLocator } from "./ComfyPromptSerializer";
|
import ComfyPromptSerializer, { isActiveBackendNode, UpstreamNodeLocator } from "./ComfyPromptSerializer";
|
||||||
import DanbooruTags from "$lib/DanbooruTags";
|
import DanbooruTags from "$lib/DanbooruTags";
|
||||||
|
import { deserializeTemplateFromSVG } from "$lib/ComfyBoxTemplate";
|
||||||
|
import templateState from "$lib/stores/templateState";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ export type OpenWorkflowOptions = {
|
|||||||
type PromptQueueItem = {
|
type PromptQueueItem = {
|
||||||
num: number,
|
num: number,
|
||||||
batchCount: number
|
batchCount: number
|
||||||
workflow: ComfyWorkflow
|
workflow: ComfyBoxWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
export type A1111PromptAndInfo = {
|
export type A1111PromptAndInfo = {
|
||||||
@@ -189,6 +192,11 @@ export default class ComfyApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.loadConfig();
|
||||||
|
|
||||||
|
this.api.hostname = get(configState).comfyUIHostname
|
||||||
|
this.api.port = get(configState).comfyUIPort
|
||||||
|
|
||||||
this.setupColorScheme()
|
this.setupColorScheme()
|
||||||
|
|
||||||
this.rootEl = document.getElementById("app-root") as HTMLDivElement;
|
this.rootEl = document.getElementById("app-root") as HTMLDivElement;
|
||||||
@@ -220,7 +228,7 @@ export default class ComfyApp {
|
|||||||
setActive: false
|
setActive: false
|
||||||
}
|
}
|
||||||
await this.initDefaultWorkflow("defaultWorkflow", options);
|
await this.initDefaultWorkflow("defaultWorkflow", options);
|
||||||
await this.initDefaultWorkflow("upscale", options);
|
await this.initDefaultWorkflow("upscaleByModel", options);
|
||||||
await this.initDefaultWorkflow("conditioningRegions", options);
|
await this.initDefaultWorkflow("conditioningRegions", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +240,7 @@ export default class ComfyApp {
|
|||||||
this.addKeyboardHandler();
|
this.addKeyboardHandler();
|
||||||
|
|
||||||
await this.updateHistoryAndQueue();
|
await this.updateHistoryAndQueue();
|
||||||
|
templateState.load();
|
||||||
|
|
||||||
await this.initFrontendFeatures();
|
await this.initFrontendFeatures();
|
||||||
|
|
||||||
@@ -248,7 +257,24 @@ export default class ComfyApp {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
async loadConfig() {
|
||||||
|
try {
|
||||||
|
const config = await fetch(`/config.json`);
|
||||||
|
const state = await config.json() as ConfigState;
|
||||||
|
configState.set(state);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`Failed to load config`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resizeCanvas() {
|
resizeCanvas() {
|
||||||
|
if (!this.canvasEl)
|
||||||
|
return;
|
||||||
|
|
||||||
this.canvasEl.width = this.canvasEl.parentElement.offsetWidth;
|
this.canvasEl.width = this.canvasEl.parentElement.offsetWidth;
|
||||||
this.canvasEl.height = this.canvasEl.parentElement.offsetHeight;
|
this.canvasEl.height = this.canvasEl.parentElement.offsetHeight;
|
||||||
this.canvasEl.style.width = ""
|
this.canvasEl.style.width = ""
|
||||||
@@ -256,7 +282,7 @@ export default class ComfyApp {
|
|||||||
this.lCanvas.draw(true, true);
|
this.lCanvas.draw(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(workflow: ComfyWorkflow, canvas?: SerializedGraphCanvasState): SerializedAppState {
|
serialize(workflow: ComfyBoxWorkflow, canvas?: SerializedGraphCanvasState): SerializedAppState {
|
||||||
const layoutState = layoutStates.getLayout(workflow.id);
|
const layoutState = layoutStates.getLayout(workflow.id);
|
||||||
if (layoutState == null)
|
if (layoutState == null)
|
||||||
throw new Error("Workflow has no layout!")
|
throw new Error("Workflow has no layout!")
|
||||||
@@ -278,7 +304,7 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
saveStateToLocalStorage() {
|
saveStateToLocalStorage() {
|
||||||
try {
|
try {
|
||||||
uiState.update(s => { s.isSavingToLocalStorage = true; return s; })
|
uiState.update(s => { s.forceSaveUserState = true; return s; })
|
||||||
const state = get(workflowState)
|
const state = get(workflowState)
|
||||||
const workflows = state.openedWorkflows
|
const workflows = state.openedWorkflows
|
||||||
const savedWorkflows = workflows.map(w => this.serialize(w));
|
const savedWorkflows = workflows.map(w => this.serialize(w));
|
||||||
@@ -294,7 +320,7 @@ export default class ComfyApp {
|
|||||||
notify(`Failed saving to local storage:\n${err}`, { type: "error" })
|
notify(`Failed saving to local storage:\n${err}`, { type: "error" })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
uiState.update(s => { s.isSavingToLocalStorage = false; return s; })
|
uiState.update(s => { s.forceSaveUserState = null; return s; })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,12 +485,16 @@ export default class ComfyApp {
|
|||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflow && typeof workflow.createdBy === "string") {
|
if (workflow == null)
|
||||||
this.openWorkflow(workflow);
|
return;
|
||||||
}
|
|
||||||
else {
|
if (typeof workflow === "object") {
|
||||||
// TODO handle vanilla workflows
|
if (typeof workflow.createdBy === "string")
|
||||||
throw new Error("Workflow was not in ComfyBox format!")
|
this.openWorkflow(workflow);
|
||||||
|
else {
|
||||||
|
// TODO handle vanilla workflows
|
||||||
|
throw new Error("Workflow was not in ComfyBox format!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -631,7 +661,7 @@ export default class ComfyApp {
|
|||||||
refreshCombos: true,
|
refreshCombos: true,
|
||||||
warnMissingNodeTypes: true
|
warnMissingNodeTypes: true
|
||||||
}
|
}
|
||||||
): Promise<ComfyWorkflow> {
|
): Promise<ComfyBoxWorkflow> {
|
||||||
if (data.version !== COMFYBOX_SERIAL_VERSION) {
|
if (data.version !== COMFYBOX_SERIAL_VERSION) {
|
||||||
const mes = `Invalid ComfyBox saved data format: ${data.version} `
|
const mes = `Invalid ComfyBox saved data format: ${data.version} `
|
||||||
notify(mes, { type: "error" })
|
notify(mes, { type: "error" })
|
||||||
@@ -640,7 +670,7 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
this.clean();
|
this.clean();
|
||||||
|
|
||||||
let workflow: ComfyWorkflow;
|
let workflow: ComfyBoxWorkflow;
|
||||||
try {
|
try {
|
||||||
workflow = workflowState.openWorkflow(this.lCanvas, data, options.setActive);
|
workflow = workflowState.openWorkflow(this.lCanvas, data, options.setActive);
|
||||||
}
|
}
|
||||||
@@ -761,6 +791,30 @@ export default class ComfyApp {
|
|||||||
await this.openWorkflow(state, options)
|
await this.openWorkflow(state, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveWorkflowStateAsDefault(workflow: ComfyBoxWorkflow | null) {
|
||||||
|
workflow ||= workflowState.getActiveWorkflow();
|
||||||
|
if (workflow == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||||
|
if (isComfyWidgetNode(node)) {
|
||||||
|
node.properties.defaultValue = node.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCurrentWorkflow() {
|
||||||
|
const workflow = workflowState.getActiveWorkflow();
|
||||||
|
if (workflow == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||||
|
if (isComfyWidgetNode(node)) {
|
||||||
|
node.setValue(node.properties.defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.clean();
|
this.clean();
|
||||||
|
|
||||||
@@ -798,6 +852,8 @@ export default class ComfyApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.saveWorkflowStateAsDefault(workflow);
|
||||||
|
|
||||||
const promptFilename = get(configState).promptForWorkflowName;
|
const promptFilename = get(configState).promptForWorkflowName;
|
||||||
|
|
||||||
const title = workflow.attrs.title.trim() || "workflow"
|
const title = workflow.attrs.title.trim() || "workflow"
|
||||||
@@ -831,7 +887,7 @@ export default class ComfyApp {
|
|||||||
* Converts the current graph workflow for sending to the API
|
* Converts the current graph workflow for sending to the API
|
||||||
* @returns The workflow and node links
|
* @returns The workflow and node links
|
||||||
*/
|
*/
|
||||||
graphToPrompt(workflow: ComfyWorkflow, tag: string | null = null): SerializedPrompt {
|
graphToPrompt(workflow: ComfyBoxWorkflow, tag: string | null = null): SerializedPrompt {
|
||||||
return this.promptSerializer.serialize(workflow.graph, tag)
|
return this.promptSerializer.serialize(workflow.graph, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,7 +909,7 @@ export default class ComfyApp {
|
|||||||
tag = null;
|
tag = null;
|
||||||
|
|
||||||
this.processingQueue = true;
|
this.processingQueue = true;
|
||||||
let workflow: ComfyWorkflow;
|
let workflow: ComfyBoxWorkflow;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (this.queueItems.length) {
|
while (this.queueItems.length) {
|
||||||
@@ -1001,6 +1057,45 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
|
} else if (file.type === "image/svg+xml" || file.name.endsWith(".svg")) {
|
||||||
|
const templateAndSvg = await deserializeTemplateFromSVG(file);
|
||||||
|
|
||||||
|
const importTemplate = () => {
|
||||||
|
try {
|
||||||
|
if (templateState.add(templateAndSvg)) {
|
||||||
|
notify("Template imported successfully!", { type: "success" })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notify("Template already exists in saved list.", { type: "warning" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
notify(`Error importing template: ${error}`, { type: "error", timeout: 10000 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.pushModal({
|
||||||
|
title: "ComfyBox Template Preview",
|
||||||
|
svelteComponent: EditTemplateModal,
|
||||||
|
closeOnClick: false,
|
||||||
|
showCloseButton: false,
|
||||||
|
svelteProps: {
|
||||||
|
templateAndSvg,
|
||||||
|
editable: false
|
||||||
|
},
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
name: "Import",
|
||||||
|
variant: "primary",
|
||||||
|
onClick: importTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Close",
|
||||||
|
variant: "secondary",
|
||||||
|
onClick: () => { }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,7 +1112,7 @@ export default class ComfyApp {
|
|||||||
/**
|
/**
|
||||||
* Refresh combo list on whole nodes
|
* Refresh combo list on whole nodes
|
||||||
*/
|
*/
|
||||||
async refreshComboInNodes(workflow?: ComfyWorkflow, defs?: Record<string, ComfyNodeDef>, flashUI: boolean = false) {
|
async refreshComboInNodes(workflow?: ComfyBoxWorkflow, defs?: Record<string, ComfyNodeDef>, flashUI: boolean = false) {
|
||||||
workflow ||= workflowState.getActiveWorkflow();
|
workflow ||= workflowState.getActiveWorkflow();
|
||||||
if (workflow == null) {
|
if (workflow == null) {
|
||||||
notify("No active workflow!", { type: "error" })
|
notify("No active workflow!", { type: "error" })
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
import Menu from './menu/Menu.svelte';
|
import Menu from './menu/Menu.svelte';
|
||||||
import MenuOption from './menu/MenuOption.svelte';
|
import MenuOption from './menu/MenuOption.svelte';
|
||||||
import MenuDivider from './menu/MenuDivider.svelte';
|
import MenuDivider from './menu/MenuDivider.svelte';
|
||||||
import type { ComfyWorkflow } from "$lib/stores/workflowState";
|
import type { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
export let workflow: ComfyWorkflow;
|
export let workflow: ComfyBoxWorkflow | null = null;
|
||||||
|
|
||||||
let layoutState: WritableLayoutStateStore | null;
|
let layoutState: WritableLayoutStateStore | null;
|
||||||
|
|
||||||
@@ -111,9 +111,9 @@
|
|||||||
let menuPos = { x: 0, y: 0 };
|
let menuPos = { x: 0, y: 0 };
|
||||||
let showMenu = false;
|
let showMenu = false;
|
||||||
|
|
||||||
$: $layoutState.isMenuOpen = showMenu;
|
$: if (layoutState) $layoutState.isMenuOpen = showMenu;
|
||||||
|
|
||||||
$: if ($layoutState.root) {
|
$: if (layoutState && $layoutState.root) {
|
||||||
root = $layoutState.root
|
root = $layoutState.root
|
||||||
} else {
|
} else {
|
||||||
root = null;
|
root = null;
|
||||||
@@ -138,51 +138,70 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if layoutState != null}
|
{#if workflow != null}
|
||||||
<div id="comfy-workflow-view" on:contextmenu={onRightClick}>
|
{#if layoutState != null}
|
||||||
<WidgetContainer bind:dragItem={root} classes={["root-container"]} {layoutState} />
|
<div class="comfy-workflow-view" on:contextmenu={onRightClick}>
|
||||||
|
<WidgetContainer bind:dragItem={root} classes={["root-container"]} {layoutState} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showMenu}
|
||||||
|
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length !== 1}
|
||||||
|
on:click={() => moveUp()}
|
||||||
|
text="Move Up" />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length !== 1}
|
||||||
|
on:click={() => moveDown()}
|
||||||
|
text="Move Down" />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length !== 1}
|
||||||
|
on:click={() => sendToTop()}
|
||||||
|
text="Send to Top" />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length !== 1}
|
||||||
|
on:click={() => sendToBottom()}
|
||||||
|
text="Send to Bottom" />
|
||||||
|
<MenuDivider/>
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length === 0}
|
||||||
|
on:click={() => groupWidgets(false)}
|
||||||
|
text="Group" />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={$selectionState.currentSelection.length === 0}
|
||||||
|
on:click={() => groupWidgets(true)}
|
||||||
|
text="Group Horizontally" />
|
||||||
|
<MenuOption
|
||||||
|
isDisabled={!canUngroup}
|
||||||
|
on:click={ungroup}
|
||||||
|
text={isDeleteGroup ? "Delete Group" : "Ungroup"} />
|
||||||
|
</Menu>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="no-workflows">
|
||||||
|
<span>No workflow loaded</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showMenu}
|
|
||||||
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length !== 1}
|
|
||||||
on:click={() => moveUp()}
|
|
||||||
text="Move Up" />
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length !== 1}
|
|
||||||
on:click={() => moveDown()}
|
|
||||||
text="Move Down" />
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length !== 1}
|
|
||||||
on:click={() => sendToTop()}
|
|
||||||
text="Send to Top" />
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length !== 1}
|
|
||||||
on:click={() => sendToBottom()}
|
|
||||||
text="Send to Bottom" />
|
|
||||||
<MenuDivider/>
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length === 0}
|
|
||||||
on:click={() => groupWidgets(false)}
|
|
||||||
text="Group" />
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={$selectionState.currentSelection.length === 0}
|
|
||||||
on:click={() => groupWidgets(true)}
|
|
||||||
text="Group Horizontally" />
|
|
||||||
<MenuOption
|
|
||||||
isDisabled={!canUngroup}
|
|
||||||
on:click={ungroup}
|
|
||||||
text={isDeleteGroup ? "Delete Group" : "Ungroup"} />
|
|
||||||
</Menu>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#comfy-workflow-view {
|
.comfy-workflow-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-workflows {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
import type ComfyApp from "./ComfyApp";
|
import type ComfyApp from "./ComfyApp";
|
||||||
import DropZone from "./DropZone.svelte";
|
import DropZone from "./DropZone.svelte";
|
||||||
|
import interfaceState from "$lib/stores/interfaceState";
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
export let transitioning: boolean = false;
|
|
||||||
|
|
||||||
function doRecenter(): void {
|
function doRecenter(): void {
|
||||||
app?.lCanvas?.recenter();
|
app?.lCanvas?.recenter();
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<DropZone {app} />
|
<DropZone {app} />
|
||||||
</div>
|
</div>
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
{#if !transitioning}
|
{#if !$interfaceState.graphTransitioning}
|
||||||
<span class="left">
|
<span class="left">
|
||||||
<button on:click={doRecenter}>Recenter</button>
|
<button on:click={doRecenter}>Recenter</button>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
108
src/lib/components/ComfyPaneView.svelte
Normal file
108
src/lib/components/ComfyPaneView.svelte
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
export type ComfyPaneMode = "none" | "activeWorkflow" | "graph" | "properties" | "templates" | "queue"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* A panel/sidebar that can be switched between different modes.
|
||||||
|
*/
|
||||||
|
import workflowState from "$lib/stores/workflowState";
|
||||||
|
import type ComfyApp from "./ComfyApp";
|
||||||
|
import { Sliders2, BoxSeam, LayoutTextSidebarReverse } from "svelte-bootstrap-icons";
|
||||||
|
|
||||||
|
import ComfyBoxWorkflowView from "./ComfyBoxWorkflowView.svelte";
|
||||||
|
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||||
|
import ComfyProperties from "./ComfyProperties.svelte";
|
||||||
|
import ComfyQueue from "./ComfyQueue.svelte";
|
||||||
|
import ComfyTemplates from "./ComfyTemplates.svelte";
|
||||||
|
import { SvelteComponent } from "svelte";
|
||||||
|
|
||||||
|
export let app: ComfyApp
|
||||||
|
export let mode: ComfyPaneMode = "none";
|
||||||
|
export let showSwitcher: boolean = false;
|
||||||
|
|
||||||
|
const MODES: [ComfyPaneMode, typeof SvelteComponent][] = [
|
||||||
|
["properties", Sliders2],
|
||||||
|
["templates", BoxSeam],
|
||||||
|
["queue", LayoutTextSidebarReverse]
|
||||||
|
]
|
||||||
|
|
||||||
|
function switchMode(newMode: ComfyPaneMode) {
|
||||||
|
console.warn("switch", mode, newMode)
|
||||||
|
mode = newMode;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pane">
|
||||||
|
<div class="pane-wrapper" class:has-switcher={showSwitcher}>
|
||||||
|
{#if mode === "activeWorkflow"}
|
||||||
|
<ComfyBoxWorkflowView {app} workflow={$workflowState.activeWorkflow} />
|
||||||
|
{:else if mode === "graph"}
|
||||||
|
<ComfyGraphView {app} />
|
||||||
|
{:else if mode === "properties"}
|
||||||
|
<ComfyProperties workflow={$workflowState.activeWorkflow} />
|
||||||
|
{:else if mode === "templates"}
|
||||||
|
<ComfyTemplates {app} />
|
||||||
|
{:else if mode === "queue"}
|
||||||
|
<ComfyQueue {app} />
|
||||||
|
{:else}
|
||||||
|
<div class="blank-panel">(Blank)</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if showSwitcher}
|
||||||
|
<div class="switcher">
|
||||||
|
{#each MODES as [theMode, icon]}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<button class="mode-button ternary"
|
||||||
|
disabled={mode === theMode}
|
||||||
|
class:selected={mode === theMode}
|
||||||
|
on:click={() => switchMode(theMode)}>
|
||||||
|
<svelte:component this={icon} width="100%" height="100%" />
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$button-height: 2.5rem;
|
||||||
|
|
||||||
|
.pane {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.pane-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
&.has-switcher {
|
||||||
|
height: calc(100% - $button-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher {
|
||||||
|
height: $button-height;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
color: var(--comfy-accent-soft);
|
||||||
|
|
||||||
|
.mode-button {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
@include square-button;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
background-color: var(--panel-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
||||||
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
||||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||||
import type { ComfyWorkflow } from "$lib/stores/workflowState";
|
import type { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
|
|
||||||
export let workflow: ComfyWorkflow | null;
|
export let workflow: ComfyBoxWorkflow | null;
|
||||||
|
|
||||||
let layoutState: WritableLayoutStateStore | null = null
|
let layoutState: WritableLayoutStateStore | null = null
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@
|
|||||||
await tick(); // Wait for list size to be recalculated
|
await tick(); // Wait for list size to be recalculated
|
||||||
queueList.scroll({ top: queueList.scrollHeight })
|
queueList.scroll({ top: queueList.scrollHeight })
|
||||||
}
|
}
|
||||||
console.warn("[ComfyQueue] BUILDQUEUE", _entries, $queuePending, $queueRunning)
|
console.warn("[ComfyQueue] BUILDQUEUE", _entries.length, $queuePending.length, $queueRunning.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateFromHistory() {
|
async function updateFromHistory() {
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
if (queueList) {
|
if (queueList) {
|
||||||
queueList.scrollTo(0, 0);
|
queueList.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted)
|
console.warn("[ComfyQueue] BUILDHISTORY", _entries.length, $queueCompleted.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function interrupt() {
|
async function interrupt() {
|
||||||
@@ -354,10 +354,11 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
$pending-height: 200px;
|
$pending-height: 200px;
|
||||||
$display-mode-buttons-height: 2rem;
|
$display-mode-buttons-height: 2rem;
|
||||||
|
$pane-mode-buttons-height: 2.5rem;
|
||||||
$bottom-bar-height: 70px;
|
$bottom-bar-height: 70px;
|
||||||
$workflow-tabs-height: 2.5rem;
|
$workflow-tabs-height: 2.5rem;
|
||||||
$mode-buttons-height: 30px;
|
$mode-buttons-height: 30px;
|
||||||
$queue-height: calc(100vh - #{$pending-height} - #{$mode-buttons-height} - #{$bottom-bar-height} - #{$workflow-tabs-height} - 0.9rem);
|
$queue-height: calc(100vh - #{$pending-height} - #{$pane-mode-buttons-height} - #{$mode-buttons-height} - #{$bottom-bar-height} - #{$workflow-tabs-height} - 0.9rem);
|
||||||
$queue-height-history: calc(#{$queue-height} - #{$display-mode-buttons-height});
|
$queue-height-history: calc(#{$queue-height} - #{$display-mode-buttons-height});
|
||||||
|
|
||||||
.prompt-modal-header {
|
.prompt-modal-header {
|
||||||
|
|||||||
280
src/lib/components/ComfyTemplates.svelte
Normal file
280
src/lib/components/ComfyTemplates.svelte
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { embedTemplateInSvg, type SerializedComfyBoxTemplate } from "$lib/ComfyBoxTemplate";
|
||||||
|
import templateState from "$lib/stores/templateState";
|
||||||
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import { download, truncateString } from "$lib/utils";
|
||||||
|
import type ComfyApp from "./ComfyApp";
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import {fade} from 'svelte/transition';
|
||||||
|
import {cubicIn} from 'svelte/easing';
|
||||||
|
import { dndzone, TRIGGERS, SHADOW_PLACEHOLDER_ITEM_ID, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
|
||||||
|
import { defaultWidgetAttributes, type TemplateLayout } from "$lib/stores/layoutStates";
|
||||||
|
import { v4 as uuidv4 } from "uuid"
|
||||||
|
import { get, writable } from "svelte/store";
|
||||||
|
import EditTemplateModal from "./modal/EditTemplateModal.svelte";
|
||||||
|
import modalState, { type ModalData } from "$lib/stores/modalState";
|
||||||
|
import notify from "$lib/notify";
|
||||||
|
|
||||||
|
export let app: ComfyApp
|
||||||
|
|
||||||
|
type DNDConsiderOrFinalizeEvent<T> = {
|
||||||
|
items: T[],
|
||||||
|
info: any,
|
||||||
|
el: Node,
|
||||||
|
id: string,
|
||||||
|
trigger?: string,
|
||||||
|
source?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let _sorted: TemplateLayout[] = []
|
||||||
|
|
||||||
|
$: rebuildTemplates($templateState.templates);
|
||||||
|
|
||||||
|
function rebuildTemplates(templates: SerializedComfyBoxTemplate[]) {
|
||||||
|
_sorted = Array.from(templates).map(t => {
|
||||||
|
return {
|
||||||
|
type: "template", id: uuidv4(), template: t, attrs: {...defaultWidgetAttributes}, attrsChanged: writable(0)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_sorted.sort(t => t.template.metadata.createdAt || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flipDurationMs = 200;
|
||||||
|
let shouldIgnoreDndEvents = false;
|
||||||
|
|
||||||
|
function handleDndConsider(e: CustomEvent<DNDConsiderOrFinalizeEvent<TemplateLayout>>) {
|
||||||
|
// console.warn(`got consider ${JSON.stringify(e.detail, null, 2)}`);
|
||||||
|
const {trigger, id} = e.detail.info;
|
||||||
|
if (trigger === TRIGGERS.DRAG_STARTED) {
|
||||||
|
// console.warn(`copying ${id}`);
|
||||||
|
const idx = _sorted.findIndex(item => item.id === id);
|
||||||
|
const newId = `${id}_copy_${Math.round(Math.random()*1000000)}`;
|
||||||
|
|
||||||
|
e.detail.items = e.detail.items.filter(item => !item[SHADOW_ITEM_MARKER_PROPERTY_NAME]);
|
||||||
|
e.detail.items.splice(idx, 0, {..._sorted[idx], id: newId});
|
||||||
|
|
||||||
|
_sorted = e.detail.items;
|
||||||
|
shouldIgnoreDndEvents = true;
|
||||||
|
}
|
||||||
|
else if (!shouldIgnoreDndEvents) {
|
||||||
|
_sorted = e.detail.items;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_sorted = _sorted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDndFinalize(e: CustomEvent<DNDConsiderOrFinalizeEvent<TemplateLayout>>) {
|
||||||
|
if (!shouldIgnoreDndEvents) {
|
||||||
|
_sorted = e.detail.items;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_sorted = _sorted;
|
||||||
|
shouldIgnoreDndEvents = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(layout: TemplateLayout) {
|
||||||
|
if ($uiState.uiUnlocked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const updateTemplate = (modal: ModalData) => {
|
||||||
|
const state = get(modal.state);
|
||||||
|
layout.template.metadata.title = state.name || layout.template.metadata.title
|
||||||
|
layout.template.metadata.author = state.author || layout.template.metadata.author
|
||||||
|
layout.template.metadata.description = state.description || layout.template.metadata.description
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveTemplate = (modal: ModalData) => {
|
||||||
|
updateTemplate(modal);
|
||||||
|
try {
|
||||||
|
templateState.update(layout.template);
|
||||||
|
notify("Saved template!", { type: "success" })
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
notify(`Failed to save template: ${error}`, { type: "error", timeout: 10000 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteTemplate = (modal: ModalData) => {
|
||||||
|
if (!confirm("Are you sure you want to delete this template?"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (templateState.remove(layout.template.id)) {
|
||||||
|
notify("Template deleted!", { type: "success" })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notify("Failed to delete template: not saved to local storage.", { type: "warning" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
notify(`Failed to delete template: ${error}`, { type: "error", timeout: 10000 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadTemplate = (modal: ModalData) => {
|
||||||
|
updateTemplate(modal);
|
||||||
|
const svg = embedTemplateInSvg(layout.template);
|
||||||
|
const title = layout.template.metadata.title || "template";
|
||||||
|
download(`${title}.svg`, svg, "image/svg+xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.pushModal({
|
||||||
|
svelteComponent: EditTemplateModal,
|
||||||
|
svelteProps: {
|
||||||
|
templateAndSvg: layout.template
|
||||||
|
},
|
||||||
|
showCloseButton: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
name: "Save",
|
||||||
|
variant: "primary",
|
||||||
|
onClick: saveTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Download",
|
||||||
|
variant: "secondary",
|
||||||
|
onClick: downloadTemplate,
|
||||||
|
closeOnClick: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete",
|
||||||
|
variant: "secondary",
|
||||||
|
onClick: deleteTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Close",
|
||||||
|
variant: "secondary",
|
||||||
|
onClick: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="template-list">
|
||||||
|
<div class="template-entries">
|
||||||
|
{#if _sorted.length > 0}
|
||||||
|
{@const draggable = $uiState.uiUnlocked}
|
||||||
|
<div class="template-category-group">
|
||||||
|
<div class="template-category-header">
|
||||||
|
General
|
||||||
|
</div>
|
||||||
|
<div class="template-entries-wrapper"
|
||||||
|
use:dndzone={{
|
||||||
|
type: "layout",
|
||||||
|
items: _sorted,
|
||||||
|
flipDurationMs,
|
||||||
|
dragDisabled: !draggable,
|
||||||
|
dropFromOthersDisabled: true
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsider}
|
||||||
|
on:finalize={handleDndFinalize}>
|
||||||
|
{#each _sorted.filter(i => i.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="template-entry" class:draggable on:click={() => handleClick(item)}>
|
||||||
|
<div class="template-name">{item.template.metadata.title}</div>
|
||||||
|
<div class="template-desc">{item.template.metadata.description}</div>
|
||||||
|
</div>
|
||||||
|
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||||
|
<div in:fade={{duration:200, easing: cubicIn}} class='template-drag-item-shadow'/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="no-templates">
|
||||||
|
<span>(No templates)</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.template-list {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-entries {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-category-header {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
background: var(--block-background-fill);
|
||||||
|
border-color: var(--panel-border-color);
|
||||||
|
padding: 0.8rem 1.0rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-entry {
|
||||||
|
padding: 1.0rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-bottom: 1px solid var(--block-border-color);
|
||||||
|
border-top: 1px solid var(--table-border-color);
|
||||||
|
color: var(--body-text-color);
|
||||||
|
background: var(--panel-background-fill);
|
||||||
|
max-height: 14rem;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
font-size: 13pt;
|
||||||
|
.template-desc {
|
||||||
|
opacity: 65%;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.draggable {
|
||||||
|
border: 5px dashed var(--secondary-500);
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:has(img:hover)):not(:has(button:hover)) {
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--block-background-fill);
|
||||||
|
|
||||||
|
&.draggable {
|
||||||
|
cursor: grab;
|
||||||
|
background: var(--secondary-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
background: var(--comfy-accent-soft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-templates {
|
||||||
|
display: flex;
|
||||||
|
color: var(--comfy-accent-soft);
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--comfy-accent-soft);
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-drag-item-shadow {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left:0; right: 0; bottom: 0;
|
||||||
|
visibility: visible;
|
||||||
|
border: 1px dashed grey;
|
||||||
|
background: lightblue;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onButtonClicked(modal: ModalData, button: ModalButton, closeDialog: Function) {
|
function onButtonClicked(modal: ModalData, button: ModalButton, closeDialog: Function) {
|
||||||
button.onClick(modal);
|
if (button.onClick(modal) === false)
|
||||||
|
return
|
||||||
|
|
||||||
if (button.closeOnClick !== false) {
|
if (button.closeOnClick !== false) {
|
||||||
closeDialog()
|
closeDialog()
|
||||||
@@ -29,9 +30,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment>
|
<svelte:fragment>
|
||||||
{#if modal != null && modal.svelteComponent != null}
|
<div class="modal-body">
|
||||||
<svelte:component this={modal.svelteComponent} {...modal.svelteProps} _modal={modal}/>
|
{#if modal != null && modal.svelteComponent != null}
|
||||||
{/if}
|
<svelte:component this={modal.svelteComponent} {...modal.svelteProps} _modal={modal}/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<div slot="buttons" class="buttons" let:closeDialog>
|
<div slot="buttons" class="buttons" let:closeDialog>
|
||||||
{#if modal != null && modal.buttons?.length > 0}
|
{#if modal != null && modal.buttons?.length > 0}
|
||||||
@@ -52,6 +55,12 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.buttons {
|
.buttons {
|
||||||
gap: var(--spacing-sm);
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
||||||
import type { ComfyImageLocation } from "$lib/nodes/ComfyWidgetNodes";
|
import type { ComfyImageLocation } from "$lib/nodes/ComfyWidgetNodes";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
|
import configState from "$lib/stores/configState";
|
||||||
import { convertComfyOutputEntryToGradio, convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
import { convertComfyOutputEntryToGradio, convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||||
import { Block, BlockLabel } from "@gradio/atoms";
|
import { Block, BlockLabel } from "@gradio/atoms";
|
||||||
import { File as FileIcon } from "@gradio/icons";
|
import { File as FileIcon } from "@gradio/icons";
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
|
|
||||||
dispatch("uploading")
|
dispatch("uploading")
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const url = configState.getBackendURL();
|
||||||
|
|
||||||
const requests = files.map(async (file) => {
|
const requests = files.map(async (file) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -174,8 +175,6 @@
|
|||||||
uploaded = true;
|
uploaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: console.warn(imgWidth, imgHeight, "IMGSIZE!!")
|
|
||||||
|
|
||||||
function handle_clear(_e: CustomEvent<null>) {
|
function handle_clear(_e: CustomEvent<null>) {
|
||||||
_value = null;
|
_value = null;
|
||||||
value = [];
|
value = [];
|
||||||
|
|||||||
@@ -94,9 +94,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
}
|
gap: var(--spacing-md);
|
||||||
|
|
||||||
.button-row, .buttons {
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import type { Styles } from "@gradio/utils";
|
import type { Styles } from "@gradio/utils";
|
||||||
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
|
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
|
||||||
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
||||||
import workflowState, { type ComfyWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||||
import type ComfyApp from "./ComfyApp";
|
import type ComfyApp from "./ComfyApp";
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
// ImageViewer.instance.showLightbox(e.detail)
|
// ImageViewer.instance.showLightbox(e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
function sendOutput(workflow: ComfyBoxWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||||
if (workflow == null || targetNode == null)
|
if (workflow == null || targetNode == null)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { isHidden } from "$lib/widgets/utils";
|
import { isHidden } from "$lib/widgets/utils";
|
||||||
|
import { handleContainerConsider, handleContainerFinalize } from "./utils";
|
||||||
|
|
||||||
export let layoutState: WritableLayoutStateStore;
|
export let layoutState: WritableLayoutStateStore;
|
||||||
export let container: ContainerLayout | null = null;
|
export let container: ContainerLayout | null = null;
|
||||||
@@ -38,14 +39,12 @@
|
|||||||
// attrsChanged = writable(0)
|
// attrsChanged = writable(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerConsider(layoutState, container, evt)
|
||||||
// console.log(dragItems);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleFinalize(evt: any) {
|
function handleFinalize(evt: CustomEvent<DndEvent<IDragItem>>) {
|
||||||
children = layoutState.updateChildren(container, evt.detail.items)
|
children = handleContainerFinalize(layoutState, container, evt)
|
||||||
// Ensure dragging is stopped on drag finish
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTabName(container: ContainerLayout, i: number): string {
|
function getTabName(container: ContainerLayout, i: number): string {
|
||||||
@@ -89,6 +88,7 @@
|
|||||||
class:empty={children.length === 0}
|
class:empty={children.length === 0}
|
||||||
class:edit={edit}
|
class:edit={edit}
|
||||||
use:dndzone="{{
|
use:dndzone="{{
|
||||||
|
type: "layout",
|
||||||
items: children,
|
items: children,
|
||||||
flipDurationMs,
|
flipDurationMs,
|
||||||
centreDraggedOnCursor: true,
|
centreDraggedOnCursor: true,
|
||||||
@@ -222,8 +222,7 @@
|
|||||||
|
|
||||||
.animation-wrapper {
|
.animation-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 100;
|
flex: 1 100 0%;
|
||||||
flex-basis: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-widget:hover {
|
.handle-widget:hover {
|
||||||
|
|||||||
@@ -1,35 +1,73 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { setContext, createEventDispatcher } from 'svelte';
|
import { setContext, createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { key } from './menu.ts';
|
||||||
import { key } from './menu.ts';
|
|
||||||
|
|
||||||
export let x;
|
import { offset, flip, shift } from "svelte-floating-ui/dom";
|
||||||
export let y;
|
import { createFloatingActions, type ClientRectObject, type VirtualElement } from "svelte-floating-ui";
|
||||||
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
// whenever x and y is changed, restrict box to be within bounds
|
const [floatingRef, floatingContent] = createFloatingActions({
|
||||||
$: (() => {
|
placement: "right-start",
|
||||||
if (!menuEl) return;
|
strategy: "fixed",
|
||||||
|
middleware: [
|
||||||
|
offset({ mainAxis: 5, alignmentAxis: 4 }),
|
||||||
|
flip({
|
||||||
|
fallbackPlacements: ["left-start"]
|
||||||
|
}),
|
||||||
|
shift({ padding: 10 })
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const rect = menuEl.getBoundingClientRect();
|
|
||||||
x = Math.min(window.innerWidth - rect.width, x);
|
|
||||||
if (y > window.innerHeight - rect.height) y -= rect.height;
|
|
||||||
})(x, y);
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
export let x;
|
||||||
|
export let y;
|
||||||
|
|
||||||
setContext(key, {
|
// whenever x and y is changed, restrict box to be within bounds
|
||||||
dispatchClick: () => dispatch('click')
|
$: (() => {
|
||||||
});
|
if (!menuEl) return;
|
||||||
|
|
||||||
let menuEl;
|
const rect = menuEl.getBoundingClientRect();
|
||||||
function onPageClick(e) {
|
x = Math.min(window.innerWidth - rect.width, x);
|
||||||
if (e.target === menuEl || menuEl.contains(e.target)) return;
|
if (y > window.innerHeight - rect.height) y -= rect.height;
|
||||||
dispatch('clickoutside');
|
})();
|
||||||
}
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
setContext(key, {
|
||||||
|
dispatchClick: () => dispatch('click')
|
||||||
|
});
|
||||||
|
|
||||||
|
let menuEl;
|
||||||
|
function onPageClick(e) {
|
||||||
|
if (e.target === menuEl || menuEl.contains(e.target)) return;
|
||||||
|
dispatch('clickoutside');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let getBoundingClientRect: () => ClientRectObject;
|
||||||
|
|
||||||
|
$: getBoundingClientRect = (): ClientRectObject => {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
top: y,
|
||||||
|
left: x,
|
||||||
|
bottom: y,
|
||||||
|
right: x,
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const virtualElement: Writable<VirtualElement> = writable({ getBoundingClientRect })
|
||||||
|
|
||||||
|
$: virtualElement.set({ getBoundingClientRect })
|
||||||
|
|
||||||
|
floatingRef(virtualElement)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:body on:click={onPageClick} />
|
<svelte:body on:click={onPageClick} />
|
||||||
<div class="menu" bind:this={menuEl} style="top: {y}px; left: {x}px;">
|
<div class="menu" bind:this={menuEl} style="top: {y}px; left: {x}px;" use:floatingContent>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
129
src/lib/components/modal/EditTemplateModal.svelte
Normal file
129
src/lib/components/modal/EditTemplateModal.svelte
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ComfyBoxTemplate, SerializedComfyBoxTemplate } from "$lib/ComfyBoxTemplate";
|
||||||
|
import type { SerializedDragEntry, SerializedLayoutState } from "$lib/stores/layoutStates";
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
import SerializedLayoutPreviewNode from "./SerializedLayoutPreviewNode.svelte";
|
||||||
|
import Row from "../gradio/app/Row.svelte";
|
||||||
|
import createDOMPurify from "dompurify"
|
||||||
|
import Column from "../gradio/app/Column.svelte";
|
||||||
|
import Accordion from "../gradio/app/Accordion.svelte";
|
||||||
|
import Textbox from "@gradio/form/src/Textbox.svelte";
|
||||||
|
import type { ModalData } from "$lib/stores/modalState";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
const DOMPurify = createDOMPurify(window);
|
||||||
|
|
||||||
|
export let templateAndSvg: SerializedComfyBoxTemplate;
|
||||||
|
export let editable: boolean = true;
|
||||||
|
export let _modal: ModalData;
|
||||||
|
let layout: SerializedLayoutState | null
|
||||||
|
let root: SerializedDragEntry | null
|
||||||
|
let state: Writable<any> = writable({})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
state = _modal.state;
|
||||||
|
if (!("name" in $state)) {
|
||||||
|
$state.name = templateAndSvg.metadata.title;
|
||||||
|
$state.author = templateAndSvg.metadata.author;
|
||||||
|
$state.description = templateAndSvg.metadata.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let saneSvg: string = "";
|
||||||
|
|
||||||
|
$: saneSvg = templateAndSvg
|
||||||
|
? DOMPurify.sanitize(templateAndSvg.svg, { USE_PROFILES: { svg: true, svgFilters: true } })
|
||||||
|
.replace("<svg", "<svg style='background: url(\"image/graph-bg.png\")'")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
$: if (templateAndSvg) {
|
||||||
|
layout = templateAndSvg.layout;
|
||||||
|
if (layout) {
|
||||||
|
root = layout.allItems[layout.root];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
root = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
layout = null;
|
||||||
|
root = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="template-preview">
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<div class="template-metadata">
|
||||||
|
<Block>
|
||||||
|
<BlockTitle>Metadata</BlockTitle>
|
||||||
|
<div>
|
||||||
|
<Textbox label="Name" disabled={!editable} bind:value={$state.name} lines={1} max_lines={1} />
|
||||||
|
<Textbox label="Author" disabled={!editable} bind:value={$state.author} lines={1} max_lines={1} />
|
||||||
|
<Textbox label="Description" disabled={!editable} bind:value={$state.description} lines={5} max_lines={5} />
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
{#if root}
|
||||||
|
<Column>
|
||||||
|
<div class="template-layout-preview">
|
||||||
|
<Block>
|
||||||
|
<BlockTitle>Layout</BlockTitle>
|
||||||
|
<SerializedLayoutPreviewNode {layout} entry={root} entryID={root.dragItem.id} />
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
{/if}
|
||||||
|
</Row>
|
||||||
|
<div class="template-graph-preview">
|
||||||
|
<Block>
|
||||||
|
<Accordion label="Graph">
|
||||||
|
<Block>
|
||||||
|
<div class="template-graph-wrapper">
|
||||||
|
{@html saneSvg}
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
|
</Accordion>
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.template-preview {
|
||||||
|
width: 70vw;
|
||||||
|
height: 70vh;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: var(--layout-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-metadata {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--panel-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-layout-preview {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--panel-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-graph-preview {
|
||||||
|
min-width: 0;
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--panel-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-graph-wrapper {
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
import type { ComfyBoxWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
select: { workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode };
|
select: { workflow: ComfyBoxWorkflow, targetNode: ComfyReceiveOutputNode };
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||||
|
|
||||||
function onSelected( workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode ) {
|
function onSelected( workflow: ComfyBoxWorkflow, targetNode: ComfyReceiveOutputNode ) {
|
||||||
dispatch("select", {
|
dispatch("select", {
|
||||||
workflow,
|
workflow,
|
||||||
targetNode
|
targetNode
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type SendOutputModalResult = {
|
export type SendOutputModalResult = {
|
||||||
workflow?: ComfyWorkflow,
|
workflow?: ComfyBoxWorkflow,
|
||||||
targetNode?: ComfyReceiveOutputNode,
|
targetNode?: ComfyReceiveOutputNode,
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
import type { SlotType } from "@litegraph-ts/core";
|
import type { SlotType } from "@litegraph-ts/core";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { StaticImage } from "$lib/components/gradio/image";
|
import { StaticImage } from "$lib/components/gradio/image";
|
||||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
import type { ComfyBoxWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||||
import { comfyBoxImageToComfyURL } from "$lib/utils";
|
import { comfyBoxImageToComfyURL } from "$lib/utils";
|
||||||
import { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
images = [comfyBoxImageToComfyURL(value)];
|
images = [comfyBoxImageToComfyURL(value)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
function sendOutput(workflow: ComfyBoxWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||||
const result: SendOutputModalResult = {
|
const result: SendOutputModalResult = {
|
||||||
workflow,
|
workflow,
|
||||||
targetNode
|
targetNode
|
||||||
|
|||||||
48
src/lib/components/modal/SerializedLayoutPreviewNode.svelte
Normal file
48
src/lib/components/modal/SerializedLayoutPreviewNode.svelte
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { type DragItemID, type SerializedDragEntry, type SerializedLayoutState } from "$lib/stores/layoutStates";
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
import Accordion from "../gradio/app/Accordion.svelte";
|
||||||
|
|
||||||
|
export let layout: SerializedLayoutState
|
||||||
|
export let entryID: DragItemID
|
||||||
|
export let entry: SerializedDragEntry
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if entry}
|
||||||
|
{#if entry.dragItem.type === "container"}
|
||||||
|
<div class="layout-container">
|
||||||
|
<Block>
|
||||||
|
<Accordion label={entry.dragItem.attrs.title || "(Container)"} open={false}>
|
||||||
|
{#each entry.children as childID}
|
||||||
|
{@const child = layout.allItems[childID]}
|
||||||
|
<svelte:self {layout} entry={child} entryID={childID} />
|
||||||
|
{/each}
|
||||||
|
</Accordion>
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="layout-widget">
|
||||||
|
<Block>
|
||||||
|
<BlockTitle>{entry.dragItem.attrs.title}</BlockTitle>
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<Block>
|
||||||
|
Missing drag entry! {entryID}
|
||||||
|
</Block>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.layout-container {
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--panel-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-widget {
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--block-background-fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
src/lib/components/utils.ts
Normal file
47
src/lib/components/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import type ComfyGraphCanvas from "$lib/ComfyGraphCanvas";
|
||||||
|
import { type ContainerLayout, type IDragItem, type TemplateLayout, type WritableLayoutStateStore } from "$lib/stores/layoutStates"
|
||||||
|
import type { LGraphCanvas, Vector2 } from "@litegraph-ts/core";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
export function handleContainerConsider(layoutState: WritableLayoutStateStore, container: ContainerLayout, evt: CustomEvent<DndEvent<IDragItem>>): IDragItem[] {
|
||||||
|
return layoutState.updateChildren(container, evt.detail.items)
|
||||||
|
};
|
||||||
|
|
||||||
|
export function handleContainerFinalize(layoutState: WritableLayoutStateStore, container: ContainerLayout, evt: CustomEvent<DndEvent<IDragItem>>): IDragItem[] {
|
||||||
|
const dnd = evt.detail
|
||||||
|
const info = dnd.info;
|
||||||
|
const droppedItem = dnd.items.find(i => i.id === info.id);
|
||||||
|
const isDroppingTemplate = droppedItem?.type === "template"
|
||||||
|
|
||||||
|
if (isDroppingTemplate) {
|
||||||
|
return doInsertTemplate(layoutState, droppedItem as TemplateLayout, container, dnd.items)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return layoutState.updateChildren(container, dnd.items)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function isComfyGraphCanvas(canvas: LGraphCanvas): canvas is ComfyGraphCanvas {
|
||||||
|
return "insertTemplate" in canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doInsertTemplate(layoutState: WritableLayoutStateStore, droppedTemplate: TemplateLayout, container: ContainerLayout, items: IDragItem[]): IDragItem[] {
|
||||||
|
const workflow = layoutState.workflow;
|
||||||
|
const templateItemIndex = items.findIndex(i => i.id === droppedTemplate.id)
|
||||||
|
|
||||||
|
const newChildren = items.filter(i => i.id !== droppedTemplate.id);
|
||||||
|
|
||||||
|
const canvas = workflow.canvases["app"]?.canvas
|
||||||
|
if (canvas == null || !isComfyGraphCanvas(canvas) || canvas.graph !== workflow.graph) {
|
||||||
|
console.error("Couldn't get main graph canvas!")
|
||||||
|
return newChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutState.updateChildren(container, newChildren);
|
||||||
|
|
||||||
|
const newPos: Vector2 = [canvas.visible_area[0] + canvas.visible_area[2] / 2, canvas.visible_area[1] + canvas.visible_area[3] / 2]
|
||||||
|
|
||||||
|
canvas.insertTemplate(droppedTemplate.template, newPos, container, templateItemIndex);
|
||||||
|
|
||||||
|
return get(layoutState).allItems[container.id].children;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { LGraph, type INodeInputSlot, type SerializedLGraph, type LinkID, type UUID, type NodeID, LiteGraph, BuiltInSlotType, type SerializedLGraphNode, type Vector2, BuiltInSlotShape, type INodeOutputSlot, type SlotType } from "@litegraph-ts/core";
|
import { LGraph, type INodeInputSlot, type SerializedLGraph, type LinkID, type UUID, type NodeID, LiteGraph, BuiltInSlotType, type SerializedLGraphNode, type Vector2, BuiltInSlotShape, type INodeOutputSlot, type SlotType } from "@litegraph-ts/core";
|
||||||
import type { SerializedAppState } from "./components/ComfyApp";
|
import type { SerializedAppState } from "./components/ComfyApp";
|
||||||
import layoutStates, { defaultWorkflowAttributes, type ContainerLayout, type DragItemID, type SerializedDragEntry, type SerializedLayoutState, type WritableLayoutStateStore } from "./stores/layoutStates";
|
import layoutStates, { defaultWorkflowAttributes, type ContainerLayout, type DragItemID, type SerializedDragEntry, type SerializedLayoutState, type WritableLayoutStateStore } from "./stores/layoutStates";
|
||||||
import { ComfyWorkflow, type WorkflowAttributes } from "./stores/workflowState";
|
import { ComfyBoxWorkflow, type WorkflowAttributes } from "./stores/workflowState";
|
||||||
import type { SerializedGraphCanvasState } from "./ComfyGraphCanvas";
|
import type { SerializedGraphCanvasState } from "./ComfyGraphCanvas";
|
||||||
import ComfyApp from "./components/ComfyApp";
|
import ComfyApp from "./components/ComfyApp";
|
||||||
import { iterateNodeDefInputs, type ComfyNodeDefInputType, type ComfyNodeDefInputOptions } from "./ComfyNodeDef";
|
import { iterateNodeDefInputs, type ComfyNodeDefInputType, type ComfyNodeDefInputOptions } from "./ComfyNodeDef";
|
||||||
@@ -346,8 +346,8 @@ function removeSerializedNode(vanillaWorkflow: SerializedLGraph, node: Serialize
|
|||||||
* Converts a workflow saved with vanilla ComfyUI into a ComfyBox workflow,
|
* Converts a workflow saved with vanilla ComfyUI into a ComfyBox workflow,
|
||||||
* adding UI nodes for each widget.
|
* adding UI nodes for each widget.
|
||||||
*/
|
*/
|
||||||
export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWorkflow, attrs: WorkflowAttributes): [ComfyWorkflow, WritableLayoutStateStore] {
|
export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWorkflow, attrs: WorkflowAttributes): [ComfyBoxWorkflow, WritableLayoutStateStore] {
|
||||||
const [comfyBoxWorkflow, layoutState] = ComfyWorkflow.create();
|
const [comfyBoxWorkflow, layoutState] = ComfyBoxWorkflow.create();
|
||||||
const { root, left, right } = layoutState.initDefaultLayout();
|
const { root, left, right } = layoutState.initDefaultLayout();
|
||||||
|
|
||||||
// TODO will need to convert IDs to UUIDs
|
// TODO will need to convert IDs to UUIDs
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { get } from "svelte/store";
|
|||||||
import configState from "$lib/stores/configState";
|
import configState from "$lib/stores/configState";
|
||||||
import type { WidgetLayout, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
import type { WidgetLayout, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||||
import layoutStates from "$lib/stores/layoutStates";
|
import layoutStates from "$lib/stores/layoutStates";
|
||||||
import workflowStateStore, { ComfyWorkflow } from "$lib/stores/workflowState";
|
import workflowStateStore, { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
|
|
||||||
export type DefaultWidgetSpec = {
|
export type DefaultWidgetSpec = {
|
||||||
defaultWidgetNode: new (name?: string) => ComfyWidgetNode,
|
defaultWidgetNode: new (name?: string) => ComfyWidgetNode,
|
||||||
@@ -111,7 +111,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
return layoutStates.getDragItemByNode(this);
|
return layoutStates.getDragItemByNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get workflow(): ComfyWorkflow | null {
|
get workflow(): ComfyBoxWorkflow | null {
|
||||||
return workflowStateStore.getWorkflowByNode(this);
|
return workflowStateStore.getWorkflowByNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +312,13 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(o as any).saveUserState = this.saveUserState
|
(o as any).saveUserState = this.saveUserState
|
||||||
if (!this.saveUserState && (!get(uiState).isSavingToLocalStorage || get(configState).alwaysStripUserState)) {
|
|
||||||
|
let saveUserState = this.saveUserState || get(configState).alwaysStripUserState;
|
||||||
|
const forceSaveUserState = get(uiState).forceSaveUserState;
|
||||||
|
if (forceSaveUserState !== null)
|
||||||
|
saveUserState = forceSaveUserState;
|
||||||
|
|
||||||
|
if (!saveUserState) {
|
||||||
this.stripUserState(o)
|
this.stripUserState(o)
|
||||||
console.debug("[ComfyGraphNode] stripUserState", this, o)
|
console.debug("[ComfyGraphNode] stripUserState", this, o)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ export default class ComfyComboNode extends ComfyWidgetNode<string> {
|
|||||||
override stripUserState(o: SerializedLGraphNode) {
|
override stripUserState(o: SerializedLGraphNode) {
|
||||||
super.stripUserState(o);
|
super.stripUserState(o);
|
||||||
o.properties.values = []
|
o.properties.values = []
|
||||||
|
o.properties.defaultValue = null;
|
||||||
|
(o as any).comfyValue = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -297,6 +297,9 @@ export default abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyPropsChanged() {
|
notifyPropsChanged() {
|
||||||
|
if (!this.layoutState)
|
||||||
|
return;
|
||||||
|
|
||||||
const layoutEntry = this.layoutState.findLayoutEntryForNode(this.id as ComfyNodeID)
|
const layoutEntry = this.layoutState.findLayoutEntryForNode(this.id as ComfyNodeID)
|
||||||
if (layoutEntry && layoutEntry.parent) {
|
if (layoutEntry && layoutEntry.parent) {
|
||||||
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
||||||
@@ -352,7 +355,6 @@ export default abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
|
|
||||||
override stripUserState(o: SerializedLGraphNode) {
|
override stripUserState(o: SerializedLGraphNode) {
|
||||||
super.stripUserState(o);
|
super.stripUserState(o);
|
||||||
(o as any).comfyValue = this.defaultValue;
|
(o as any).comfyValue = this.properties.defaultValue;
|
||||||
o.properties.defaultValue = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { get, writable } from 'svelte/store';
|
|||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export type ConfigState = {
|
export type ConfigState = {
|
||||||
|
/** Backend domain for ComfyUI */
|
||||||
|
comfyUIHostname: string,
|
||||||
|
|
||||||
|
/** Backend port for ComfyUI */
|
||||||
|
comfyUIPort: number,
|
||||||
|
|
||||||
/** Strip user state even if saving to local storage */
|
/** Strip user state even if saving to local storage */
|
||||||
alwaysStripUserState: boolean,
|
alwaysStripUserState: boolean,
|
||||||
|
|
||||||
@@ -14,18 +20,27 @@ export type ConfigState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConfigStateOps = {
|
type ConfigStateOps = {
|
||||||
|
getBackendURL: () => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableConfigStateStore = Writable<ConfigState> & ConfigStateOps;
|
export type WritableConfigStateStore = Writable<ConfigState> & ConfigStateOps;
|
||||||
const store: Writable<ConfigState> = writable(
|
const store: Writable<ConfigState> = writable(
|
||||||
{
|
{
|
||||||
|
comfyUIHostname: "localhost",
|
||||||
|
comfyUIPort: 8188,
|
||||||
alwaysStripUserState: false,
|
alwaysStripUserState: false,
|
||||||
promptForWorkflowName: false,
|
promptForWorkflowName: false,
|
||||||
confirmWhenUnloadingUnsavedChanges: true
|
confirmWhenUnloadingUnsavedChanges: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getBackendURL(): string {
|
||||||
|
const state = get(store);
|
||||||
|
return `${window.location.protocol}//${state.comfyUIHostname}:${state.comfyUIPort}`
|
||||||
|
}
|
||||||
|
|
||||||
const configStateStore: WritableConfigStateStore =
|
const configStateStore: WritableConfigStateStore =
|
||||||
{
|
{
|
||||||
...store
|
...store,
|
||||||
|
getBackendURL
|
||||||
}
|
}
|
||||||
export default configStateStore;
|
export default configStateStore;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export type InterfaceState = {
|
|||||||
pointerNearLeft: boolean,
|
pointerNearLeft: boolean,
|
||||||
showIndicator: boolean,
|
showIndicator: boolean,
|
||||||
indicatorValue: any,
|
indicatorValue: any,
|
||||||
|
|
||||||
|
graphTransitioning: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceStateOps = {
|
type InterfaceStateOps = {
|
||||||
@@ -22,6 +24,8 @@ const store: Writable<InterfaceState> = writable(
|
|||||||
pointerNearLeft: false,
|
pointerNearLeft: false,
|
||||||
showIndicator: false,
|
showIndicator: false,
|
||||||
indicatorValue: null,
|
indicatorValue: null,
|
||||||
|
|
||||||
|
graphTransitioning: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)
|
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import type { ComfyNodeID } from '$lib/api';
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import type { ComfyWidgetNode } from '$lib/nodes/widgets';
|
import type { ComfyWidgetNode } from '$lib/nodes/widgets';
|
||||||
import type ComfyGraph from '$lib/ComfyGraph';
|
import type ComfyGraph from '$lib/ComfyGraph';
|
||||||
import type { ComfyWorkflow, WorkflowAttributes, WorkflowInstID } from './workflowState';
|
import type { ComfyBoxWorkflow, WorkflowAttributes, WorkflowInstID } from './workflowState';
|
||||||
import workflowState from './workflowState';
|
import workflowState from './workflowState';
|
||||||
|
import type { SerializedComfyBoxTemplate } from '$lib/ComfyBoxTemplate';
|
||||||
|
|
||||||
export function isComfyWidgetNode(node: LGraphNode): node is ComfyWidgetNode {
|
export function isComfyWidgetNode(node: LGraphNode): node is ComfyWidgetNode {
|
||||||
return "svelteComponentType" in node
|
return "svelteComponentType" in node
|
||||||
@@ -524,7 +525,37 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
canShow: (di: IDragItem) => di.type === "container"
|
canShow: (di: IDragItem) => di.type === "container"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Combo
|
||||||
|
{
|
||||||
|
name: "defaultValue",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: "A",
|
||||||
|
validNodeTypes: ["ui/combo"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
{
|
||||||
|
name: "defaultValue",
|
||||||
|
type: "boolean",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: true,
|
||||||
|
validNodeTypes: ["ui/checkbox"],
|
||||||
|
},
|
||||||
|
|
||||||
// Range
|
// Range
|
||||||
|
{
|
||||||
|
name: "defaultValue",
|
||||||
|
type: "number",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: 0,
|
||||||
|
min: -2 ^ 16,
|
||||||
|
max: 2 ^ 16,
|
||||||
|
validNodeTypes: ["ui/number"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "min",
|
name: "min",
|
||||||
type: "number",
|
type: "number",
|
||||||
@@ -586,6 +617,14 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Radio
|
// Radio
|
||||||
|
{
|
||||||
|
name: "defaultValue",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: "Choice A",
|
||||||
|
validNodeTypes: ["ui/radio"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "choices",
|
name: "choices",
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -597,6 +636,16 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
deserialize: deserializeStringArray,
|
deserialize: deserializeStringArray,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Text
|
||||||
|
{
|
||||||
|
name: "defaultValue",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: "",
|
||||||
|
validNodeTypes: ["ui/text"],
|
||||||
|
},
|
||||||
|
|
||||||
// Workflow
|
// Workflow
|
||||||
{
|
{
|
||||||
name: "title",
|
name: "title",
|
||||||
@@ -635,7 +684,7 @@ for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
|||||||
export { ALL_ATTRIBUTES };
|
export { ALL_ATTRIBUTES };
|
||||||
|
|
||||||
// TODO Should be nested by category for name uniqueness?
|
// TODO Should be nested by category for name uniqueness?
|
||||||
const defaultWidgetAttributes: Attributes = {} as any
|
export const defaultWidgetAttributes: Attributes = {} as any
|
||||||
export const defaultWorkflowAttributes: WorkflowAttributes = {} as any
|
export const defaultWorkflowAttributes: WorkflowAttributes = {} as any
|
||||||
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
||||||
for (const spec of Object.values(cat.specs)) {
|
for (const spec of Object.values(cat.specs)) {
|
||||||
@@ -657,7 +706,7 @@ export interface IDragItem {
|
|||||||
/*
|
/*
|
||||||
* Type of the item.
|
* Type of the item.
|
||||||
*/
|
*/
|
||||||
type: "container" | "widget",
|
type: "container" | "widget" | "template",
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unique ID of the item.
|
* Unique ID of the item.
|
||||||
@@ -707,6 +756,21 @@ export interface WidgetLayout extends IDragItem {
|
|||||||
node: ComfyWidgetNode
|
node: ComfyWidgetNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A template being dragged into the workflow UI.
|
||||||
|
*
|
||||||
|
* These will never be saved, instead they will be expanded inside
|
||||||
|
* updateChildren() and then removed.
|
||||||
|
*/
|
||||||
|
export interface TemplateLayout extends IDragItem {
|
||||||
|
type: "template",
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Template to expand
|
||||||
|
*/
|
||||||
|
template: SerializedComfyBoxTemplate
|
||||||
|
}
|
||||||
|
|
||||||
export type DefaultLayout = {
|
export type DefaultLayout = {
|
||||||
root: ContainerLayout,
|
root: ContainerLayout,
|
||||||
left: ContainerLayout,
|
left: ContainerLayout,
|
||||||
@@ -716,7 +780,7 @@ export type DefaultLayout = {
|
|||||||
export type DragItemID = UUID;
|
export type DragItemID = UUID;
|
||||||
|
|
||||||
type LayoutStateOps = {
|
type LayoutStateOps = {
|
||||||
workflow: ComfyWorkflow | null,
|
workflow: ComfyBoxWorkflow | null,
|
||||||
|
|
||||||
addContainer: (parent: ContainerLayout | null, attrs?: Partial<Attributes>, index?: number) => ContainerLayout,
|
addContainer: (parent: ContainerLayout | null, attrs?: Partial<Attributes>, index?: number) => ContainerLayout,
|
||||||
addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs?: Partial<Attributes>, index?: number) => WidgetLayout,
|
addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs?: Partial<Attributes>, index?: number) => WidgetLayout,
|
||||||
@@ -724,6 +788,7 @@ type LayoutStateOps = {
|
|||||||
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
||||||
nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void,
|
nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void,
|
||||||
nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void,
|
nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void,
|
||||||
|
insertTemplate: (template: SerializedComfyBoxTemplate, graph: LGraph, templateNodeIDToNode: Record<NodeID, LGraphNode>, container: ContainerLayout, childIndex: number) => IDragItem,
|
||||||
moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void,
|
moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void,
|
||||||
groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout,
|
groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||||
ungroup: (container: ContainerLayout) => void,
|
ungroup: (container: ContainerLayout) => void,
|
||||||
@@ -752,13 +817,13 @@ export type SerializedDragEntry = {
|
|||||||
export type SerializedDragItem = {
|
export type SerializedDragItem = {
|
||||||
type: string,
|
type: string,
|
||||||
id: DragItemID,
|
id: DragItemID,
|
||||||
nodeId: UUID | null,
|
nodeId: NodeID | null,
|
||||||
attrs: Attributes
|
attrs: Attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableLayoutStateStore = Writable<LayoutState> & LayoutStateOps;
|
export type WritableLayoutStateStore = Writable<LayoutState> & LayoutStateOps;
|
||||||
|
|
||||||
function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateStore {
|
function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStateStore {
|
||||||
const store: Writable<LayoutState> = writable({
|
const store: Writable<LayoutState> = writable({
|
||||||
root: null,
|
root: null,
|
||||||
allItems: {},
|
allItems: {},
|
||||||
@@ -874,7 +939,7 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
if (newChildren)
|
if (newChildren)
|
||||||
state.allItems[parent.id].children = newChildren;
|
state.allItems[parent.id].children = newChildren;
|
||||||
for (const child of state.allItems[parent.id].children) {
|
for (const child of state.allItems[parent.id].children) {
|
||||||
if (child.id === SHADOW_PLACEHOLDER_ITEM_ID)
|
if (child.id === SHADOW_PLACEHOLDER_ITEM_ID || child.type === "template")
|
||||||
continue;
|
continue;
|
||||||
state.allItems[child.id].parent = parent;
|
state.allItems[child.id].parent = parent;
|
||||||
}
|
}
|
||||||
@@ -912,7 +977,11 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
|
|
||||||
let attrs: Partial<Attributes> = {}
|
let attrs: Partial<Attributes> = {}
|
||||||
|
|
||||||
if (options.addedBy === "moveIntoSubgraph" || options.addedBy === "moveOutOfSubgraph") {
|
if ((options.addedBy as any) === "template") {
|
||||||
|
// Template layout will be deserialized shortly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (options.addedBy === "moveIntoSubgraph" || options.addedBy === "moveOutOfSubgraph") {
|
||||||
// All we need to do is update the nodeID linked to this node.
|
// All we need to do is update the nodeID linked to this node.
|
||||||
const item = state.allItemsByNode[options.prevNodeID]
|
const item = state.allItemsByNode[options.prevNodeID]
|
||||||
delete state.allItemsByNode[options.prevNodeID]
|
delete state.allItemsByNode[options.prevNodeID]
|
||||||
@@ -984,6 +1053,56 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
store.set(state)
|
store.set(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: Modifies the template in-place, be sure you cloned it beforehand!
|
||||||
|
*/
|
||||||
|
function insertTemplate(template: SerializedComfyBoxTemplate, graph: LGraph, templateNodeIDToNode: Record<NodeID, LGraphNode>, container: ContainerLayout, childIndex: number): IDragItem {
|
||||||
|
const idMapping: Record<DragItemID, DragItemID> = {};
|
||||||
|
|
||||||
|
const getDragItemID = (id: DragItemID): DragItemID => {
|
||||||
|
idMapping[id] ||= uuidv4();
|
||||||
|
return idMapping[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all IDs are unique, and rewrite node IDs in widgets to point
|
||||||
|
// to newly created nodes
|
||||||
|
for (const [id, entry] of Object.entries(template.layout.allItems)) {
|
||||||
|
const newId = getDragItemID(id);
|
||||||
|
template.layout.allItems[newId] = entry;
|
||||||
|
entry.dragItem.id = newId;
|
||||||
|
|
||||||
|
if (entry.parent)
|
||||||
|
entry.parent = getDragItemID(entry.parent)
|
||||||
|
entry.children = entry.children.map(getDragItemID);
|
||||||
|
|
||||||
|
if (entry.dragItem.type === "widget") {
|
||||||
|
entry.dragItem.nodeId = templateNodeIDToNode[entry.dragItem.nodeId].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.layout.root) {
|
||||||
|
template.layout.root = getDragItemID(template.layout.root)
|
||||||
|
|
||||||
|
// make sure the new root doesn't have a parent since that parent
|
||||||
|
// was detached from the serialized layout and won't be found in
|
||||||
|
// template.layout.allItems
|
||||||
|
template.layout.allItems[template.layout.root].parent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = deserializeRaw(template.layout, graph);
|
||||||
|
|
||||||
|
// merge the template's detached layout tree into this layout
|
||||||
|
store.update(s => {
|
||||||
|
s.allItems = { ...s.allItems, ...raw.allItems }
|
||||||
|
s.allItemsByNode = { ...s.allItemsByNode, ...raw.allItemsByNode }
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
|
||||||
|
moveItem(raw.root, container, childIndex);
|
||||||
|
|
||||||
|
return raw.root
|
||||||
|
}
|
||||||
|
|
||||||
function moveItem(target: IDragItem, to: ContainerLayout, index?: number) {
|
function moveItem(target: IDragItem, to: ContainerLayout, index?: number) {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const entry = state.allItems[target.id]
|
const entry = state.allItems[target.id]
|
||||||
@@ -1148,6 +1267,13 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
const queue = [state.allItems[rootID]]
|
const queue = [state.allItems[rootID]]
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
const entry = queue.shift();
|
const entry = queue.shift();
|
||||||
|
|
||||||
|
if (entry.dragItem.type === "template") {
|
||||||
|
// If this happens then there's a bug somewhere
|
||||||
|
console.error("[layoutState] Found template drag item in current layout, skipping!")
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
allItems[entry.dragItem.id] = {
|
allItems[entry.dragItem.id] = {
|
||||||
dragItem: {
|
dragItem: {
|
||||||
type: entry.dragItem.type,
|
type: entry.dragItem.type,
|
||||||
@@ -1177,6 +1303,13 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
const allItems: Record<DragItemID, SerializedDragEntry> = {}
|
const allItems: Record<DragItemID, SerializedDragEntry> = {}
|
||||||
for (const pair of Object.entries(state.allItems)) {
|
for (const pair of Object.entries(state.allItems)) {
|
||||||
const [id, entry] = pair;
|
const [id, entry] = pair;
|
||||||
|
|
||||||
|
if (entry.dragItem.type === "template") {
|
||||||
|
// If this happens then there's a bug somewhere
|
||||||
|
console.error("[layoutState] Found template drag item in current layout, skipping!")
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
allItems[id] = {
|
allItems[id] = {
|
||||||
dragItem: {
|
dragItem: {
|
||||||
type: entry.dragItem.type,
|
type: entry.dragItem.type,
|
||||||
@@ -1195,12 +1328,19 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
function deserializeRaw(data: SerializedLayoutState, graph: LGraph): LayoutState {
|
||||||
const allItems: Record<DragItemID, DragItemEntry> = {}
|
const allItems: Record<DragItemID, DragItemEntry> = {}
|
||||||
const allItemsByNode: Record<number, DragItemEntry> = {}
|
const allItemsByNode: Record<number, DragItemEntry> = {}
|
||||||
|
|
||||||
for (const pair of Object.entries(data.allItems)) {
|
for (const pair of Object.entries(data.allItems)) {
|
||||||
const [id, entry] = pair;
|
const [id, entry] = pair;
|
||||||
|
|
||||||
|
if (entry.dragItem.type === "template") {
|
||||||
|
// If this happens then there's a bug somewhere
|
||||||
|
console.error("[layoutState] Found template drag item in serialized layout, skipping!")
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const dragItem: IDragItem = {
|
const dragItem: IDragItem = {
|
||||||
type: entry.dragItem.type,
|
type: entry.dragItem.type,
|
||||||
id: entry.dragItem.id,
|
id: entry.dragItem.id,
|
||||||
@@ -1249,6 +1389,12 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
isConfiguring: false,
|
isConfiguring: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
||||||
|
const state = deserializeRaw(data, graph);
|
||||||
|
|
||||||
console.debug("[layoutState] deserialize", data, state, defaultWorkflowAttributes)
|
console.debug("[layoutState] deserialize", data, state, defaultWorkflowAttributes)
|
||||||
|
|
||||||
store.set(state)
|
store.set(state)
|
||||||
@@ -1282,6 +1428,7 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
updateChildren,
|
updateChildren,
|
||||||
nodeAdded,
|
nodeAdded,
|
||||||
nodeRemoved,
|
nodeRemoved,
|
||||||
|
insertTemplate,
|
||||||
moveItem,
|
moveItem,
|
||||||
groupItems,
|
groupItems,
|
||||||
findLayoutEntryForNode,
|
findLayoutEntryForNode,
|
||||||
@@ -1299,7 +1446,7 @@ function createRaw(workflow: ComfyWorkflow | null = null): WritableLayoutStateSt
|
|||||||
return layoutStateStore
|
return layoutStateStore
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(workflow: ComfyWorkflow): WritableLayoutStateStore {
|
function create(workflow: ComfyBoxWorkflow): WritableLayoutStateStore {
|
||||||
if (get(layoutStates).all[workflow.id] != null) {
|
if (get(layoutStates).all[workflow.id] != null) {
|
||||||
throw new Error(`Layout state already created! ${id}`)
|
throw new Error(`Layout state already created! ${id}`)
|
||||||
}
|
}
|
||||||
@@ -1369,8 +1516,8 @@ export type LayoutStateStores = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LayoutStateStoresOps = {
|
export type LayoutStateStoresOps = {
|
||||||
create: (workflow: ComfyWorkflow) => WritableLayoutStateStore,
|
create: (workflow: ComfyBoxWorkflow) => WritableLayoutStateStore,
|
||||||
createRaw: (workflow?: ComfyWorkflow | null) => WritableLayoutStateStore,
|
createRaw: (workflow?: ComfyBoxWorkflow | null) => WritableLayoutStateStore,
|
||||||
remove: (workflowID: WorkflowInstID) => void,
|
remove: (workflowID: WorkflowInstID) => void,
|
||||||
getLayout: (workflowID: WorkflowInstID) => WritableLayoutStateStore | null,
|
getLayout: (workflowID: WorkflowInstID) => WritableLayoutStateStore | null,
|
||||||
getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null,
|
getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type ModalState = Record<string, any>;
|
|||||||
export type ModalButton = {
|
export type ModalButton = {
|
||||||
name: string,
|
name: string,
|
||||||
variant: "primary" | "secondary",
|
variant: "primary" | "secondary",
|
||||||
onClick: (state: ModalData) => void,
|
onClick: (state: ModalData) => boolean | void,
|
||||||
closeOnClick?: boolean
|
closeOnClick?: boolean
|
||||||
}
|
}
|
||||||
export interface ModalData {
|
export interface ModalData {
|
||||||
|
|||||||
128
src/lib/stores/templateState.ts
Normal file
128
src/lib/stores/templateState.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import type { SerializedComfyBoxTemplate } from '$lib/ComfyBoxTemplate';
|
||||||
|
import type { UUID } from '@litegraph-ts/core';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
import type { Readable, Writable } from 'svelte/store';
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
export type TemplateState = {
|
||||||
|
templates: SerializedComfyBoxTemplate[]
|
||||||
|
templatesByID: Record<UUID, SerializedComfyBoxTemplate>
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateStateOps = {
|
||||||
|
save: () => void,
|
||||||
|
load: () => void,
|
||||||
|
add: (template: SerializedComfyBoxTemplate) => boolean,
|
||||||
|
update: (template: SerializedComfyBoxTemplate) => boolean,
|
||||||
|
remove: (templateID: UUID) => boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WritableTemplateStateStore = Writable<TemplateState> & TemplateStateOps;
|
||||||
|
const store: Writable<TemplateState> = writable(
|
||||||
|
{
|
||||||
|
templates: [],
|
||||||
|
templatesByID: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
function add(template: SerializedComfyBoxTemplate): boolean {
|
||||||
|
const state = get(store);
|
||||||
|
if (state.templatesByID[template.id]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.update(s => {
|
||||||
|
s.templates.push(template);
|
||||||
|
s.templatesByID[template.id] = template;
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
|
||||||
|
save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(templateID: UUID): boolean {
|
||||||
|
const state = get(store);
|
||||||
|
if (!state.templatesByID[templateID]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.update(s => {
|
||||||
|
const index = s.templates.findIndex(t => t.id === templateID)
|
||||||
|
s.templates.splice(index, 1);
|
||||||
|
delete s.templatesByID[templateID];
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
|
||||||
|
save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(template: SerializedComfyBoxTemplate): boolean {
|
||||||
|
const state = get(store);
|
||||||
|
if (!state.templatesByID[template.id]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.update(s => {
|
||||||
|
const oldId = template.id
|
||||||
|
const index = s.templates.findIndex(t => t.id === oldId)
|
||||||
|
s.templates.splice(index, 1);
|
||||||
|
delete s.templatesByID[oldId];
|
||||||
|
|
||||||
|
template.id = uuidv4();
|
||||||
|
|
||||||
|
s.templates.push(template);
|
||||||
|
s.templatesByID[template.id] = template;
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
|
||||||
|
save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const json = JSON.stringify(get(store).templates)
|
||||||
|
localStorage.setItem("templates", json)
|
||||||
|
store.set(get(store))
|
||||||
|
}
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
const json = localStorage.getItem("templates")
|
||||||
|
if (!json) {
|
||||||
|
console.info("No templates in local storage, creating store")
|
||||||
|
save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(json) as SerializedComfyBoxTemplate[];
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
const templatesByID: Record<UUID, SerializedComfyBoxTemplate> =
|
||||||
|
data.map(d => [d.id, d])
|
||||||
|
.reduce((dict, el: [UUID, SerializedComfyBoxTemplate]) => (dict[el[0]] = el[1], dict), {})
|
||||||
|
|
||||||
|
store.set({
|
||||||
|
templates: data,
|
||||||
|
templatesByID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
store.set({
|
||||||
|
templates: [],
|
||||||
|
templatesByID: {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateStateStore: WritableTemplateStateStore =
|
||||||
|
{
|
||||||
|
...store,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
|
save,
|
||||||
|
load,
|
||||||
|
}
|
||||||
|
export default templateStateStore;
|
||||||
@@ -11,7 +11,7 @@ export type UIState = {
|
|||||||
uiEditMode: UIEditMode,
|
uiEditMode: UIEditMode,
|
||||||
|
|
||||||
reconnecting: boolean,
|
reconnecting: boolean,
|
||||||
isSavingToLocalStorage: boolean
|
forceSaveUserState: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type UIStateOps = {
|
type UIStateOps = {
|
||||||
@@ -29,7 +29,7 @@ const store: Writable<UIState> = writable(
|
|||||||
uiEditMode: "widgets",
|
uiEditMode: "widgets",
|
||||||
|
|
||||||
reconnecting: false,
|
reconnecting: false,
|
||||||
isSavingToLocalStorage: false
|
forceSaveUserState: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
function reconnecting() {
|
function reconnecting() {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export type WorkflowAttributes = {
|
|||||||
queuePromptButtonRunWorkflow: boolean,
|
queuePromptButtonRunWorkflow: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyWorkflow {
|
export class ComfyBoxWorkflow {
|
||||||
/*
|
/*
|
||||||
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
||||||
*/
|
*/
|
||||||
@@ -176,8 +176,8 @@ export class ComfyWorkflow {
|
|||||||
* will not. If you change your mind later be sure to call
|
* will not. If you change your mind later be sure to call
|
||||||
* layoutStates.remove(workflow.id)!
|
* layoutStates.remove(workflow.id)!
|
||||||
*/
|
*/
|
||||||
static create(title: string = "New Workflow"): [ComfyWorkflow, WritableLayoutStateStore] {
|
static create(title: string = "New Workflow"): [ComfyBoxWorkflow, WritableLayoutStateStore] {
|
||||||
const workflow = new ComfyWorkflow(title);
|
const workflow = new ComfyBoxWorkflow(title);
|
||||||
const layoutState = layoutStates.create(workflow);
|
const layoutState = layoutStates.create(workflow);
|
||||||
return [workflow, layoutState]
|
return [workflow, layoutState]
|
||||||
}
|
}
|
||||||
@@ -227,29 +227,29 @@ export class ComfyWorkflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowState = {
|
export type WorkflowState = {
|
||||||
openedWorkflows: ComfyWorkflow[],
|
openedWorkflows: ComfyBoxWorkflow[],
|
||||||
openedWorkflowsByID: Record<WorkflowInstID, ComfyWorkflow>,
|
openedWorkflowsByID: Record<WorkflowInstID, ComfyBoxWorkflow>,
|
||||||
activeWorkflowID: WorkflowInstID | null,
|
activeWorkflowID: WorkflowInstID | null,
|
||||||
activeWorkflow: ComfyWorkflow | null,
|
activeWorkflow: ComfyBoxWorkflow | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkflowReceiveOutputTargets = {
|
export type WorkflowReceiveOutputTargets = {
|
||||||
workflow: ComfyWorkflow,
|
workflow: ComfyBoxWorkflow,
|
||||||
targetNodes: ComfyReceiveOutputNode[]
|
targetNodes: ComfyReceiveOutputNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowStateOps = {
|
type WorkflowStateOps = {
|
||||||
getWorkflow: (id: WorkflowInstID) => ComfyWorkflow | null
|
getWorkflow: (id: WorkflowInstID) => ComfyBoxWorkflow | null
|
||||||
getWorkflowByGraph: (graph: LGraph) => ComfyWorkflow | null
|
getWorkflowByGraph: (graph: LGraph) => ComfyBoxWorkflow | null
|
||||||
getWorkflowByNode: (node: LGraphNode) => ComfyWorkflow | null
|
getWorkflowByNode: (node: LGraphNode) => ComfyBoxWorkflow | null
|
||||||
getWorkflowByNodeID: (id: NodeID) => ComfyWorkflow | null
|
getWorkflowByNodeID: (id: NodeID) => ComfyBoxWorkflow | null
|
||||||
getActiveWorkflow: () => ComfyWorkflow | null
|
getActiveWorkflow: () => ComfyBoxWorkflow | null
|
||||||
createNewWorkflow: (canvas: ComfyGraphCanvas, title?: string, setActive?: boolean) => ComfyWorkflow,
|
createNewWorkflow: (canvas: ComfyGraphCanvas, title?: string, setActive?: boolean) => ComfyBoxWorkflow,
|
||||||
openWorkflow: (canvas: ComfyGraphCanvas, data: SerializedAppState, setActive?: boolean) => ComfyWorkflow,
|
openWorkflow: (canvas: ComfyGraphCanvas, data: SerializedAppState, setActive?: boolean) => ComfyBoxWorkflow,
|
||||||
addWorkflow: (canvas: ComfyGraphCanvas, data: ComfyWorkflow, setActive?: boolean) => void,
|
addWorkflow: (canvas: ComfyGraphCanvas, data: ComfyBoxWorkflow, setActive?: boolean) => void,
|
||||||
closeWorkflow: (canvas: ComfyGraphCanvas, index: number) => void,
|
closeWorkflow: (canvas: ComfyGraphCanvas, index: number) => void,
|
||||||
closeAllWorkflows: (canvas: ComfyGraphCanvas) => void,
|
closeAllWorkflows: (canvas: ComfyGraphCanvas) => void,
|
||||||
setActiveWorkflow: (canvas: ComfyGraphCanvas, index: number | WorkflowInstID) => ComfyWorkflow | null,
|
setActiveWorkflow: (canvas: ComfyGraphCanvas, index: number | WorkflowInstID) => ComfyBoxWorkflow | null,
|
||||||
findReceiveOutputTargets: (type: SlotType | SlotType[]) => WorkflowReceiveOutputTargets[]
|
findReceiveOutputTargets: (type: SlotType | SlotType[]) => WorkflowReceiveOutputTargets[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,36 +262,36 @@ const store: Writable<WorkflowState> = writable(
|
|||||||
activeWorkflow: null
|
activeWorkflow: null
|
||||||
})
|
})
|
||||||
|
|
||||||
function getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
function getWorkflow(id: WorkflowInstID): ComfyBoxWorkflow | null {
|
||||||
return get(store).openedWorkflowsByID[id];
|
return get(store).openedWorkflowsByID[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWorkflowByGraph(graph: LGraph): ComfyWorkflow | null {
|
function getWorkflowByGraph(graph: LGraph): ComfyBoxWorkflow | null {
|
||||||
const workflowID = (graph.getRootGraph() as ComfyGraph)?.workflowID;
|
const workflowID = (graph.getRootGraph() as ComfyGraph)?.workflowID;
|
||||||
if (workflowID == null)
|
if (workflowID == null)
|
||||||
return null;
|
return null;
|
||||||
return getWorkflow(workflowID);
|
return getWorkflow(workflowID);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWorkflowByNode(node: LGraphNode): ComfyWorkflow | null {
|
function getWorkflowByNode(node: LGraphNode): ComfyBoxWorkflow | null {
|
||||||
return getWorkflowByGraph(node.graph);
|
return getWorkflowByGraph(node.graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWorkflowByNodeID(id: NodeID): ComfyWorkflow | null {
|
function getWorkflowByNodeID(id: NodeID): ComfyBoxWorkflow | null {
|
||||||
return Object.values(get(store).openedWorkflows).find(w => {
|
return Object.values(get(store).openedWorkflows).find(w => {
|
||||||
return w.graph.getNodeByIdRecursive(id) != null
|
return w.graph.getNodeByIdRecursive(id) != null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveWorkflow(): ComfyWorkflow | null {
|
function getActiveWorkflow(): ComfyBoxWorkflow | null {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
if (state.activeWorkflowID == null)
|
if (state.activeWorkflowID == null)
|
||||||
return null;
|
return null;
|
||||||
return state.openedWorkflowsByID[state.activeWorkflowID];
|
return state.openedWorkflowsByID[state.activeWorkflowID];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewWorkflow(canvas: ComfyGraphCanvas, title: string = "New Workflow", setActive: boolean = false): ComfyWorkflow {
|
function createNewWorkflow(canvas: ComfyGraphCanvas, title: string = "New Workflow", setActive: boolean = false): ComfyBoxWorkflow {
|
||||||
const workflow = new ComfyWorkflow(title);
|
const workflow = new ComfyBoxWorkflow(title);
|
||||||
const layoutState = layoutStates.create(workflow);
|
const layoutState = layoutStates.create(workflow);
|
||||||
layoutState.initDefaultLayout();
|
layoutState.initDefaultLayout();
|
||||||
|
|
||||||
@@ -307,8 +307,8 @@ function createNewWorkflow(canvas: ComfyGraphCanvas, title: string = "New Workfl
|
|||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWorkflow(canvas: ComfyGraphCanvas, data: SerializedAppState, setActive: boolean = true): ComfyWorkflow {
|
function openWorkflow(canvas: ComfyGraphCanvas, data: SerializedAppState, setActive: boolean = true): ComfyBoxWorkflow {
|
||||||
const [workflow, layoutState] = ComfyWorkflow.create("Workflow")
|
const [workflow, layoutState] = ComfyBoxWorkflow.create("Workflow")
|
||||||
workflow.deserialize(layoutState, { graph: data.workflow, layout: data.layout, attrs: data.attrs })
|
workflow.deserialize(layoutState, { graph: data.workflow, layout: data.layout, attrs: data.attrs })
|
||||||
|
|
||||||
addWorkflow(canvas, workflow, setActive);
|
addWorkflow(canvas, workflow, setActive);
|
||||||
@@ -316,7 +316,7 @@ function openWorkflow(canvas: ComfyGraphCanvas, data: SerializedAppState, setAct
|
|||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWorkflow(canvas: ComfyGraphCanvas, workflow: ComfyWorkflow, setActive: boolean = true) {
|
function addWorkflow(canvas: ComfyGraphCanvas, workflow: ComfyBoxWorkflow, setActive: boolean = true) {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
state.openedWorkflows.push(workflow);
|
state.openedWorkflows.push(workflow);
|
||||||
state.openedWorkflowsByID[workflow.id] = workflow;
|
state.openedWorkflowsByID[workflow.id] = workflow;
|
||||||
@@ -354,7 +354,7 @@ function closeAllWorkflows(canvas: ComfyGraphCanvas) {
|
|||||||
closeWorkflow(canvas, 0)
|
closeWorkflow(canvas, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowInstID): ComfyWorkflow | null {
|
function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowInstID): ComfyBoxWorkflow | null {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
|
|
||||||
if (state.openedWorkflows.length === 0) {
|
if (state.openedWorkflows.length === 0) {
|
||||||
|
|||||||
1171
src/lib/utils.ts
1171
src/lib/utils.ts
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
|||||||
import "klecks/style/style.scss";
|
import "klecks/style/style.scss";
|
||||||
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
||||||
import { uploadImageToComfyUI, type ComfyBoxImageMetadata, comfyFileToComfyBoxMetadata, comfyBoxImageToComfyURL, comfyBoxImageToComfyFile, type ComfyUploadImageType, type ComfyImageLocation } from "$lib/utils";
|
import { uploadImageToComfyUI, type ComfyBoxImageMetadata, comfyFileToComfyBoxMetadata, comfyBoxImageToComfyURL, comfyBoxImageToComfyFile, type ComfyUploadImageType, type ComfyImageLocation } from "$lib/utils";
|
||||||
|
import configState from "$lib/stores/configState";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import NumberInput from "$lib/components/NumberInput.svelte";
|
import NumberInput from "$lib/components/NumberInput.svelte";
|
||||||
import type { ComfyImageEditorNode } from "$lib/nodes/widgets";
|
import type { ComfyImageEditorNode } from "$lib/nodes/widgets";
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
|
|
||||||
showModal = true;
|
showModal = true;
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const url = configState.getBackendURL();
|
||||||
|
|
||||||
kl = new Klecks({
|
kl = new Klecks({
|
||||||
embedUrl: url,
|
embedUrl: url,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const COLOR_MAP: [string, string][] = [
|
|||||||
_node ||= node;
|
_node ||= node;
|
||||||
bboxes ||= $nodeValue
|
bboxes ||= $nodeValue
|
||||||
|
|
||||||
console.debug("[MultiRegionWidget] Recreate!", bboxes, imageElem, _node)
|
// console.debug("[MultiRegionWidget] Recreate!", bboxes, _node)
|
||||||
|
|
||||||
if (_node != null && imageElem != null && imageContainer != null) {
|
if (_node != null && imageElem != null && imageContainer != null) {
|
||||||
selectedIndex = clamp(selectedIndex, 0, bboxes.length - 1);
|
selectedIndex = clamp(selectedIndex, 0, bboxes.length - 1);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import { basicSetup } from "./TextWidgetCodeVariant";
|
import { basicSetup } from "./TextWidgetCodeVariant";
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import { TAG_CATEGORY_COLORS } from "$lib/DanbooruTags";
|
import { TAG_CATEGORY_COLORS } from "$lib/DanbooruTags";
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
|
||||||
export let widget: WidgetLayout;
|
export let widget: WidgetLayout;
|
||||||
export let node: ComfyTextNode;
|
export let node: ComfyTextNode;
|
||||||
@@ -176,7 +177,7 @@
|
|||||||
function getExtensions(): Extension[] {
|
function getExtensions(): Extension[] {
|
||||||
// TODO
|
// TODO
|
||||||
const readonly = false;
|
const readonly = false;
|
||||||
const placeholder = "Placeholder..."
|
const placeholder = ""
|
||||||
const dark_mode = true;
|
const dark_mode = true;
|
||||||
|
|
||||||
const stateExtensions: Extension[] = [
|
const stateExtensions: Extension[] = [
|
||||||
@@ -206,12 +207,22 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrap">
|
<div class="code-editor-wrapper">
|
||||||
<div class="codemirror-wrapper {classNames}" bind:this={element} />
|
<Block>
|
||||||
|
<BlockTitle>{widget.attrs.title}</BlockTitle>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="codemirror-wrapper {classNames}" bind:this={element} />
|
||||||
|
</div>
|
||||||
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
<!-- <CodeMirror bind:value={$nodeValue} {styles} /> -->
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.code-editor-wrapper {
|
||||||
|
:global(> .block) {
|
||||||
|
background: var(--panel-background-fill) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
import workflowState, { ComfyWorkflow } from "$lib/stores/workflowState";
|
import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
import { getNodeInfo } from "$lib/utils"
|
import { getNodeInfo } from "$lib/utils"
|
||||||
|
|
||||||
import { Link, Toolbar } from "framework7-svelte"
|
import { Link, Toolbar } from "framework7-svelte"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
let layoutState: WritableLayoutStateStore = null;
|
let layoutState: WritableLayoutStateStore = null;
|
||||||
let fileInput: HTMLInputElement = undefined;
|
let fileInput: HTMLInputElement = undefined;
|
||||||
let workflow: ComfyWorkflow | null = null;
|
let workflow: ComfyBoxWorkflow | null = null;
|
||||||
|
|
||||||
$: workflow = $workflowState.activeWorkflow;
|
$: workflow = $workflowState.activeWorkflow;
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
import type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import type { WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
import type { WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||||
import workflowState, { type ComfyWorkflow } from "$lib/stores/workflowState";
|
import workflowState, { type ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
|
|
||||||
export let subworkflowID: number = -1;
|
export let subworkflowID: number = -1;
|
||||||
export let app: ComfyApp
|
export let app: ComfyApp
|
||||||
|
|
||||||
// TODO move
|
// TODO move
|
||||||
let workflow: ComfyWorkflow | null = null
|
let workflow: ComfyBoxWorkflow | null = null
|
||||||
let layoutState: WritableLayoutStateStore | null = null;
|
let layoutState: WritableLayoutStateStore | null = null;
|
||||||
|
|
||||||
$: workflow = $workflowState.activeWorkflow;
|
$: workflow = $workflowState.activeWorkflow;
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ body {
|
|||||||
|
|
||||||
// Disable pull to refresh
|
// Disable pull to refresh
|
||||||
overscroll-behavior-y: contain;
|
overscroll-behavior-y: contain;
|
||||||
}
|
|
||||||
|
|
||||||
#app-root {
|
|
||||||
background: var(--body-background-fill);
|
background: var(--body-background-fill);
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
font-family: Arial;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -125,6 +128,7 @@ body {
|
|||||||
border-color: var(--comfy-disabled-textbox-border-color);
|
border-color: var(--comfy-disabled-textbox-border-color);
|
||||||
box-shadow: 0 0 0 var(--shadow-spread) transparent, rgba(0, 0, 0, 0.08) 0px 2px 4px 0px inset;
|
box-shadow: 0 0 0 var(--shadow-spread) transparent, rgba(0, 0, 0, 0.08) 0px 2px 4px 0px inset;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
color: var(--comfy-disabled-label-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin disable-inputs {
|
@mixin disable-inputs {
|
||||||
@@ -216,3 +220,8 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixes drop item shadow not appearing when dragging template items onto workflow
|
||||||
|
:global([data-is-dnd-shadow-item]) {
|
||||||
|
min-height: 5rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import { graphToGraphVis } from "$lib/utils";
|
|||||||
import { ComfyNumberNode } from "$lib/nodes/widgets";
|
import { ComfyNumberNode } from "$lib/nodes/widgets";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import layoutStates from "$lib/stores/layoutStates";
|
import layoutStates from "$lib/stores/layoutStates";
|
||||||
import { ComfyWorkflow } from "$lib/stores/workflowState";
|
import { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
|
|
||||||
export default class ComfyGraphTests extends UnitTest {
|
export default class ComfyGraphTests extends UnitTest {
|
||||||
test__onNodeAdded__updatesLayoutState() {
|
test__onNodeAdded__updatesLayoutState() {
|
||||||
const [{ graph }, layoutState] = ComfyWorkflow.create()
|
const [{ graph }, layoutState] = ComfyBoxWorkflow.create()
|
||||||
layoutState.initDefaultLayout() // adds 3 containers
|
layoutState.initDefaultLayout() // adds 3 containers
|
||||||
|
|
||||||
const state = get(layoutState)
|
const state = get(layoutState)
|
||||||
@@ -39,7 +39,7 @@ export default class ComfyGraphTests extends UnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test__onNodeAdded__handlesNodesAddedInSubgraphs() {
|
test__onNodeAdded__handlesNodesAddedInSubgraphs() {
|
||||||
const [{ graph }, layoutState] = ComfyWorkflow.create()
|
const [{ graph }, layoutState] = ComfyBoxWorkflow.create()
|
||||||
layoutState.initDefaultLayout()
|
layoutState.initDefaultLayout()
|
||||||
|
|
||||||
const subgraph = LiteGraph.createNode(Subgraph);
|
const subgraph = LiteGraph.createNode(Subgraph);
|
||||||
@@ -58,7 +58,7 @@ export default class ComfyGraphTests extends UnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test__onNodeAdded__handlesSubgraphsWithNodes() {
|
test__onNodeAdded__handlesSubgraphsWithNodes() {
|
||||||
const [{ graph }, layoutState] = ComfyWorkflow.create()
|
const [{ graph }, layoutState] = ComfyBoxWorkflow.create()
|
||||||
layoutState.initDefaultLayout()
|
layoutState.initDefaultLayout()
|
||||||
|
|
||||||
const state = get(layoutState)
|
const state = get(layoutState)
|
||||||
@@ -76,7 +76,7 @@ export default class ComfyGraphTests extends UnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test__onNodeRemoved__updatesLayoutState() {
|
test__onNodeRemoved__updatesLayoutState() {
|
||||||
const [{ graph }, layoutState] = ComfyWorkflow.create()
|
const [{ graph }, layoutState] = ComfyBoxWorkflow.create()
|
||||||
layoutState.initDefaultLayout()
|
layoutState.initDefaultLayout()
|
||||||
|
|
||||||
const widget = LiteGraph.createNode(ComfyNumberNode);
|
const widget = LiteGraph.createNode(ComfyNumberNode);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
|
"typeRoots": ["./node_modules/@types/", "./src"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"$lib": ["lib"],
|
"$lib": ["lib"],
|
||||||
"$lib/*": ["lib/*"]
|
"$lib/*": ["lib/*"]
|
||||||
|
|||||||
@@ -6,13 +6,17 @@ import FullReload from 'vite-plugin-full-reload';
|
|||||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||||
import removeConsole from 'vite-plugin-svelte-console-remover';
|
import removeConsole from 'vite-plugin-svelte-console-remover';
|
||||||
import glsl from 'vite-plugin-glsl';
|
import glsl from 'vite-plugin-glsl';
|
||||||
|
import { execSync } from "child_process"
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === "production";
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
console.log("Production build: " + isProduction)
|
console.log("Production build: " + isProduction)
|
||||||
|
|
||||||
|
const commitHash = execSync('git rev-parse HEAD').toString();
|
||||||
|
console.log("Commit: " + commitHash)
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
"__GIT_COMMIT_HASH__": '"test"'
|
"__GIT_COMMIT_HASH__": JSON.stringify(commitHash)
|
||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
base: "./",
|
base: "./",
|
||||||
|
|||||||
Reference in New Issue
Block a user