mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): migrate add node popover to cmdk
Put this together as a way to figure out the library before moving on to the full app cmdk. Works great.
This commit is contained in:
parent
312093cbb0
commit
6a62854e7d
@ -64,6 +64,7 @@
|
|||||||
"@roarr/browser-log-writer": "^1.3.0",
|
"@roarr/browser-log-writer": "^1.3.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"chakra-react-select": "^4.9.1",
|
"chakra-react-select": "^4.9.1",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"dateformat": "^5.0.3",
|
"dateformat": "^5.0.3",
|
||||||
"fracturedjsonjs": "^4.0.2",
|
"fracturedjsonjs": "^4.0.2",
|
||||||
@ -92,7 +93,6 @@
|
|||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
"react-redux": "9.1.2",
|
"react-redux": "9.1.2",
|
||||||
"react-resizable-panels": "^2.0.23",
|
"react-resizable-panels": "^2.0.23",
|
||||||
"react-select": "5.8.0",
|
|
||||||
"react-use": "^17.5.1",
|
"react-use": "^17.5.1",
|
||||||
"react-virtuoso": "^4.9.0",
|
"react-virtuoso": "^4.9.0",
|
||||||
"reactflow": "^11.11.4",
|
"reactflow": "^11.11.4",
|
||||||
|
@ -41,6 +41,9 @@ dependencies:
|
|||||||
chakra-react-select:
|
chakra-react-select:
|
||||||
specifier: ^4.9.1
|
specifier: ^4.9.1
|
||||||
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
cmdk:
|
||||||
|
specifier: ^1.0.0
|
||||||
|
version: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
compare-versions:
|
compare-versions:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@ -125,9 +128,6 @@ dependencies:
|
|||||||
react-resizable-panels:
|
react-resizable-panels:
|
||||||
specifier: ^2.0.23
|
specifier: ^2.0.23
|
||||||
version: 2.0.23(react-dom@18.3.1)(react@18.3.1)
|
version: 2.0.23(react-dom@18.3.1)(react@18.3.1)
|
||||||
react-select:
|
|
||||||
specifier: 5.8.0
|
|
||||||
version: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
|
||||||
react-use:
|
react-use:
|
||||||
specifier: ^17.5.1
|
specifier: ^17.5.1
|
||||||
version: 17.5.1(react-dom@18.3.1)(react@18.3.1)
|
version: 17.5.1(react-dom@18.3.1)(react@18.3.1)
|
||||||
@ -2053,7 +2053,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/dom-utils': 2.1.0
|
'@chakra-ui/dom-utils': 2.1.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-focus-lock: 2.12.1(@types/react@18.3.3)(react@18.3.1)
|
react-focus-lock: 2.13.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
@ -3784,6 +3784,288 @@ packages:
|
|||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/primitive@1.0.1:
|
||||||
|
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-context@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
aria-hidden: 1.2.4
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-remove-scroll: 2.5.5(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-id@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
'@types/react-dom': 18.3.0
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.4
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@reactflow/background@11.3.14(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
/@reactflow/background@11.3.14(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
|
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5210,7 +5492,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
|
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/react-transition-group@4.4.10:
|
/@types/react-transition-group@4.4.10:
|
||||||
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
||||||
@ -6268,6 +6549,21 @@ packages:
|
|||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cmdk@1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0
|
||||||
|
react-dom: ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert@1.9.3:
|
/color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9712,8 +10008,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-focus-lock@2.12.1(@types/react@18.3.3)(react@18.3.1):
|
/react-focus-lock@2.13.0(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-lfp8Dve4yJagkHiFrC1bGtib3mF2ktqwPJw4/WGcgPW+pJ/AVQA5X2vI7xgp13FcxFEpYBBHpXai/N2DBNC0Jw==}
|
resolution: {integrity: sha512-w7aIcTwZwNzUp2fYQDMICy+khFwVmKmOrLF8kNsPS+dz4Oi/oxoVJ2wCMVvX6rWGriM/+mYaTyp1MRmkcs2amw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@ -9867,6 +10163,25 @@ packages:
|
|||||||
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-remove-scroll@2.5.5(@types/react@18.3.3)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.3
|
||||||
|
react: 18.3.1
|
||||||
|
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
tslib: 2.7.0
|
||||||
|
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-resizable-panels@2.0.23(react-dom@18.3.1)(react@18.3.1):
|
/react-resizable-panels@2.0.23(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-8ZKTwTU11t/FYwiwhMdtZYYyFxic5U5ysRu2YwfkAgDbUJXFvnWSJqhnzkSlW+mnDoNAzDCrJhdOSXBPA76wug==}
|
resolution: {integrity: sha512-8ZKTwTU11t/FYwiwhMdtZYYyFxic5U5ysRu2YwfkAgDbUJXFvnWSJqhnzkSlW+mnDoNAzDCrJhdOSXBPA76wug==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2,6 +2,7 @@ import 'reactflow/dist/style.css';
|
|||||||
|
|
||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { AddNodeCmdk } from 'features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk';
|
||||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||||
import { LoadWorkflowFromGraphModal } from 'features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal';
|
import { LoadWorkflowFromGraphModal } from 'features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal';
|
||||||
import { SaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/SaveWorkflowAsDialog';
|
import { SaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/SaveWorkflowAsDialog';
|
||||||
@ -10,7 +11,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { MdDeviceHub } from 'react-icons/md';
|
import { MdDeviceHub } from 'react-icons/md';
|
||||||
import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
|
import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
import AddNodePopover from './flow/AddNodePopover/AddNodePopover';
|
|
||||||
import { Flow } from './flow/Flow';
|
import { Flow } from './flow/Flow';
|
||||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||||
@ -31,7 +31,7 @@ const NodeEditor = () => {
|
|||||||
{data && (
|
{data && (
|
||||||
<>
|
<>
|
||||||
<Flow />
|
<Flow />
|
||||||
<AddNodePopover />
|
<AddNodeCmdk />
|
||||||
<TopPanel />
|
<TopPanel />
|
||||||
<BottomLeftPanel />
|
<BottomLeftPanel />
|
||||||
<MinimapPanel />
|
<MinimapPanel />
|
||||||
|
@ -0,0 +1,420 @@
|
|||||||
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalOverlay,
|
||||||
|
Spacer,
|
||||||
|
Text,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { useAppStore } from 'app/store/storeHooks';
|
||||||
|
import { CommandEmpty, CommandItem, CommandList, CommandRoot } from 'cmdk';
|
||||||
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
|
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
|
||||||
|
import {
|
||||||
|
$addNodeCmdk,
|
||||||
|
$cursorPos,
|
||||||
|
$edgePendingUpdate,
|
||||||
|
$pendingConnection,
|
||||||
|
$templates,
|
||||||
|
edgesChanged,
|
||||||
|
nodesChanged,
|
||||||
|
useAddNodeCmdk,
|
||||||
|
} from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||||
|
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
|
||||||
|
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
|
||||||
|
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
|
||||||
|
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
|
||||||
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
|
import { toast } from 'features/toast/toast';
|
||||||
|
import { memoize } from 'lodash-es';
|
||||||
|
import { computed } from 'nanostores';
|
||||||
|
import type { ChangeEvent } from 'react';
|
||||||
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiFlaskBold, PiHammerBold } from 'react-icons/pi';
|
||||||
|
import type { EdgeChange, NodeChange } from 'reactflow';
|
||||||
|
import type { S } from 'services/api/types';
|
||||||
|
|
||||||
|
const useThrottle = <T,>(value: T, limit: number) => {
|
||||||
|
const [throttledValue, setThrottledValue] = useState(value);
|
||||||
|
const lastRan = useRef(Date.now());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(
|
||||||
|
function () {
|
||||||
|
if (Date.now() - lastRan.current >= limit) {
|
||||||
|
setThrottledValue(value);
|
||||||
|
lastRan.current = Date.now();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit - (Date.now() - lastRan.current)
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, limit]);
|
||||||
|
|
||||||
|
return throttledValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useAddNode = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const store = useAppStore();
|
||||||
|
const buildInvocation = useBuildNode();
|
||||||
|
const templates = useStore($templates);
|
||||||
|
const pendingConnection = useStore($pendingConnection);
|
||||||
|
|
||||||
|
const addNode = useCallback(
|
||||||
|
(nodeType: string): void => {
|
||||||
|
const node = buildInvocation(nodeType);
|
||||||
|
if (!node) {
|
||||||
|
const errorMessage = t('nodes.unknownNode', {
|
||||||
|
nodeType: nodeType,
|
||||||
|
});
|
||||||
|
toast({
|
||||||
|
status: 'error',
|
||||||
|
title: errorMessage,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a cozy spot for the node
|
||||||
|
const cursorPos = $cursorPos.get();
|
||||||
|
const { nodes, edges } = selectNodesSlice(store.getState());
|
||||||
|
node.position = findUnoccupiedPosition(nodes, cursorPos?.x ?? node.position.x, cursorPos?.y ?? node.position.y);
|
||||||
|
node.selected = true;
|
||||||
|
|
||||||
|
// Deselect all other nodes and edges
|
||||||
|
const nodeChanges: NodeChange[] = [{ type: 'add', item: node }];
|
||||||
|
const edgeChanges: EdgeChange[] = [];
|
||||||
|
nodes.forEach(({ id, selected }) => {
|
||||||
|
if (selected) {
|
||||||
|
nodeChanges.push({ type: 'select', id, selected: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
edges.forEach(({ id, selected }) => {
|
||||||
|
if (selected) {
|
||||||
|
edgeChanges.push({ type: 'select', id, selected: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Onwards!
|
||||||
|
if (nodeChanges.length > 0) {
|
||||||
|
store.dispatch(nodesChanged(nodeChanges));
|
||||||
|
}
|
||||||
|
if (edgeChanges.length > 0) {
|
||||||
|
store.dispatch(edgesChanged(edgeChanges));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-connect an edge if we just added a node and have a pending connection
|
||||||
|
if (pendingConnection && isInvocationNode(node)) {
|
||||||
|
const edgePendingUpdate = $edgePendingUpdate.get();
|
||||||
|
const { handleType } = pendingConnection;
|
||||||
|
|
||||||
|
const source = handleType === 'source' ? pendingConnection.nodeId : node.id;
|
||||||
|
const sourceHandle = handleType === 'source' ? pendingConnection.handleId : null;
|
||||||
|
const target = handleType === 'target' ? pendingConnection.nodeId : node.id;
|
||||||
|
const targetHandle = handleType === 'target' ? pendingConnection.handleId : null;
|
||||||
|
|
||||||
|
const { nodes, edges } = selectNodesSlice(store.getState());
|
||||||
|
const connection = getFirstValidConnection(
|
||||||
|
source,
|
||||||
|
sourceHandle,
|
||||||
|
target,
|
||||||
|
targetHandle,
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
templates,
|
||||||
|
edgePendingUpdate
|
||||||
|
);
|
||||||
|
if (connection) {
|
||||||
|
const newEdge = connectionToEdge(connection);
|
||||||
|
store.dispatch(edgesChanged([{ type: 'add', item: newEdge }]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[buildInvocation, pendingConnection, store, t, templates]
|
||||||
|
);
|
||||||
|
|
||||||
|
return addNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmdkRootSx: SystemStyleObject = {
|
||||||
|
'[cmdk-root]': {
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
},
|
||||||
|
'[cmdk-list]': {
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddNodeCmdk = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const addNodeCmdk = useAddNodeCmdk();
|
||||||
|
const addNodeCmdkIsOpen = useStore(addNodeCmdk.$boolean);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const addNode = useAddNode();
|
||||||
|
const throttledSearchTerm = useThrottle(searchTerm, 100);
|
||||||
|
|
||||||
|
useHotkeys(['shift+a', 'space'], addNodeCmdk.setTrue, { preventDefault: true });
|
||||||
|
|
||||||
|
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSelect = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
addNode(value);
|
||||||
|
$addNodeCmdk.set(false);
|
||||||
|
setSearchTerm('');
|
||||||
|
},
|
||||||
|
[addNode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
addNodeCmdk.setFalse();
|
||||||
|
setSearchTerm('');
|
||||||
|
$pendingConnection.set(null);
|
||||||
|
}, [addNodeCmdk]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={addNodeCmdkIsOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
useInert={false}
|
||||||
|
initialFocusRef={inputRef}
|
||||||
|
size="xl"
|
||||||
|
isCentered
|
||||||
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent h="512" maxH="70%">
|
||||||
|
<ModalBody p={2} h="full" sx={cmdkRootSx}>
|
||||||
|
<CommandRoot loop shouldFilter={false}>
|
||||||
|
<Flex flexDir="column" h="full" gap={2}>
|
||||||
|
<Input ref={inputRef} value={searchTerm} onChange={onChange} placeholder={t('nodes.nodeSearch')} />
|
||||||
|
<Box w="full" h="full">
|
||||||
|
<ScrollableContent>
|
||||||
|
<CommandEmpty>
|
||||||
|
<IAINoContentFallback
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
left={0}
|
||||||
|
icon={null}
|
||||||
|
label="No matching items"
|
||||||
|
/>
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandList>
|
||||||
|
<NodeCommandList searchTerm={throttledSearchTerm} onSelect={onSelect} />
|
||||||
|
</CommandList>
|
||||||
|
</ScrollableContent>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</CommandRoot>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddNodeCmdk.displayName = 'AddNodeCmdk';
|
||||||
|
|
||||||
|
const cmdkItemSx: SystemStyleObject = {
|
||||||
|
'&[data-selected="true"]': {
|
||||||
|
bg: 'base.700',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type NodeCommandItemData = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
classification: S['Classification'];
|
||||||
|
nodePack: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const $templatesArray = computed($templates, (templates) => Object.values(templates));
|
||||||
|
|
||||||
|
const createRegex = memoize(
|
||||||
|
(inputValue: string) =>
|
||||||
|
new RegExp(
|
||||||
|
inputValue
|
||||||
|
.trim()
|
||||||
|
.replace(/[-[\]{}()*+!<=:?./\\^$|#,]/g, '')
|
||||||
|
.split(' ')
|
||||||
|
.join('.*'),
|
||||||
|
'gi'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filterable items are a subset of Invocation template - we also want to filter for notes or current image node,
|
||||||
|
// so we are using a less specific type instead of `InvocationTemplate`
|
||||||
|
type FilterableItem = {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
classification: S['Classification'];
|
||||||
|
nodePack: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filter = memoize(
|
||||||
|
(item: FilterableItem, searchTerm: string) => {
|
||||||
|
const regex = createRegex(searchTerm);
|
||||||
|
|
||||||
|
if (!searchTerm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.title.includes(searchTerm) || regex.test(item.title)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type.includes(searchTerm) || regex.test(item.type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.description.includes(searchTerm) || regex.test(item.description)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.nodePack.includes(searchTerm) || regex.test(item.nodePack)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.classification.includes(searchTerm) || regex.test(item.classification)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of item.tags) {
|
||||||
|
if (tag.includes(searchTerm) || regex.test(tag)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
(item: FilterableItem, searchTerm: string) => `${item.type}-${searchTerm}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const NodeCommandList = memo(({ searchTerm, onSelect }: { searchTerm: string; onSelect: (value: string) => void }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const templatesArray = useStore($templatesArray);
|
||||||
|
const pendingConnection = useStore($pendingConnection);
|
||||||
|
const currentImageFilterItem = useMemo<FilterableItem>(
|
||||||
|
() => ({
|
||||||
|
type: 'current_image',
|
||||||
|
title: t('nodes.currentImage'),
|
||||||
|
description: t('nodes.currentImageDescription'),
|
||||||
|
tags: ['progress', 'image', 'current'],
|
||||||
|
classification: 'stable',
|
||||||
|
nodePack: 'invokeai',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
const notesFilterItem = useMemo<FilterableItem>(
|
||||||
|
() => ({
|
||||||
|
type: 'notes',
|
||||||
|
title: t('nodes.notes'),
|
||||||
|
description: t('nodes.notesDescription'),
|
||||||
|
tags: ['notes'],
|
||||||
|
classification: 'stable',
|
||||||
|
nodePack: 'invokeai',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = useMemo<NodeCommandItemData[]>(() => {
|
||||||
|
// If we have a connection in progress, we need to filter the node choices
|
||||||
|
const _items: NodeCommandItemData[] = [];
|
||||||
|
|
||||||
|
if (!pendingConnection) {
|
||||||
|
for (const template of templatesArray) {
|
||||||
|
if (filter(template, searchTerm)) {
|
||||||
|
_items.push({
|
||||||
|
label: template.title,
|
||||||
|
value: template.type,
|
||||||
|
description: template.description,
|
||||||
|
classification: template.classification,
|
||||||
|
nodePack: template.nodePack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of [currentImageFilterItem, notesFilterItem]) {
|
||||||
|
if (filter(item, searchTerm)) {
|
||||||
|
_items.push({
|
||||||
|
label: item.title,
|
||||||
|
value: item.type,
|
||||||
|
description: item.description,
|
||||||
|
classification: item.classification,
|
||||||
|
nodePack: item.nodePack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const template of templatesArray) {
|
||||||
|
if (filter(template, searchTerm)) {
|
||||||
|
const candidateFields = pendingConnection.handleType === 'source' ? template.inputs : template.outputs;
|
||||||
|
|
||||||
|
for (const field of Object.values(candidateFields)) {
|
||||||
|
const sourceType =
|
||||||
|
pendingConnection.handleType === 'source' ? field.type : pendingConnection.fieldTemplate.type;
|
||||||
|
const targetType =
|
||||||
|
pendingConnection.handleType === 'target' ? field.type : pendingConnection.fieldTemplate.type;
|
||||||
|
|
||||||
|
if (validateConnectionTypes(sourceType, targetType)) {
|
||||||
|
_items.push({
|
||||||
|
label: template.title,
|
||||||
|
value: template.type,
|
||||||
|
description: template.description,
|
||||||
|
classification: template.classification,
|
||||||
|
nodePack: template.nodePack,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _items;
|
||||||
|
}, [pendingConnection, currentImageFilterItem, searchTerm, notesFilterItem, templatesArray]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{items.map((item) => (
|
||||||
|
<CommandItem key={item.value} value={item.value} onSelect={onSelect} asChild>
|
||||||
|
<Flex role="button" flexDir="column" sx={cmdkItemSx} py={1} px={2} borderRadius="base">
|
||||||
|
<Flex alignItems="center" gap={2}>
|
||||||
|
{item.classification === 'beta' && <Icon boxSize={4} color="invokeYellow.300" as={PiHammerBold} />}
|
||||||
|
{item.classification === 'prototype' && <Icon boxSize={4} color="invokeRed.300" as={PiFlaskBold} />}
|
||||||
|
<Text fontWeight="semibold">{item.label}</Text>
|
||||||
|
<Spacer />
|
||||||
|
<Text variant="subtext" fontWeight="semibold">
|
||||||
|
{item.nodePack}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
{item.description && <Text color="base.200">{item.description}</Text>}
|
||||||
|
</Flex>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
NodeCommandList.displayName = 'CommandListItems';
|
@ -1,267 +0,0 @@
|
|||||||
import 'reactflow/dist/style.css';
|
|
||||||
|
|
||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
|
||||||
import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
|
|
||||||
import type { SelectInstance } from 'chakra-react-select';
|
|
||||||
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
|
|
||||||
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
|
|
||||||
import {
|
|
||||||
$cursorPos,
|
|
||||||
$edgePendingUpdate,
|
|
||||||
$isAddNodePopoverOpen,
|
|
||||||
$pendingConnection,
|
|
||||||
$templates,
|
|
||||||
closeAddNodePopover,
|
|
||||||
edgesChanged,
|
|
||||||
nodesChanged,
|
|
||||||
openAddNodePopover,
|
|
||||||
} from 'features/nodes/store/nodesSlice';
|
|
||||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
|
||||||
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
|
|
||||||
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
|
|
||||||
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
|
|
||||||
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
|
|
||||||
import type { AnyNode } from 'features/nodes/types/invocation';
|
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { filter, map, memoize, some } from 'lodash-es';
|
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
|
||||||
import { flushSync } from 'react-dom';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import type { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import type { FilterOptionOption } from 'react-select/dist/declarations/src/filters';
|
|
||||||
import type { EdgeChange, NodeChange } from 'reactflow';
|
|
||||||
|
|
||||||
const createRegex = memoize(
|
|
||||||
(inputValue: string) =>
|
|
||||||
new RegExp(
|
|
||||||
inputValue
|
|
||||||
.trim()
|
|
||||||
.replace(/[-[\]{}()*+!<=:?./\\^$|#,]/g, '')
|
|
||||||
.split(' ')
|
|
||||||
.join('.*'),
|
|
||||||
'gi'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const filterOption = memoize((option: FilterOptionOption<ComboboxOption>, inputValue: string) => {
|
|
||||||
if (!inputValue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const regex = createRegex(inputValue);
|
|
||||||
return (
|
|
||||||
regex.test(option.label) ||
|
|
||||||
regex.test(option.data.description ?? '') ||
|
|
||||||
(option.data.tags ?? []).some((tag) => regex.test(tag))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const AddNodePopover = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const buildInvocation = useBuildNode();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const selectRef = useRef<SelectInstance<ComboboxOption> | null>(null);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const templates = useStore($templates);
|
|
||||||
const pendingConnection = useStore($pendingConnection);
|
|
||||||
const isOpen = useStore($isAddNodePopoverOpen);
|
|
||||||
const store = useAppStore();
|
|
||||||
const isWorkflowsActive = useStore(INTERACTION_SCOPES.workflows.$isActive);
|
|
||||||
|
|
||||||
const filteredTemplates = useMemo(() => {
|
|
||||||
// If we have a connection in progress, we need to filter the node choices
|
|
||||||
const templatesArray = map(templates);
|
|
||||||
if (!pendingConnection) {
|
|
||||||
return templatesArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter(templates, (template) => {
|
|
||||||
const candidateFields = pendingConnection.handleType === 'source' ? template.inputs : template.outputs;
|
|
||||||
return some(candidateFields, (field) => {
|
|
||||||
const sourceType =
|
|
||||||
pendingConnection.handleType === 'source' ? field.type : pendingConnection.fieldTemplate.type;
|
|
||||||
const targetType =
|
|
||||||
pendingConnection.handleType === 'target' ? field.type : pendingConnection.fieldTemplate.type;
|
|
||||||
return validateConnectionTypes(sourceType, targetType);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [templates, pendingConnection]);
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
const _options: ComboboxOption[] = map(filteredTemplates, (template) => {
|
|
||||||
return {
|
|
||||||
label: template.title,
|
|
||||||
value: template.type,
|
|
||||||
description: template.description,
|
|
||||||
tags: template.tags,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//We only want these nodes if we're not filtered
|
|
||||||
if (!pendingConnection) {
|
|
||||||
_options.push({
|
|
||||||
label: t('nodes.currentImage'),
|
|
||||||
value: 'current_image',
|
|
||||||
description: t('nodes.currentImageDescription'),
|
|
||||||
tags: ['progress'],
|
|
||||||
});
|
|
||||||
|
|
||||||
_options.push({
|
|
||||||
label: t('nodes.notes'),
|
|
||||||
value: 'notes',
|
|
||||||
description: t('nodes.notesDescription'),
|
|
||||||
tags: ['notes'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_options.sort((a, b) => a.label.localeCompare(b.label));
|
|
||||||
|
|
||||||
return _options;
|
|
||||||
}, [filteredTemplates, pendingConnection, t]);
|
|
||||||
|
|
||||||
const addNode = useCallback(
|
|
||||||
(nodeType: string): AnyNode | null => {
|
|
||||||
const node = buildInvocation(nodeType);
|
|
||||||
if (!node) {
|
|
||||||
const errorMessage = t('nodes.unknownNode', {
|
|
||||||
nodeType: nodeType,
|
|
||||||
});
|
|
||||||
toast({
|
|
||||||
status: 'error',
|
|
||||||
title: errorMessage,
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find a cozy spot for the node
|
|
||||||
const cursorPos = $cursorPos.get();
|
|
||||||
const { nodes, edges } = selectNodesSlice(store.getState());
|
|
||||||
node.position = findUnoccupiedPosition(nodes, cursorPos?.x ?? node.position.x, cursorPos?.y ?? node.position.y);
|
|
||||||
node.selected = true;
|
|
||||||
|
|
||||||
// Deselect all other nodes and edges
|
|
||||||
const nodeChanges: NodeChange[] = [{ type: 'add', item: node }];
|
|
||||||
const edgeChanges: EdgeChange[] = [];
|
|
||||||
nodes.forEach(({ id, selected }) => {
|
|
||||||
if (selected) {
|
|
||||||
nodeChanges.push({ type: 'select', id, selected: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
edges.forEach(({ id, selected }) => {
|
|
||||||
if (selected) {
|
|
||||||
edgeChanges.push({ type: 'select', id, selected: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Onwards!
|
|
||||||
if (nodeChanges.length > 0) {
|
|
||||||
dispatch(nodesChanged(nodeChanges));
|
|
||||||
}
|
|
||||||
if (edgeChanges.length > 0) {
|
|
||||||
dispatch(edgesChanged(edgeChanges));
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
[buildInvocation, store, dispatch, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChange = useCallback<ComboboxOnChange>(
|
|
||||||
(v) => {
|
|
||||||
if (!v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const node = addNode(v.value);
|
|
||||||
|
|
||||||
// Auto-connect an edge if we just added a node and have a pending connection
|
|
||||||
if (pendingConnection && isInvocationNode(node)) {
|
|
||||||
const edgePendingUpdate = $edgePendingUpdate.get();
|
|
||||||
const { handleType } = pendingConnection;
|
|
||||||
|
|
||||||
const source = handleType === 'source' ? pendingConnection.nodeId : node.id;
|
|
||||||
const sourceHandle = handleType === 'source' ? pendingConnection.handleId : null;
|
|
||||||
const target = handleType === 'target' ? pendingConnection.nodeId : node.id;
|
|
||||||
const targetHandle = handleType === 'target' ? pendingConnection.handleId : null;
|
|
||||||
|
|
||||||
const { nodes, edges } = selectNodesSlice(store.getState());
|
|
||||||
const connection = getFirstValidConnection(
|
|
||||||
source,
|
|
||||||
sourceHandle,
|
|
||||||
target,
|
|
||||||
targetHandle,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
templates,
|
|
||||||
edgePendingUpdate
|
|
||||||
);
|
|
||||||
if (connection) {
|
|
||||||
const newEdge = connectionToEdge(connection);
|
|
||||||
dispatch(edgesChanged([{ type: 'add', item: newEdge }]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAddNodePopover();
|
|
||||||
},
|
|
||||||
[addNode, dispatch, pendingConnection, store, templates]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleHotkeyOpen: HotkeyCallback = useCallback((e) => {
|
|
||||||
if (!$isAddNodePopoverOpen.get()) {
|
|
||||||
e.preventDefault();
|
|
||||||
openAddNodePopover();
|
|
||||||
flushSync(() => {
|
|
||||||
selectRef.current?.inputRef?.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useHotkeys(['shift+a', 'space'], handleHotkeyOpen, { enabled: isWorkflowsActive }, [isWorkflowsActive]);
|
|
||||||
|
|
||||||
const noOptionsMessage = useCallback(() => t('nodes.noMatchingNodes'), [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={closeAddNodePopover}
|
|
||||||
placement="bottom"
|
|
||||||
openDelay={0}
|
|
||||||
closeDelay={0}
|
|
||||||
closeOnBlur={true}
|
|
||||||
returnFocusOnClose={true}
|
|
||||||
initialFocusRef={inputRef}
|
|
||||||
isLazy
|
|
||||||
>
|
|
||||||
<PopoverAnchor>
|
|
||||||
<Flex position="absolute" top="15%" insetInlineStart="50%" pointerEvents="none" />
|
|
||||||
</PopoverAnchor>
|
|
||||||
<PopoverContent
|
|
||||||
p={0}
|
|
||||||
top={-1}
|
|
||||||
shadow="dark-lg"
|
|
||||||
borderColor="invokeBlue.400"
|
|
||||||
borderWidth="2px"
|
|
||||||
borderStyle="solid"
|
|
||||||
>
|
|
||||||
<PopoverBody w="32rem" p={0}>
|
|
||||||
<Combobox
|
|
||||||
menuIsOpen={isOpen}
|
|
||||||
selectRef={selectRef}
|
|
||||||
value={null}
|
|
||||||
placeholder={t('nodes.nodeSearch')}
|
|
||||||
options={options}
|
|
||||||
noOptionsMessage={noOptionsMessage}
|
|
||||||
filterOption={filterOption}
|
|
||||||
onChange={onChange}
|
|
||||||
onMenuClose={closeAddNodePopover}
|
|
||||||
inputRef={inputRef}
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
/>
|
|
||||||
</PopoverBody>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(AddNodePopover);
|
|
@ -8,10 +8,10 @@ import { useSyncExecutionState } from 'features/nodes/hooks/useExecutionState';
|
|||||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
import { useWorkflowWatcher } from 'features/nodes/hooks/useWorkflowWatcher';
|
import { useWorkflowWatcher } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||||
import {
|
import {
|
||||||
|
$addNodeCmdk,
|
||||||
$cursorPos,
|
$cursorPos,
|
||||||
$didUpdateEdge,
|
$didUpdateEdge,
|
||||||
$edgePendingUpdate,
|
$edgePendingUpdate,
|
||||||
$isAddNodePopoverOpen,
|
|
||||||
$lastEdgeUpdateMouseEvent,
|
$lastEdgeUpdateMouseEvent,
|
||||||
$pendingConnection,
|
$pendingConnection,
|
||||||
$viewport,
|
$viewport,
|
||||||
@ -281,7 +281,7 @@ export const Flow = memo(() => {
|
|||||||
const onEscapeHotkey = useCallback(() => {
|
const onEscapeHotkey = useCallback(() => {
|
||||||
if (!$edgePendingUpdate.get()) {
|
if (!$edgePendingUpdate.get()) {
|
||||||
$pendingConnection.set(null);
|
$pendingConnection.set(null);
|
||||||
$isAddNodePopoverOpen.set(false);
|
$addNodeCmdk.set(false);
|
||||||
cancelConnection();
|
cancelConnection();
|
||||||
}
|
}
|
||||||
}, [cancelConnection]);
|
}, [cancelConnection]);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { openAddNodePopover } from 'features/nodes/store/nodesSlice';
|
import { useAddNodeCmdk } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const AddNodeButton = () => {
|
const AddNodeButton = () => {
|
||||||
|
const addNodeCmdk = useAddNodeCmdk();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -12,7 +13,7 @@ const AddNodeButton = () => {
|
|||||||
tooltip={t('nodes.addNodeToolTip')}
|
tooltip={t('nodes.addNodeToolTip')}
|
||||||
aria-label={t('nodes.addNode')}
|
aria-label={t('nodes.addNode')}
|
||||||
icon={<PiPlusBold />}
|
icon={<PiPlusBold />}
|
||||||
onClick={openAddNodePopover}
|
onClick={addNodeCmdk.setTrue}
|
||||||
pointerEvents="auto"
|
pointerEvents="auto"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,9 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { useAppStore } from 'app/store/storeHooks';
|
import { useAppStore } from 'app/store/storeHooks';
|
||||||
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||||
import {
|
import {
|
||||||
|
$addNodeCmdk,
|
||||||
$didUpdateEdge,
|
$didUpdateEdge,
|
||||||
$edgePendingUpdate,
|
$edgePendingUpdate,
|
||||||
$isAddNodePopoverOpen,
|
|
||||||
$pendingConnection,
|
$pendingConnection,
|
||||||
$templates,
|
$templates,
|
||||||
edgesChanged,
|
edgesChanged,
|
||||||
@ -107,7 +107,7 @@ export const useConnection = () => {
|
|||||||
$pendingConnection.set(null);
|
$pendingConnection.set(null);
|
||||||
} else {
|
} else {
|
||||||
// The mouse is not over a node - we should open the add node popover
|
// The mouse is not over a node - we should open the add node popover
|
||||||
$isAddNodePopoverOpen.set(true);
|
$addNodeCmdk.set(true);
|
||||||
}
|
}
|
||||||
}, [store, templates, updateNodeInternals]);
|
}, [store, templates, updateNodeInternals]);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
|
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig } from 'app/store/store';
|
import type { PersistConfig } from 'app/store/store';
|
||||||
|
import { buildUseBoolean } from 'common/hooks/useBoolean';
|
||||||
import { workflowLoaded } from 'features/nodes/store/actions';
|
import { workflowLoaded } from 'features/nodes/store/actions';
|
||||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||||
import type {
|
import type {
|
||||||
@ -443,14 +444,8 @@ export const $didUpdateEdge = atom(false);
|
|||||||
export const $lastEdgeUpdateMouseEvent = atom<MouseEvent | null>(null);
|
export const $lastEdgeUpdateMouseEvent = atom<MouseEvent | null>(null);
|
||||||
|
|
||||||
export const $viewport = atom<Viewport>({ x: 0, y: 0, zoom: 1 });
|
export const $viewport = atom<Viewport>({ x: 0, y: 0, zoom: 1 });
|
||||||
export const $isAddNodePopoverOpen = atom(false);
|
export const $addNodeCmdk = atom(false);
|
||||||
export const closeAddNodePopover = () => {
|
export const useAddNodeCmdk = buildUseBoolean($addNodeCmdk);
|
||||||
$isAddNodePopoverOpen.set(false);
|
|
||||||
$pendingConnection.set(null);
|
|
||||||
};
|
|
||||||
export const openAddNodePopover = () => {
|
|
||||||
$isAddNodePopoverOpen.set(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
const migrateNodesState = (state: any): any => {
|
const migrateNodesState = (state: any): any => {
|
||||||
|
Loading…
Reference in New Issue
Block a user