feat(ui): upgrade to reactflow 12

This commit is contained in:
psychedelicious 2024-08-10 20:40:34 +10:00
parent 09d1e190e7
commit 3dfa8ce11f
46 changed files with 159 additions and 407 deletions

View File

@ -299,7 +299,7 @@ Migration logic is in [migrations.ts].
[pydantic]: https://github.com/pydantic/pydantic 'pydantic'
[zod]: https://github.com/colinhacks/zod 'zod'
[openapi-types]: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-types 'openapi-types'
[reactflow]: https://github.com/xyflow/xyflow 'reactflow'
[reactflow]: https://github.com/xyflow/xyflow '@xyflow/react'
[reactflow-concepts]: https://reactflow.dev/learn/concepts/terms-and-definitions
[reactflow-events]: https://reactflow.dev/api-reference/react-flow#event-handlers
[buildWorkflow.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts

View File

@ -63,6 +63,7 @@
"@nanostores/react": "^0.7.2",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
"@xyflow/react": "^12.0.4",
"chakra-react-select": "^4.7.6",
"compare-versions": "^6.1.0",
"dateformat": "^5.0.3",
@ -94,7 +95,6 @@
"react-select": "5.8.0",
"react-use": "^17.5.0",
"react-virtuoso": "^4.7.10",
"reactflow": "^11.11.3",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0",
"redux-undo": "^1.1.0",

View File

@ -41,6 +41,9 @@ dependencies:
'@roarr/browser-log-writer':
specifier: ^1.3.0
version: 1.3.0
'@xyflow/react':
specifier: ^12.0.4
version: 12.0.4(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
chakra-react-select:
specifier: ^4.7.6
version: 4.7.6(@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.11.4)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
@ -134,9 +137,6 @@ dependencies:
react-virtuoso:
specifier: ^4.7.10
version: 4.7.10(react-dom@18.3.1)(react@18.3.1)
reactflow:
specifier: ^11.11.3
version: 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
redux-dynamic-middlewares:
specifier: ^2.2.0
version: 2.2.0
@ -3874,114 +3874,6 @@ packages:
react: 18.3.1
dev: true
/@reactflow/background@11.3.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-hkvpVEhgvfTDyCvdlitw4ioKCYLaaiRXnuEG+1QM3Np+7N1DiWF1XOv5I8AFyNoJL07yXEkbECUTsHvkBvcG5A==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
classcat: 5.0.5
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/controls@11.2.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-3xgEg6ALIVkAQCS4NiBjb7ad8Cb3D8CtA7Vvl4Hf5Ar2PIVs6FOaeft9s2iDZGtsWP35ECDYId1rIFVhQL8r+A==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
classcat: 5.0.5
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/core@11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-+adHdUa7fJSEM93fWfjQwyWXeI92a1eLKwWbIstoCakHpL8UjzwhEh6sn+mN2h/59MlVI7Ehr1iGTt3MsfcIFA==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@types/d3': 7.4.3
'@types/d3-drag': 3.0.7
'@types/d3-selection': 3.0.10
'@types/d3-zoom': 3.0.8
classcat: 5.0.5
d3-drag: 3.0.0
d3-selection: 3.0.0
d3-zoom: 3.0.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/minimap@11.7.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-m2MvdiGSyOu44LEcERDEl1Aj6x//UQRWo3HEAejNU4HQTlJnYrSN8tgrYF8TxC1+c/9UdyzQY5VYgrTwW4QWdg==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@types/d3-selection': 3.0.10
'@types/d3-zoom': 3.0.8
classcat: 5.0.5
d3-selection: 3.0.0
d3-zoom: 3.0.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/node-resizer@2.2.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-X7ceQ2s3jFLgbkg03n2RYr4hm3jTVrzkW2W/8ANv/SZfuVmF8XJxlERuD8Eka5voKqLda0ywIZGAbw9GoHLfUQ==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
classcat: 5.0.5
d3-drag: 3.0.0
d3-selection: 3.0.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/node-toolbar@1.3.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-aknvNICO10uWdthFSpgD6ctY/CTBeJUMV9co8T9Ilugr08Nb89IQ4uD0dPmr031ewMQxixtYIkw+sSDDzd2aaQ==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
classcat: 5.0.5
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reduxjs/toolkit@2.2.3(react-redux@9.1.2)(react@18.3.1):
resolution: {integrity: sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==}
peerDependencies:
@ -5233,137 +5125,26 @@ packages:
'@types/node': 20.12.10
dev: true
/@types/d3-array@3.2.1:
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
dev: false
/@types/d3-axis@3.0.6:
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-brush@3.0.6:
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-chord@3.0.6:
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
dev: false
/@types/d3-color@3.1.3:
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
dev: false
/@types/d3-contour@3.0.6:
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
dependencies:
'@types/d3-array': 3.2.1
'@types/geojson': 7946.0.14
dev: false
/@types/d3-delaunay@6.0.4:
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
dev: false
/@types/d3-dispatch@3.0.6:
resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
dev: false
/@types/d3-drag@3.0.7:
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-dsv@3.0.7:
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
dev: false
/@types/d3-ease@3.0.2:
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
dev: false
/@types/d3-fetch@3.0.7:
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
dependencies:
'@types/d3-dsv': 3.0.7
dev: false
/@types/d3-force@3.0.9:
resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==}
dev: false
/@types/d3-format@3.0.4:
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
dev: false
/@types/d3-geo@3.1.0:
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
dependencies:
'@types/geojson': 7946.0.14
dev: false
/@types/d3-hierarchy@3.1.7:
resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
dev: false
/@types/d3-interpolate@3.0.4:
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
dependencies:
'@types/d3-color': 3.1.3
dev: false
/@types/d3-path@3.1.0:
resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==}
dev: false
/@types/d3-polygon@3.0.2:
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
dev: false
/@types/d3-quadtree@3.0.6:
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
dev: false
/@types/d3-random@3.0.3:
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
dev: false
/@types/d3-scale-chromatic@3.0.3:
resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
dev: false
/@types/d3-scale@4.0.8:
resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
dependencies:
'@types/d3-time': 3.0.3
dev: false
/@types/d3-selection@3.0.10:
resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==}
dev: false
/@types/d3-shape@3.1.6:
resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
dependencies:
'@types/d3-path': 3.1.0
dev: false
/@types/d3-time-format@4.0.3:
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
dev: false
/@types/d3-time@3.0.3:
resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
dev: false
/@types/d3-timer@3.0.2:
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
dev: false
/@types/d3-transition@3.0.8:
resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==}
dependencies:
@ -5377,41 +5158,6 @@ packages:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3@7.4.3:
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
dependencies:
'@types/d3-array': 3.2.1
'@types/d3-axis': 3.0.6
'@types/d3-brush': 3.0.6
'@types/d3-chord': 3.0.6
'@types/d3-color': 3.1.3
'@types/d3-contour': 3.0.6
'@types/d3-delaunay': 6.0.4
'@types/d3-dispatch': 3.0.6
'@types/d3-drag': 3.0.7
'@types/d3-dsv': 3.0.7
'@types/d3-ease': 3.0.2
'@types/d3-fetch': 3.0.7
'@types/d3-force': 3.0.9
'@types/d3-format': 3.0.4
'@types/d3-geo': 3.1.0
'@types/d3-hierarchy': 3.1.7
'@types/d3-interpolate': 3.0.4
'@types/d3-path': 3.1.0
'@types/d3-polygon': 3.0.2
'@types/d3-quadtree': 3.0.6
'@types/d3-random': 3.0.3
'@types/d3-scale': 4.0.8
'@types/d3-scale-chromatic': 3.0.3
'@types/d3-selection': 3.0.10
'@types/d3-shape': 3.1.6
'@types/d3-time': 3.0.3
'@types/d3-time-format': 4.0.3
'@types/d3-timer': 3.0.2
'@types/d3-transition': 3.0.8
'@types/d3-zoom': 3.0.8
dev: false
/@types/dateformat@5.0.2:
resolution: {integrity: sha512-M95hNBMa/hnwErH+a+VOD/sYgTmo15OTYTM2Hr52/e0OdOuY+Crag+kd3/ioZrhg0WGbl9Sm3hR7UU+MH6rfOw==}
dev: true
@ -5481,10 +5227,6 @@ packages:
resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==}
dev: true
/@types/geojson@7946.0.14:
resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
dev: false
/@types/glob@7.2.0:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
@ -6008,6 +5750,34 @@ packages:
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
dev: false
/@xyflow/react@12.0.4(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-eeQzw1gIbLKOB55rp2+20uB1PASDUf1q6zy2VsgugnuPEcL/olVMX3WT42XxyG8m3rcbUiHlq2NSmPTFWEjiUQ==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@xyflow/system': 0.0.37
classcat: 5.0.5
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
zustand: 4.5.2(@types/react@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@xyflow/system@0.0.37:
resolution: {integrity: sha512-hSIhezhxgftPUpC+xiQVIorcRILZUOWlLjpYPTyGWRu8s4RJvM4GqvrsFmD5OnMKXLgpU7/PqqUibDVO67oWQQ==}
dependencies:
'@types/d3-drag': 3.0.7
'@types/d3-selection': 3.0.10
'@types/d3-transition': 3.0.8
'@types/d3-zoom': 3.0.8
d3-drag: 3.0.0
d3-selection: 3.0.0
d3-zoom: 3.0.0
dev: false
/@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.20.2):
resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
engines: {node: '>=14.15.0'}
@ -11318,25 +11088,6 @@ packages:
dependencies:
loose-envify: 1.4.0
/reactflow@11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-wusd1Xpn1wgsSEv7UIa4NNraCwH9syBtubBy4xVNXg3b+CDKM+sFaF3hnMx0tr0et4km9urIDdNvwm34QiZong==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/background': 11.3.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/controls': 11.2.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/core': 11.11.3(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/minimap': 11.7.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/node-resizer': 2.2.13(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/node-toolbar': 1.3.13(@types/react@18.3.1)(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'
- immer
dev: false
/read-pkg-up@7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}

View File

@ -1,4 +1,5 @@
import { useStore } from '@nanostores/react';
import { getConnectedEdges } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
@ -22,7 +23,6 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import i18n from 'i18next';
import { forEach, upperFirst } from 'lodash-es';
import { useMemo } from 'react';
import { getConnectedEdges } from 'reactflow';
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
initial_image_layer: 'controlLayers.globalInitialImage',

View File

@ -1,4 +1,4 @@
import 'reactflow/dist/style.css';
import '@xyflow/react/dist/style.css';
import { Flex } from '@invoke-ai/ui-library';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';

View File

@ -1,8 +1,7 @@
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 type { EdgeChange, NodeChange } from '@xyflow/react';
import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
import type { SelectInstance } from 'chakra-react-select';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
@ -21,7 +20,7 @@ import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupied
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 type { AppNode } 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';
@ -31,7 +30,6 @@ 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) =>
@ -120,7 +118,7 @@ const AddNodePopover = () => {
}, [filteredTemplates, pendingConnection, t]);
const addNode = useCallback(
(nodeType: string): AnyNode | null => {
(nodeType: string): AppNode | null => {
const node = buildInvocation(nodeType);
if (!node) {
const errorMessage = t('nodes.unknownNode', {

View File

@ -1,5 +1,20 @@
import { useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type {
EdgeChange,
EdgeTypes,
NodeChange,
NodeTypes,
OnEdgesChange,
OnInit,
OnMoveEnd,
OnNodesChange,
OnReconnect,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from '@xyflow/react';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { useConnection } from 'features/nodes/hooks/useConnection';
import { useCopyPaste } from 'features/nodes/hooks/useCopyPaste';
@ -24,36 +39,23 @@ import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import type { CSSProperties, MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import type {
EdgeChange,
NodeChange,
OnEdgesChange,
OnEdgeUpdateFunc,
OnInit,
OnMoveEnd,
OnNodesChange,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from 'reactflow';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from 'reactflow';
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
import InvocationDefaultEdge from './edges/InvocationDefaultEdge';
import CurrentImageNode from './nodes/CurrentImage/CurrentImageNode';
import InvocationNodeWrapper from './nodes/Invocation/InvocationNodeWrapper';
import NotesNode from './nodes/Notes/NotesNode';
import { NotesNodeComponent } from './nodes/Notes/NotesNode';
const edgeTypes = {
const edgeTypes: EdgeTypes = {
collapsed: InvocationCollapsedEdge,
default: InvocationDefaultEdge,
};
const nodeTypes = {
const nodeTypes: NodeTypes = {
invocation: InvocationNodeWrapper,
current_image: CurrentImageNode,
notes: NotesNode,
notes: NotesNodeComponent,
};
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
@ -151,13 +153,13 @@ export const Flow = memo(() => {
* where the edge is deleted if you click it accidentally).
*/
const onEdgeUpdateStart: NonNullable<ReactFlowProps['onEdgeUpdateStart']> = useCallback((e, edge, _handleType) => {
const onReconnectStart: NonNullable<ReactFlowProps['onReconnectStart']> = useCallback((e, edge, _handleType) => {
$edgePendingUpdate.set(edge);
$didUpdateEdge.set(false);
$lastEdgeUpdateMouseEvent.set(e);
}, []);
const onEdgeUpdate: OnEdgeUpdateFunc = useCallback(
const onReconnect: OnReconnect = useCallback(
(oldEdge, newConnection) => {
// This event is fired when an edge update is successful
$didUpdateEdge.set(true);
@ -176,7 +178,7 @@ export const Flow = memo(() => {
[dispatch, updateNodeInternals]
);
const onEdgeUpdateEnd: NonNullable<ReactFlowProps['onEdgeUpdateEnd']> = useCallback(
const onReconnectEnd: NonNullable<ReactFlowProps['onReconnectEnd']> = useCallback(
(e, edge, _handleType) => {
const didUpdateEdge = $didUpdateEdge.get();
// Fall back to a reasonable default event
@ -316,9 +318,9 @@ export const Flow = memo(() => {
onMouseMove={onMouseMove}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}
onReconnectEnd={onReconnectEnd}
onConnectStart={onConnectStart}
onConnect={onConnect}
onConnectEnd={onConnectEnd}

View File

@ -1,12 +1,12 @@
import { useStore } from '@nanostores/react';
import type { ConnectionLineComponentProps } from '@xyflow/react';
import { getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $pendingConnection } from 'features/nodes/store/nodesSlice';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import type { ConnectionLineComponentProps } from 'reactflow';
import { getBezierPath } from 'reactflow';
const pathStyles: CSSProperties = { opacity: 0.8 };

View File

@ -1,13 +1,15 @@
import { Badge, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { Edge, EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { makeEdgeSelector } from 'features/nodes/components/flow/edges/util/makeEdgeSelector';
import { $templates } from 'features/nodes/store/nodesSlice';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
export type CollapsedEdge = Edge<{ count: number }>;
const InvocationCollapsedEdge = ({
sourceX,
@ -23,7 +25,7 @@ const InvocationCollapsedEdge = ({
sourceHandleId,
target,
targetHandleId,
}: EdgeProps<{ count: number }>) => {
}: EdgeProps<CollapsedEdge>) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),

View File

@ -1,11 +1,11 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $templates } from 'features/nodes/store/nodesSlice';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
import { makeEdgeSelector } from './util/makeEdgeSelector';

View File

@ -1,4 +1,5 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import type { NodeProps } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
@ -13,7 +14,6 @@ import { motion } from 'framer-motion';
import type { CSSProperties, PropsWithChildren } from 'react';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { NodeProps } from 'reactflow';
const selector = createMemoizedSelector(selectSystemSlice, selectGallerySlice, (system, gallery) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];

View File

@ -1,9 +1,9 @@
import { Handle, Position } from '@xyflow/react';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { map } from 'lodash-es';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { Handle, Position } from 'reactflow';
interface Props {
nodeId: string;

View File

@ -16,10 +16,10 @@ type Props = {
isOpen: boolean;
label: string;
type: string;
selected: boolean;
selected?: boolean;
};
const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
export const InvocationNodeComponent = memo(({ nodeId, isOpen, label, type, selected = false }: Props) => {
const fieldNames = useFieldNames(nodeId);
const withFooter = useWithFooter(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);
@ -78,6 +78,6 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
)}
</NodeWrapper>
);
};
});
export default memo(InvocationNode);
InvocationNodeComponent.displayName = 'InvocationNodeComponent';

View File

@ -11,10 +11,10 @@ type Props = {
isOpen: boolean;
label: string;
type: string;
selected: boolean;
selected?: boolean;
};
const InvocationNodeUnknownFallback = ({ nodeId, isOpen, label, type, selected }: Props) => {
const InvocationNodeUnknownFallback = ({ nodeId, isOpen, label, type, selected = false }: Props) => {
const { t } = useTranslation();
const nodePack = useNodePack(nodeId);
return (

View File

@ -1,14 +1,14 @@
import { useStore } from '@nanostores/react';
import type { NodeProps } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { InvocationNodeComponent } from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeComponent';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
import type { InvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { NodeProps } from 'reactflow';
import InvocationNodeUnknownFallback from './InvocationNodeUnknownFallback';
const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const InvocationNodeWrapper = (props: NodeProps<InvocationNode>) => {
const { data, selected } = props;
const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates);
@ -25,7 +25,7 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
);
}
return <InvocationNode nodeId={nodeId} isOpen={isOpen} label={label} type={type} selected={selected} />;
return <InvocationNodeComponent nodeId={nodeId} isOpen={isOpen} label={label} type={type} selected={selected} />;
};
export default memo(InvocationNodeWrapper);

View File

@ -1,4 +1,6 @@
import { Tooltip } from '@invoke-ai/ui-library';
import type { HandleType } from '@xyflow/react';
import { Handle, Position } from '@xyflow/react';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
@ -8,8 +10,6 @@ import { type FieldInputTemplate, type FieldOutputTemplate, isSingle } from 'fea
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { HandleType } from 'reactflow';
import { Handle, Position } from 'reactflow';
type FieldHandleProps = {
fieldTemplate: FieldInputTemplate | FieldOutputTemplate;

View File

@ -1,15 +1,15 @@
import { Box, Flex, Textarea } from '@invoke-ai/ui-library';
import type { NodeProps } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
import type { NotesNodeData } from 'features/nodes/types/invocation';
import type { NotesNode } from 'features/nodes/types/invocation';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import type { NodeProps } from 'reactflow';
const NotesNode = (props: NodeProps<NotesNodeData>) => {
export const NotesNodeComponent = memo((props: NodeProps<NotesNode>) => {
const { id: nodeId, data, selected } = props;
const { notes, isOpen } = data;
const dispatch = useAppDispatch();
@ -55,6 +55,6 @@ const NotesNode = (props: NodeProps<NotesNodeData>) => {
)}
</NodeWrapper>
);
};
});
export default memo(NotesNode);
NotesNodeComponent.displayName = 'NotesNodeComponent';

View File

@ -1,9 +1,9 @@
import { Icon, IconButton } from '@invoke-ai/ui-library';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { nodeIsOpenChanged } from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react';
import { PiCaretUpBold } from 'react-icons/pi';
import { useUpdateNodeInternals } from 'reactflow';
interface Props {
nodeId: string;

View File

@ -1,5 +1,6 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import type { NodeChange } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
@ -9,16 +10,15 @@ import { DRAG_HANDLE_CLASSNAME, NODE_WIDTH } from 'features/nodes/types/constant
import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';
import type { NodeChange } from 'reactflow';
type NodeWrapperProps = PropsWithChildren & {
nodeId: string;
selected: boolean;
selected?: boolean;
width?: ChakraProps['w'];
};
const NodeWrapper = (props: NodeWrapperProps) => {
const { nodeId, width, children, selected } = props;
const { nodeId, width, children, selected = false } = props;
const store = useAppStore();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId);

View File

@ -1,4 +1,5 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useReactFlow } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldShowMinimapPanelChanged } from 'features/nodes/store/workflowSettingsSlice';
import { memo, useCallback } from 'react';
@ -9,7 +10,6 @@ import {
PiMagnifyingGlassPlusBold,
PiMapPinBold,
} from 'react-icons/pi';
import { useReactFlow } from 'reactflow';
const ViewportControls = () => {
const { t } = useTranslation();

View File

@ -1,8 +1,8 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { chakra, Flex } from '@invoke-ai/ui-library';
import { MiniMap } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react';
import { MiniMap } from 'reactflow';
const ChakraMiniMap = chakra(MiniMap);

View File

@ -16,6 +16,7 @@ import {
Switch,
useDisclosure,
} from '@invoke-ai/ui-library';
import { SelectionMode } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
@ -31,7 +32,6 @@ import {
import type { ChangeEvent, ReactNode } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { SelectionMode } from 'reactflow';
const formLabelProps: FormLabelProps = { flexGrow: 1 };

View File

@ -1,5 +1,3 @@
import 'reactflow/dist/style.css';
import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import QueueControls from 'features/queue/components/QueueControls';

View File

@ -1,12 +1,12 @@
import { useStore } from '@nanostores/react';
import { useReactFlow } from '@xyflow/react';
import { $templates } from 'features/nodes/store/nodesSlice';
import { NODE_WIDTH } from 'features/nodes/types/constants';
import type { AnyNode, InvocationTemplate } from 'features/nodes/types/invocation';
import type { AppNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildCurrentImageNode } from 'features/nodes/util/node/buildCurrentImageNode';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import { buildNotesNode } from 'features/nodes/util/node/buildNotesNode';
import { useCallback } from 'react';
import { useReactFlow } from 'reactflow';
export const useBuildNode = () => {
const templates = useStore($templates);
@ -14,7 +14,7 @@ export const useBuildNode = () => {
return useCallback(
// string here is "any invocation type"
(type: string | 'current_image' | 'notes'): AnyNode => {
(type: string | 'current_image' | 'notes'): AppNode => {
let _x = window.innerWidth / 2;
let _y = window.innerHeight / 2;

View File

@ -1,4 +1,6 @@
import { useStore } from '@nanostores/react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from '@xyflow/react';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppStore } from 'app/store/storeHooks';
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import {
@ -12,8 +14,6 @@ import {
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { useCallback, useMemo } from 'react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from 'reactflow';
import { useUpdateNodeInternals } from 'reactflow';
import { assert } from 'tsafe';
export const useConnection = () => {

View File

@ -1,3 +1,4 @@
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { getStore } from 'app/store/nanostores/store';
import { deepClone } from 'common/util/deepClone';
import {
@ -11,7 +12,6 @@ import {
} from 'features/nodes/store/nodesSlice';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { isEqual, uniqWith } from 'lodash-es';
import type { EdgeChange, NodeChange } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
const copySelection = () => {

View File

@ -1,10 +1,10 @@
// TODO: enable this at some point
import { useStore } from '@nanostores/react';
import type { IsValidConnection } from '@xyflow/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import { useCallback } from 'react';
import type { Connection } from 'reactflow';
/**
* NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts`
@ -15,8 +15,8 @@ export const useIsValidConnection = () => {
const store = useAppStore();
const templates = useStore($templates);
const shouldValidateGraph = useAppSelector((s) => s.workflowSettings.shouldValidateGraph);
const isValidConnection = useCallback(
({ source, sourceHandle, target, targetHandle }: Connection): boolean => {
const isValidConnection = useCallback<IsValidConnection>(
({ source, sourceHandle, target, targetHandle }) => {
// Connection must have valid targets
if (!(source && sourceHandle && target && targetHandle)) {
return false;

View File

@ -1,6 +1,9 @@
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { Edge, EdgeChange, NodeChange, Viewport, XYPosition } from '@xyflow/react';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from '@xyflow/react';
import type { PersistConfig, RootState } from 'app/store/store';
import type { CollapsedEdge } from 'features/nodes/components/flow/edges/InvocationCollapsedEdge';
import { workflowLoaded } from 'features/nodes/store/actions';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type {
@ -46,12 +49,10 @@ import {
zT2IAdapterModelFieldValue,
zVAEModelFieldValue,
} from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import { isInvocationNode, isNotesNode } from 'features/nodes/types/invocation';
import { atom } from 'nanostores';
import type { MouseEvent } from 'react';
import type { Edge, EdgeChange, NodeChange, Viewport, XYPosition } from 'reactflow';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from 'reactflow';
import type { UndoableOptions } from 'redux-undo';
import type { z } from 'zod';
@ -92,10 +93,10 @@ export const nodesSlice = createSlice({
name: 'nodes',
initialState: initialNodesState,
reducers: {
nodesChanged: (state, action: PayloadAction<NodeChange[]>) => {
state.nodes = applyNodeChanges(action.payload, state.nodes);
nodesChanged: (state, action: PayloadAction<NodeChange<AppNode>[]>) => {
state.nodes = applyNodeChanges<AppNode>(action.payload, state.nodes);
// Remove edges that are no longer valid, due to a removed or otherwise changed node
const edgeChanges: EdgeChange[] = [];
const edgeChanges: EdgeChange<InvocationNodeEdge | CollapsedEdge>[] = [];
state.edges.forEach((e) => {
const sourceExists = state.nodes.some((n) => n.id === e.source);
const targetExists = state.nodes.some((n) => n.id === e.target);
@ -103,10 +104,10 @@ export const nodesSlice = createSlice({
edgeChanges.push({ type: 'remove', id: e.id });
}
});
state.edges = applyEdgeChanges(edgeChanges, state.edges);
state.edges = applyEdgeChanges<InvocationNodeEdge | CollapsedEdge>(edgeChanges, state.edges);
},
edgesChanged: (state, action: PayloadAction<EdgeChange[]>) => {
const changes: EdgeChange[] = [];
edgesChanged: (state, action: PayloadAction<EdgeChange<InvocationNodeEdge | CollapsedEdge>[]>) => {
const changes: EdgeChange<InvocationNodeEdge | CollapsedEdge>[] = [];
// We may need to massage the edge changes or otherwise handle them
action.payload.forEach((change) => {
if (change.type === 'remove' || change.type === 'select') {
@ -134,7 +135,7 @@ export const nodesSlice = createSlice({
}
changes.push(change);
});
state.edges = applyEdgeChanges(changes, state.edges);
state.edges = applyEdgeChanges<InvocationNodeEdge | CollapsedEdge>(changes, state.edges);
},
fieldLabelChanged: (
state,
@ -218,7 +219,7 @@ export const nodesSlice = createSlice({
(node) => isInvocationNode(node) && node.data.isOpen === false
);
const collapsedEdgesToCreate: Edge<{ count: number }>[] = [];
const collapsedEdgesToCreate: CollapsedEdge[] = [];
// hide all edges
connectedEdges.forEach((edge) => {
@ -238,7 +239,7 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
updatable: false,
reconnectable: false,
selected: edge.selected,
});
}
@ -259,14 +260,14 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
updatable: false,
reconnectable: false,
selected: edge.selected,
});
}
}
});
if (collapsedEdgesToCreate.length) {
state.edges = applyEdgeChanges(
state.edges = applyEdgeChanges<CollapsedEdge | InvocationNodeEdge>(
collapsedEdgesToCreate.map((edge) => ({ type: 'add', item: edge })),
state.edges
);
@ -366,7 +367,7 @@ export const nodesSlice = createSlice({
extraReducers: (builder) => {
builder.addCase(workflowLoaded, (state, action) => {
const { nodes, edges } = action.payload;
state.nodes = applyNodeChanges(
state.nodes = applyNodeChanges<AppNode>(
nodes.map((node) => ({
type: 'add',
item: { ...node, ...SHARED_NODE_PROPERTIES },
@ -416,7 +417,7 @@ export const {
export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({});
export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedNodes = atom<AppNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $edgesToCopiedNodes = atom<InvocationNodeEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null);

View File

@ -1,5 +1,5 @@
import type { ReactFlowInstance } from '@xyflow/react';
import { atom } from 'nanostores';
import type { ReactFlowInstance } from 'reactflow';
export const $flow = atom<ReactFlowInstance | null>(null);
export const $needsFit = atom<boolean>(true);

View File

@ -1,3 +1,5 @@
import type { HandleType } from '@xyflow/react';
import type { CollapsedEdge } from 'features/nodes/components/flow/edges/InvocationCollapsedEdge';
import type {
FieldIdentifier,
FieldInputTemplate,
@ -5,13 +7,12 @@ import type {
StatefulFieldValue,
} from 'features/nodes/types/field';
import type {
AnyNode,
AppNode,
InvocationNodeEdge,
InvocationTemplate,
NodeExecutionState,
} from 'features/nodes/types/invocation';
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { HandleType } from 'reactflow';
export type Templates = Record<string, InvocationTemplate>;
export type NodeExecutionStates = Record<string, NodeExecutionState | undefined>;
@ -25,8 +26,8 @@ export type PendingConnection = {
export type NodesState = {
_version: 1;
nodes: AnyNode[];
edges: InvocationNodeEdge[];
nodes: AppNode[];
edges: (InvocationNodeEdge | CollapsedEdge)[];
};
export type WorkflowMode = 'edit' | 'view';

View File

@ -1,4 +1,4 @@
import type { Node } from 'reactflow';
import type { Node } from '@xyflow/react';
export const findUnoccupiedPosition = (nodes: Node[], x: number, y: number) => {
let newX = x;

View File

@ -1,6 +1,6 @@
import type { Templates } from 'features/nodes/store/types';
import type { FieldType } from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
/**
* Given a collect node, return the type of the items it collects. The graph is traversed to find the first node and
@ -14,7 +14,7 @@ import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocatio
*/
export const getCollectItemType = (
templates: Templates,
nodes: AnyNode[],
nodes: AppNode[],
edges: InvocationNodeEdge[],
nodeId: string
): FieldType | null => {

View File

@ -1,9 +1,9 @@
import type { Connection, Edge } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import { map } from 'lodash-es';
import type { Connection, Edge } from 'reactflow';
/**
*
@ -22,7 +22,7 @@ export const getFirstValidConnection = (
sourceHandle: string | null,
target: string,
targetHandle: string | null,
nodes: AnyNode[],
nodes: AppNode[],
edges: InvocationNodeEdge[],
templates: Templates,
edgePendingUpdate: Edge | null
@ -80,7 +80,7 @@ export const getTargetCandidateFields = (
source: string,
sourceHandle: string,
target: string,
nodes: AnyNode[],
nodes: AppNode[],
edges: Edge[],
templates: Templates,
edgePendingUpdate: Edge | null
@ -116,7 +116,7 @@ export const getSourceCandidateFields = (
target: string,
targetHandle: string,
source: string,
nodes: AnyNode[],
nodes: AppNode[],
edges: Edge[],
templates: Templates,
edgePendingUpdate: Edge | null

View File

@ -1,5 +1,5 @@
import graphlib from '@dagrejs/graphlib';
import type { Edge, Node } from 'reactflow';
import type { Edge, Node } from '@xyflow/react';
/**
* Check if adding an edge between the source and target nodes would create a cycle in the graph.

View File

@ -1,9 +1,9 @@
import type { Edge, HandleType } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types';
import { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection';
import type { Edge, HandleType } from 'reactflow';
/**
* Creates a selector that validates a pending connection.

View File

@ -1,4 +1,4 @@
import type { Connection, Edge } from 'reactflow';
import type { Connection, Edge } from '@xyflow/react';
import { assert } from 'tsafe';
/**

View File

@ -1,10 +1,9 @@
import type { Templates } from 'features/nodes/store/types';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import type { InvocationNodeEdge, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import type { OpenAPIV3_1 } from 'openapi-types';
import type { Edge } from 'reactflow';
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): Edge => ({
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): InvocationNodeEdge => ({
source,
sourceHandle,
target,

View File

@ -1,10 +1,10 @@
import type { Connection as NullableConnection, Edge } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { areTypesEqual } from 'features/nodes/store/util/areTypesEqual';
import { getCollectItemType } from 'features/nodes/store/util/getCollectItemType';
import { getHasCycles } from 'features/nodes/store/util/getHasCycles';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AnyNode } from 'features/nodes/types/invocation';
import type { Connection as NullableConnection, Edge } from 'reactflow';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { O } from 'ts-toolbelt';
type Connection = O.NonNullable<NullableConnection>;
@ -21,8 +21,8 @@ export type ValidationResult =
type ValidateConnectionFunc = (
connection: Connection,
nodes: AnyNode[],
edges: Edge[],
nodes: AppNode[],
edges: InvocationNodeEdge[],
templates: Templates,
ignoreEdge: Edge | null,
strict?: boolean

View File

@ -1,7 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { SelectionMode } from '@xyflow/react';
import type { PersistConfig, RootState } from 'app/store/store';
import { SelectionMode } from 'reactflow';
type WorkflowSettingsState = {
_version: 1;

View File

@ -1,4 +1,4 @@
import type { Node } from 'reactflow';
import type { Node } from '@xyflow/react';
/**
* How long to wait before showing a tooltip when hovering a field handle.

View File

@ -1,4 +1,4 @@
import type { Edge, Node } from 'reactflow';
import type { Edge, Node } from '@xyflow/react';
import { z } from 'zod';
import { zClassification, zProgressImage } from './common';
@ -59,11 +59,11 @@ type AnyNodeData = z.infer<typeof zAnyNodeData>;
export type InvocationNode = Node<InvocationNodeData, 'invocation'>;
export type NotesNode = Node<NotesNodeData, 'notes'>;
export type CurrentImageNode = Node<CurrentImageNodeData, 'current_image'>;
export type AnyNode = Node<AnyNodeData>;
export type AppNode = Node<AnyNodeData>;
export const isInvocationNode = (node?: AnyNode | null): node is InvocationNode =>
export const isInvocationNode = (node?: AppNode | null): node is InvocationNode =>
Boolean(node && node.type === 'invocation');
export const isNotesNode = (node?: AnyNode | null): node is NotesNode => Boolean(node && node.type === 'notes');
export const isNotesNode = (node?: AppNode | null): node is NotesNode => Boolean(node && node.type === 'notes');
export const isInvocationNodeData = (node?: AnyNodeData | null): node is InvocationNodeData =>
Boolean(node && !['notes', 'current_image'].includes(node.type)); // node.type may be 'notes', 'current_image', or any invocation type
// #endregion

View File

@ -1,5 +1,5 @@
import type * as ReactFlow from '@xyflow/react';
import type { WorkflowCategory, WorkflowV3, XYPosition } from 'features/nodes/types/workflow';
import type * as ReactFlow from 'reactflow';
import type { S } from 'services/api/types';
import type { Equals, Extends } from 'tsafe';
import { assert } from 'tsafe';

View File

@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { CurrentImageNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildCurrentImageNode = (position: XYPosition): CurrentImageNode => {

View File

@ -1,9 +1,9 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildFieldInputInstance } from 'features/nodes/util/schema/buildFieldInputInstance';
import { reduce } from 'lodash-es';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildInvocationNode = (position: XYPosition, template: InvocationTemplate): InvocationNode => {

View File

@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { NotesNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildNotesNode = (position: XYPosition): NotesNode => {

View File

@ -1,10 +1,10 @@
import { Box } from '@invoke-ai/ui-library';
import { ReactFlowProvider } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditor from 'features/nodes/components/NodeEditor';
import { memo } from 'react';
import { ReactFlowProvider } from 'reactflow';
const NodesTab = () => {
const mode = useAppSelector((s) => s.workflow.mode);