Kaynağa Gözat

Scenario graph (#128) (#145)

* #128 WiP

* #128 scenario graph

* format

* isort

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 2 yıl önce
ebeveyn
işleme
8d667b4a14

+ 313 - 169
gui/package-lock.json

@@ -15,11 +15,12 @@
         "@mui/lab": "^5.0.0-alpha.53",
         "@mui/material": "^5.0.6",
         "@mui/x-date-pickers": "^6.0.0",
+        "@projectstorm/react-diagrams": "^7.0.2",
         "date-fns": "^2.29.3",
         "formik": "^2.2.9",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
-        "taipy-gui": "file:../../../.virtualenvs/taipy-ow_unobx/lib/site-packages/taipy/gui/webapp"
+        "taipy-gui": "file:../../../.virtualenvs/taipy-OW_uNObx/Lib/site-packages/taipy/gui/webapp"
       },
       "devDependencies": {
         "@types/react": "^18.0.15",
@@ -37,7 +38,11 @@
         "webpack-cli": "^5.0.1"
       }
     },
-    "../../../.virtualenvs/taipy-ow_unobx/lib/site-packages/taipy/gui/webapp": {
+    "../../../../MJ/.virtualenvs/taipy-lNlhytKY/Lib/site-packages/taipy/gui/webapp": {
+      "extraneous": true
+    },
+    "../../../.virtualenvs/taipy-OW_uNObx/Lib/site-packages/taipy/gui/webapp": {
+      "name": "taipy-gui",
       "version": "2.3.0"
     },
     "node_modules/@babel/code-frame": {
@@ -189,65 +194,65 @@
       }
     },
     "node_modules/@emotion/babel-plugin": {
-      "version": "11.10.8",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.8.tgz",
-      "integrity": "sha512-gxNky50AJL3AlkbjvTARiwAqei6/tNUxDZPSKd+3jqWVM3AmdVTTdpjHorR/an/M0VJqdsuq5oGcFH+rjtyujQ==",
+      "version": "11.11.0",
+      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
+      "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
       "dependencies": {
         "@babel/helper-module-imports": "^7.16.7",
         "@babel/runtime": "^7.18.3",
-        "@emotion/hash": "^0.9.0",
-        "@emotion/memoize": "^0.8.0",
-        "@emotion/serialize": "^1.1.1",
+        "@emotion/hash": "^0.9.1",
+        "@emotion/memoize": "^0.8.1",
+        "@emotion/serialize": "^1.1.2",
         "babel-plugin-macros": "^3.1.0",
         "convert-source-map": "^1.5.0",
         "escape-string-regexp": "^4.0.0",
         "find-root": "^1.1.0",
         "source-map": "^0.5.7",
-        "stylis": "4.1.4"
+        "stylis": "4.2.0"
       }
     },
     "node_modules/@emotion/cache": {
-      "version": "11.10.8",
-      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.8.tgz",
-      "integrity": "sha512-5fyqGHi51LU95o7qQ/vD1jyvC4uCY5GcBT+UgP4LHdpO9jPDlXqhrRr9/wCKmfoAvh5G/F7aOh4MwQa+8uEqhA==",
+      "version": "11.11.0",
+      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+      "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
       "dependencies": {
-        "@emotion/memoize": "^0.8.0",
-        "@emotion/sheet": "^1.2.1",
-        "@emotion/utils": "^1.2.0",
-        "@emotion/weak-memoize": "^0.3.0",
-        "stylis": "4.1.4"
+        "@emotion/memoize": "^0.8.1",
+        "@emotion/sheet": "^1.2.2",
+        "@emotion/utils": "^1.2.1",
+        "@emotion/weak-memoize": "^0.3.1",
+        "stylis": "4.2.0"
       }
     },
     "node_modules/@emotion/hash": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz",
-      "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ=="
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
+      "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
     },
     "node_modules/@emotion/is-prop-valid": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz",
-      "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+      "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
       "dependencies": {
-        "@emotion/memoize": "^0.8.0"
+        "@emotion/memoize": "^0.8.1"
       }
     },
     "node_modules/@emotion/memoize": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz",
-      "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA=="
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+      "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
     },
     "node_modules/@emotion/react": {
-      "version": "11.10.8",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.8.tgz",
-      "integrity": "sha512-ZfGfiABtJ1P1OXqOBsW08EgCDp5fK6C5I8hUJauc/VcJBGSzqAirMnFslhFWnZJ/w5HxPI36XbvMV0l4KZHl+w==",
+      "version": "11.11.0",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.0.tgz",
+      "integrity": "sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw==",
       "dependencies": {
         "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.10.8",
-        "@emotion/cache": "^11.10.8",
-        "@emotion/serialize": "^1.1.1",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0",
-        "@emotion/utils": "^1.2.0",
-        "@emotion/weak-memoize": "^0.3.0",
+        "@emotion/babel-plugin": "^11.11.0",
+        "@emotion/cache": "^11.11.0",
+        "@emotion/serialize": "^1.1.2",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+        "@emotion/utils": "^1.2.1",
+        "@emotion/weak-memoize": "^0.3.1",
         "hoist-non-react-statics": "^3.3.1"
       },
       "peerDependencies": {
@@ -260,44 +265,66 @@
       }
     },
     "node_modules/@emotion/serialize": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz",
-      "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
+      "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
       "dependencies": {
-        "@emotion/hash": "^0.9.0",
-        "@emotion/memoize": "^0.8.0",
-        "@emotion/unitless": "^0.8.0",
-        "@emotion/utils": "^1.2.0",
+        "@emotion/hash": "^0.9.1",
+        "@emotion/memoize": "^0.8.1",
+        "@emotion/unitless": "^0.8.1",
+        "@emotion/utils": "^1.2.1",
         "csstype": "^3.0.2"
       }
     },
     "node_modules/@emotion/sheet": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz",
-      "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA=="
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
+      "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
+    },
+    "node_modules/@emotion/styled": {
+      "version": "11.11.0",
+      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz",
+      "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==",
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "@emotion/babel-plugin": "^11.11.0",
+        "@emotion/is-prop-valid": "^1.2.1",
+        "@emotion/serialize": "^1.1.2",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
+        "@emotion/utils": "^1.2.1"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.0.0-rc.0",
+        "react": ">=16.8.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
     },
     "node_modules/@emotion/unitless": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
-      "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+      "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
     },
     "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz",
-      "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
+      "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
       "peerDependencies": {
         "react": ">=16.8.0"
       }
     },
     "node_modules/@emotion/utils": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz",
-      "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw=="
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
+      "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
     },
     "node_modules/@emotion/weak-memoize": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
-      "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+      "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
     },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
@@ -324,14 +351,14 @@
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
-      "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
+      "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
-        "espree": "^9.5.1",
+        "espree": "^9.5.2",
         "globals": "^13.19.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.2.1",
@@ -347,9 +374,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.39.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz",
-      "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==",
+      "version": "8.40.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz",
+      "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -758,12 +785,12 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.3.0.tgz",
-      "integrity": "sha512-Qux/nRGb0HueZU4L0h1QEqmORCrpgLukWWhG1Im6cFCmLtZbBey/0JuAFRa+OgvIXgGktDt8SY/FYsRkfGt2TQ==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.3.1.tgz",
+      "integrity": "sha512-YPQrsi10rpeEUJkp77NkHqkcZbzAAWITl3MZJ8Mv8ZiS9MEFKmr2kU+SPWUomDByor2UlaS9EmvtslQW74hiWg==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
-        "@mui/utils": "^5.12.0",
+        "@mui/utils": "^5.12.3",
         "@types/react-transition-group": "^4.4.5",
         "clsx": "^1.2.1",
         "prop-types": "^15.8.1",
@@ -866,6 +893,80 @@
         "url": "https://opencollective.com/popperjs"
       }
     },
+    "node_modules/@projectstorm/geometry": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@projectstorm/geometry/-/geometry-7.0.1.tgz",
+      "integrity": "sha512-PIameXbigmMR/TIMvIwFAuWnkQHXne/T7qxMpRACAGUt4yc9ktBF6m4VuGOmjvYyyhJfAhULKNicFAdLRAEzOw==",
+      "dependencies": {
+        "lodash": "^4.17.21"
+      }
+    },
+    "node_modules/@projectstorm/react-canvas-core": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@projectstorm/react-canvas-core/-/react-canvas-core-7.0.1.tgz",
+      "integrity": "sha512-nELl/y9Wl4D4SWS+I8ogYtHvpz24p5nF4VzSpBf4/nmFDMhZtpjSuPNp456o3vOqZBOT5g7pcZTKsJBl0/M7Aw==",
+      "dependencies": {
+        "@emotion/react": "^11.10.5",
+        "@emotion/styled": "^11.10.5",
+        "@projectstorm/geometry": "7.0.1",
+        "lodash": "^4.17.21",
+        "react": "^18.2.0"
+      }
+    },
+    "node_modules/@projectstorm/react-diagrams": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/@projectstorm/react-diagrams/-/react-diagrams-7.0.2.tgz",
+      "integrity": "sha512-/1tBtU7/tGXS8YSO6OD61rmmz+CPWu1ij0QOD5BlPpdOZ+uI3k/Wlr26QeWdX1LTeExzI6qK0dJg6Fg5l+NZPA==",
+      "dependencies": {
+        "@projectstorm/react-canvas-core": "7.0.1",
+        "@projectstorm/react-diagrams-core": "7.0.1",
+        "@projectstorm/react-diagrams-defaults": "7.1.1",
+        "@projectstorm/react-diagrams-routing": "7.1.1"
+      }
+    },
+    "node_modules/@projectstorm/react-diagrams-core": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@projectstorm/react-diagrams-core/-/react-diagrams-core-7.0.1.tgz",
+      "integrity": "sha512-GWaYJjJ1wrkLBnPWksBPVZpjb4nCZ7fKZr14czgm6cOx1zEQGbiy+/whHiNwDx5581JEE2wtdUKff3M8Ur5l/w==",
+      "dependencies": {
+        "@emotion/styled": "^11.10.5",
+        "@projectstorm/geometry": "7.0.1",
+        "@projectstorm/react-canvas-core": "7.0.1",
+        "lodash": "^4.17.21",
+        "react": "^18.2.0",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
+    "node_modules/@projectstorm/react-diagrams-defaults": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@projectstorm/react-diagrams-defaults/-/react-diagrams-defaults-7.1.1.tgz",
+      "integrity": "sha512-htUbj0yJvJ6DjVLn/tAnx67If/Meu4/aE0+cTFC9vanv0QqWUn1FBJ3Z3rBr3kKP+yyuUa2lN3DZbLyRtmYfUw==",
+      "dependencies": {
+        "@emotion/react": "^11.10.5",
+        "@emotion/styled": "^11.*",
+        "@projectstorm/geometry": "7.0.1",
+        "@projectstorm/react-canvas-core": "7.0.1",
+        "@projectstorm/react-diagrams-core": "7.0.1",
+        "lodash": "^4.17.21",
+        "react": "^18.2.0"
+      }
+    },
+    "node_modules/@projectstorm/react-diagrams-routing": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@projectstorm/react-diagrams-routing/-/react-diagrams-routing-7.1.1.tgz",
+      "integrity": "sha512-ZKZ+mI0bf7QBaMeQsjkumohOx67UFA6zVt8/ODH6s0UW9bfkt6LXQEzbYxUETNlELApbnQBB601Jy8f6izVpZw==",
+      "dependencies": {
+        "@projectstorm/geometry": "7.0.1",
+        "@projectstorm/react-canvas-core": "7.0.1",
+        "@projectstorm/react-diagrams-core": "7.0.1",
+        "@projectstorm/react-diagrams-defaults": "7.1.1",
+        "dagre": "^0.8.5",
+        "lodash": "^4.17.21",
+        "pathfinding": "^0.4.18",
+        "paths-js": "^0.4.11",
+        "react": "^18.2.0"
+      }
+    },
     "node_modules/@sinclair/typebox": {
       "version": "0.25.24",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
@@ -929,9 +1030,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "18.16.3",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
-      "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==",
+      "version": "20.1.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.1.tgz",
+      "integrity": "sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==",
       "dev": true
     },
     "node_modules/@types/parse-json": {
@@ -945,9 +1046,9 @@
       "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
     },
     "node_modules/@types/react": {
-      "version": "18.2.0",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
-      "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
+      "version": "18.2.6",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz",
+      "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==",
       "dependencies": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -963,9 +1064,9 @@
       }
     },
     "node_modules/@types/react-is/node_modules/@types/react": {
-      "version": "17.0.58",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz",
-      "integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==",
+      "version": "17.0.59",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.59.tgz",
+      "integrity": "sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==",
       "dependencies": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -973,9 +1074,9 @@
       }
     },
     "node_modules/@types/react-transition-group": {
-      "version": "4.4.5",
-      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
-      "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
+      "version": "4.4.6",
+      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
+      "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
       "dependencies": {
         "@types/react": "*"
       }
@@ -986,9 +1087,9 @@
       "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
     },
     "node_modules/@types/semver": {
-      "version": "7.3.13",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
-      "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+      "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
       "dev": true
     },
     "node_modules/@types/yargs": {
@@ -1007,15 +1108,15 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz",
-      "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz",
+      "integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.59.2",
-        "@typescript-eslint/type-utils": "5.59.2",
-        "@typescript-eslint/utils": "5.59.2",
+        "@typescript-eslint/scope-manager": "5.59.5",
+        "@typescript-eslint/type-utils": "5.59.5",
+        "@typescript-eslint/utils": "5.59.5",
         "debug": "^4.3.4",
         "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
@@ -1041,14 +1142,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz",
-      "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz",
+      "integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.59.2",
-        "@typescript-eslint/types": "5.59.2",
-        "@typescript-eslint/typescript-estree": "5.59.2",
+        "@typescript-eslint/scope-manager": "5.59.5",
+        "@typescript-eslint/types": "5.59.5",
+        "@typescript-eslint/typescript-estree": "5.59.5",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1068,13 +1169,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz",
-      "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz",
+      "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.2",
-        "@typescript-eslint/visitor-keys": "5.59.2"
+        "@typescript-eslint/types": "5.59.5",
+        "@typescript-eslint/visitor-keys": "5.59.5"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1085,13 +1186,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz",
-      "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz",
+      "integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.59.2",
-        "@typescript-eslint/utils": "5.59.2",
+        "@typescript-eslint/typescript-estree": "5.59.5",
+        "@typescript-eslint/utils": "5.59.5",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -1112,9 +1213,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz",
-      "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz",
+      "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1125,13 +1226,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz",
-      "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz",
+      "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.2",
-        "@typescript-eslint/visitor-keys": "5.59.2",
+        "@typescript-eslint/types": "5.59.5",
+        "@typescript-eslint/visitor-keys": "5.59.5",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -1152,17 +1253,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz",
-      "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz",
+      "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@types/json-schema": "^7.0.9",
         "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.59.2",
-        "@typescript-eslint/types": "5.59.2",
-        "@typescript-eslint/typescript-estree": "5.59.2",
+        "@typescript-eslint/scope-manager": "5.59.5",
+        "@typescript-eslint/types": "5.59.5",
+        "@typescript-eslint/typescript-estree": "5.59.5",
         "eslint-scope": "^5.1.1",
         "semver": "^7.3.7"
       },
@@ -1178,12 +1279,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.59.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz",
-      "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==",
+      "version": "5.59.5",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz",
+      "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.2",
+        "@typescript-eslint/types": "5.59.5",
         "eslint-visitor-keys": "^3.3.0"
       },
       "engines": {
@@ -1341,9 +1442,9 @@
       }
     },
     "node_modules/@webpack-cli/configtest": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz",
-      "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz",
+      "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -1367,9 +1468,9 @@
       }
     },
     "node_modules/@webpack-cli/serve": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz",
-      "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==",
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz",
+      "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==",
       "dev": true,
       "engines": {
         "node": ">=14.15.0"
@@ -1702,9 +1803,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001482",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz",
-      "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==",
+      "version": "1.0.30001486",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz",
+      "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==",
       "dev": true,
       "funding": [
         {
@@ -1864,6 +1965,15 @@
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
       "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
     },
+    "node_modules/dagre": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
+      "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
+      "dependencies": {
+        "graphlib": "^2.1.8",
+        "lodash": "^4.17.15"
+      }
+    },
     "node_modules/date-fns": {
       "version": "2.30.0",
       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -1969,9 +2079,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.379",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.379.tgz",
-      "integrity": "sha512-eRMq6Cf4PhjB14R9U6QcXM/VRQ54Gc3OL9LKnFugUIh2AXm3KJlOizlSfVIgjH76bII4zHGK4t0PVTE5qq8dZg==",
+      "version": "1.4.387",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.387.tgz",
+      "integrity": "sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -2122,15 +2232,15 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.39.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz",
-      "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==",
+      "version": "8.40.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz",
+      "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.4.0",
-        "@eslint/eslintrc": "^2.0.2",
-        "@eslint/js": "8.39.0",
+        "@eslint/eslintrc": "^2.0.3",
+        "@eslint/js": "8.40.0",
         "@humanwhocodes/config-array": "^0.11.8",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -2141,8 +2251,8 @@
         "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
         "eslint-scope": "^7.2.0",
-        "eslint-visitor-keys": "^3.4.0",
-        "espree": "^9.5.1",
+        "eslint-visitor-keys": "^3.4.1",
+        "espree": "^9.5.2",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
@@ -2280,9 +2390,9 @@
       }
     },
     "node_modules/eslint-visitor-keys": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
-      "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2332,14 +2442,14 @@
       }
     },
     "node_modules/espree": {
-      "version": "9.5.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
-      "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
+      "version": "9.5.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
+      "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
       "dev": true,
       "dependencies": {
         "acorn": "^8.8.0",
         "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.4.0"
+        "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2739,6 +2849,14 @@
       "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
       "dev": true
     },
+    "node_modules/graphlib": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
+      "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
+      "dependencies": {
+        "lodash": "^4.17.15"
+      }
+    },
     "node_modules/has": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -2819,6 +2937,11 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/heap": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.5.tgz",
+      "integrity": "sha512-G7HLD+WKcrOyJP5VQwYZNC3Z6FcQ7YYjEFiFoIj8PfEr73mu421o8B1N5DKUcc8K37EsJ2XXWA8DtrDz/2dReg=="
+    },
     "node_modules/hoist-non-react-statics": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -3722,6 +3845,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/pathfinding": {
+      "version": "0.4.18",
+      "resolved": "https://registry.npmjs.org/pathfinding/-/pathfinding-0.4.18.tgz",
+      "integrity": "sha512-R0TGEQ9GRcFCDvAWlJAWC+KGJ9SLbW4c0nuZRcioVlXVTlw+F5RvXQ8SQgSqI9KXWC1ew95vgmIiyaWTlCe9Ag==",
+      "dependencies": {
+        "heap": "0.2.5"
+      }
+    },
+    "node_modules/paths-js": {
+      "version": "0.4.11",
+      "resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz",
+      "integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==",
+      "engines": {
+        "node": ">=0.11.0"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -3957,6 +4096,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+    },
     "node_modules/resolve": {
       "version": "1.22.2",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -4341,9 +4485,9 @@
       }
     },
     "node_modules/stylis": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.4.tgz",
-      "integrity": "sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ=="
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+      "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
     },
     "node_modules/supports-color": {
       "version": "7.2.0",
@@ -4369,7 +4513,7 @@
       }
     },
     "node_modules/taipy-gui": {
-      "resolved": "../../../.virtualenvs/taipy-ow_unobx/lib/site-packages/taipy/gui/webapp",
+      "resolved": "../../../.virtualenvs/taipy-OW_uNObx/Lib/site-packages/taipy/gui/webapp",
       "link": true
     },
     "node_modules/tapable": {
@@ -4382,9 +4526,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.17.1",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
-      "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
+      "version": "5.17.2",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.2.tgz",
+      "integrity": "sha512-1D1aGbOF1Mnayq5PvfMc0amAR1y5Z1nrZaGCvI5xsdEfZEVte8okonk02OiaK5fw5hG1GWuuVsakOnpZW8y25A==",
       "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.2",
@@ -4400,16 +4544,16 @@
       }
     },
     "node_modules/terser-webpack-plugin": {
-      "version": "5.3.7",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
-      "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
+      "version": "5.3.8",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz",
+      "integrity": "sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==",
       "dev": true,
       "dependencies": {
         "@jridgewell/trace-mapping": "^0.3.17",
         "jest-worker": "^27.4.5",
         "schema-utils": "^3.1.1",
         "serialize-javascript": "^6.0.1",
-        "terser": "^5.16.5"
+        "terser": "^5.16.8"
       },
       "engines": {
         "node": ">= 10.13.0"
@@ -4669,9 +4813,9 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.81.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz",
-      "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==",
+      "version": "5.82.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.0.tgz",
+      "integrity": "sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
@@ -4716,15 +4860,15 @@
       }
     },
     "node_modules/webpack-cli": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz",
-      "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==",
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz",
+      "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==",
       "dev": true,
       "dependencies": {
         "@discoveryjs/json-ext": "^0.5.0",
-        "@webpack-cli/configtest": "^2.0.1",
+        "@webpack-cli/configtest": "^2.1.0",
         "@webpack-cli/info": "^2.0.1",
-        "@webpack-cli/serve": "^2.0.2",
+        "@webpack-cli/serve": "^2.0.4",
         "colorette": "^2.0.14",
         "commander": "^10.0.1",
         "cross-spawn": "^7.0.3",

+ 1 - 0
gui/package.json

@@ -24,6 +24,7 @@
     "@mui/lab": "^5.0.0-alpha.53",
     "@mui/material": "^5.0.6",
     "@mui/x-date-pickers": "^6.0.0",
+    "@projectstorm/react-diagrams": "^7.0.2",
     "date-fns": "^2.29.3",
     "formik": "^2.2.9",
     "react": "^18.2.0",

+ 121 - 0
gui/src/ScenarioGraph.tsx

@@ -0,0 +1,121 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { CanvasWidget } from "@projectstorm/react-canvas-core";
+import { DeleteItemsAction } from "@projectstorm/react-diagrams";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import AppBar from "@mui/material/AppBar";
+import Dialog from "@mui/material/Dialog";
+import IconButton from "@mui/material/IconButton";
+import Slide from "@mui/material/Slide";
+import Toolbar from "@mui/material/Toolbar";
+import Typography from "@mui/material/Typography";
+import { TransitionProps } from "@mui/material/transitions";
+import { Close, ZoomIn } from "@mui/icons-material";
+
+import { DisplayModel } from "./utils/types";
+import { initDiagram, populateModel, relayoutDiagram } from "./utils/diagram";
+import { TaipyDiagramModel } from "./projectstorm/models";
+import { createRequestUpdateAction, useDispatch, useDynamicProperty, useModule } from "taipy-gui";
+
+interface ScenarioGraphProps {
+    id?: string;
+    scenario?: DisplayModel;
+    coreChanged?: Record<string, unknown>;
+    buttonLabel?: string;
+    defaultButtonLabel?: string;
+    updateVarName?: string;
+}
+
+const boxSx = { "&>div": { height: "100%", width: "100%" }, height: "100%", width: "100%" };
+const titleSx = { ml: 2, flex: 1 };
+const appBarSx = { position: "relative" };
+
+const [engine, dagreEngine] = initDiagram();
+
+const relayout = () => relayoutDiagram(engine, dagreEngine);
+
+const zoomToFit = () => engine.zoomToFit();
+
+const Transition = React.forwardRef(function Transition(
+    props: TransitionProps & {
+        children: React.ReactElement;
+    },
+    ref: React.Ref<unknown>
+) {
+    return <Slide direction="up" ref={ref} {...props} />;
+});
+
+const ScenarioGraph = (props: ScenarioGraphProps) => {
+    const [open, setOpen] = useState(false);
+    const [disabled, setDisabled] = useState(false);
+    const [title, setTitle] = useState("");
+
+    const dispatch = useDispatch();
+    const module = useModule();
+
+    const label = useDynamicProperty(props.buttonLabel, props.defaultButtonLabel, "Show graph");
+
+    const showGraph = useCallback(() => {
+        setTimeout(relayout, 500);
+        setOpen(true);
+    }, []);
+    const hideGraph = useCallback(() => setOpen(false), []);
+
+    // Refresh on broadcast
+    useEffect(() => {
+        if (props.coreChanged?.scenario) {
+            props.updateVarName && dispatch(createRequestUpdateAction(props.id, module, [props.updateVarName], true));
+        }
+    }, [props.coreChanged, props.updateVarName, module, dispatch]);
+
+    useEffect(() => {
+        const displayModel = props.scenario
+            ? Array.isArray(props.scenario)
+                ? props.scenario.length == 1
+                    ? props.scenario[0]
+                    : undefined
+                : props.scenario
+            : undefined;
+
+        if (!displayModel || !props.scenario) {
+            setDisabled(true);
+            return;
+        }
+        setDisabled(false);
+        setTitle(displayModel.label);
+        // clear model
+        const model = new TaipyDiagramModel();
+        // populate model
+        populateModel(displayModel, model);
+        engine.setModel(model);
+        // Block deletion
+        //engine.getActionEventBus().registerAction(new DeleteItemsAction({ keyCodes: [1] }));
+        model.setLocked(true);
+    }, [props.scenario]);
+
+    return (
+        <>
+            <Button id={props.id} variant="outlined" onClick={showGraph} disabled={disabled}>
+                {label}
+            </Button>
+            <Dialog fullScreen open={open} onClose={hideGraph} TransitionComponent={Transition}>
+                <AppBar sx={appBarSx}>
+                    <Toolbar>
+                        <Typography sx={titleSx} variant="h6" component="div">
+                            Scenario: {title}
+                        </Typography>
+                        <IconButton edge="end" color="inherit" onClick={zoomToFit} title="zoom to fit">
+                            <ZoomIn />
+                        </IconButton>
+                        <IconButton edge="end" color="inherit" onClick={hideGraph} title="close">
+                            <Close />
+                        </IconButton>
+                    </Toolbar>
+                </AppBar>
+                <Box sx={boxSx}>{open ? <CanvasWidget engine={engine} /> : null}</Box>
+            </Dialog>
+        </>
+    );
+};
+
+export default ScenarioGraph;

+ 48 - 43
gui/src/ScenarioSelector.tsx

@@ -260,7 +260,7 @@ const ScenarioItem = ({ scenarioId, label, isPrimary, openEditDialog }: Scenario
 
     return (
         <Grid container alignItems="center" direction="row" flexWrap="nowrap" spacing={1}>
-            <Grid item xs sx={treeItemLabelSx}>
+            <Grid item xs sx={treeItemLabelSx} key="label">
                 {isPrimary ? (
                     <Badge
                         badgeContent={<FlagOutlined sx={FlagSx} />}
@@ -275,7 +275,7 @@ const ScenarioItem = ({ scenarioId, label, isPrimary, openEditDialog }: Scenario
                 )}
                 {label}
             </Grid>
-            <Grid item xs="auto">
+            <Grid item xs="auto" key="button">
                 <IconButton data-id={scenarioId} onClick={openEditDialog} sx={tinyEditIconButtonSx}>
                     <EditOutlined />
                 </IconButton>
@@ -616,7 +616,13 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
     );
 
     const onSubmit = useCallback(
-        (...values: any[]) => dispatch(createSendActionNameAction(id, module, props.onScenarioCrud, ...values)),
+        (...values: any[]) => {
+            dispatch(createSendActionNameAction(id, module, props.onScenarioCrud, ...values));
+            if (values.length > 1 && values[1]) {
+                // delete requested => unselect current node
+                onSelect(undefined, []);
+            }
+        },
         [id, module, props.onScenarioCrud]
     );
 
@@ -629,8 +635,8 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
     }, [props.coreChanged, props.updateVars, module, dispatch]);
 
     const onSelect = useCallback(
-        (e: React.SyntheticEvent, nodeIds: Array<string> | string) => {
-            const { cycle = false } = (e.currentTarget as HTMLElement)?.parentElement?.dataset || {};
+        (e: React.SyntheticEvent | undefined, nodeIds: Array<string> | string) => {
+            const { cycle = false } = (e?.currentTarget as HTMLElement)?.parentElement?.dataset || {};
             if (cycle) {
                 return;
             }
@@ -654,49 +660,48 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
                     {scenarios
                         ? scenarios.map((item) => {
                               const [id, label, scenarios, nodeType, _] = item;
-                              return (
-                                  <>
-                                      {displayCycles ? (
-                                          nodeType === NodeType.CYCLE ? (
-                                              <TreeItem
-                                                  key={id}
-                                                  nodeId={id}
-                                                  label={
-                                                      <Box sx={treeItemLabelSx}>
-                                                          <Cycle fontSize="small" color="primary" />
-                                                          {label}
-                                                      </Box>
-                                                  }
-                                                  sx={CycleSx}
-                                                  data-cycle
-                                              >
-                                                  <ScenarioNodes
-                                                      scenarios={scenarios}
-                                                      showPrimary={showPrimaryFlag}
-                                                      openEditDialog={openEditDialog}
-                                                  />
-                                              </TreeItem>
-                                          ) : (
-                                              <ScenarioNodes
-                                                  scenarios={item as Scenario}
-                                                  showPrimary={showPrimaryFlag}
-                                                  openEditDialog={openEditDialog}
-                                              />
-                                          )
-                                      ) : nodeType === NodeType.SCENARIO ? (
-                                          <ScenarioNodes
-                                              scenarios={item as Scenario}
-                                              showPrimary={showPrimaryFlag}
-                                              openEditDialog={openEditDialog}
-                                          />
-                                      ) : (
+                              return displayCycles ? (
+                                  nodeType === NodeType.CYCLE ? (
+                                      <TreeItem
+                                          key={id}
+                                          nodeId={id}
+                                          label={
+                                              <Box sx={treeItemLabelSx}>
+                                                  <Cycle fontSize="small" color="primary" />
+                                                  {label}
+                                              </Box>
+                                          }
+                                          sx={CycleSx}
+                                          data-cycle
+                                      >
                                           <ScenarioNodes
                                               scenarios={scenarios}
                                               showPrimary={showPrimaryFlag}
                                               openEditDialog={openEditDialog}
                                           />
-                                      )}
-                                  </>
+                                      </TreeItem>
+                                  ) : (
+                                      <ScenarioNodes
+                                          key={id}
+                                          scenarios={item as Scenario}
+                                          showPrimary={showPrimaryFlag}
+                                          openEditDialog={openEditDialog}
+                                      />
+                                  )
+                              ) : nodeType === NodeType.SCENARIO ? (
+                                  <ScenarioNodes
+                                      key={id}
+                                      scenarios={item as Scenario}
+                                      showPrimary={showPrimaryFlag}
+                                      openEditDialog={openEditDialog}
+                                  />
+                              ) : (
+                                  <ScenarioNodes
+                                      key={id}
+                                      scenarios={scenarios}
+                                      showPrimary={showPrimaryFlag}
+                                      openEditDialog={openEditDialog}
+                                  />
                               );
                           })
                         : null}

+ 0 - 0
gui/src/icons/cycle.component.tsx → gui/src/icons/cycle.tsx


+ 0 - 0
gui/src/icons/datanode.component.tsx → gui/src/icons/datanode.tsx


+ 7 - 6
gui/src/icons/index.ts

@@ -1,6 +1,7 @@
-export { Cycle } from "./cycle.component";
-export { Datanode } from "./datanode.component";
-export { Job } from "./job.component";
-export { Pipeline } from "./pipeline.component";
-export { Scenario } from "./scenario.component";
-export { Task } from "./task.component";
+export { Cycle } from "./cycle";
+export { Datanode } from "./datanode";
+export { Job } from "./job";
+export { Pipeline } from "./pipeline";
+export { Scenario } from "./scenario";
+export { Task } from "./task";
+export { Input } from "./input";

+ 7 - 0
gui/src/icons/input.tsx

@@ -0,0 +1,7 @@
+import { SvgIcon, SvgIconProps } from "@mui/material";
+
+export const Input = (props: SvgIconProps) => (
+  <SvgIcon {...props} viewBox="0 0 24 24">
+    <path d="M 5,5 H 19 V 19 H 5 V 17 H 3 v 2 c 0,1.1 0.9,2 2,2 h 14 c 1.1,0 2,-0.9 2,-2 V 5 C 21,3.9 20.1,3 19,3 H 5 C 3.9,3 3,3.9 3,5 V 7 H 5 Z M 9.6,8.41 12.2,11 H 3 v 2 h 9.2 L 9.6,15.6 11,17 16,12 11,7 Z" />
+  </SvgIcon>
+);

+ 0 - 0
gui/src/icons/job.component.tsx → gui/src/icons/job.tsx


+ 0 - 0
gui/src/icons/pipeline.component.tsx → gui/src/icons/pipeline.tsx


+ 0 - 0
gui/src/icons/scenario.component.tsx → gui/src/icons/scenario.tsx


+ 0 - 0
gui/src/icons/task.component.tsx → gui/src/icons/task.tsx


+ 2 - 1
gui/src/index.ts

@@ -1,3 +1,4 @@
 import ScenarioSelector from "./ScenarioSelector";
+import ScenarioGraph from "./ScenarioGraph";
 
-export {ScenarioSelector}
+export { ScenarioSelector, ScenarioGraph as Graph };

+ 144 - 0
gui/src/projectstorm/NodeWidget.tsx

@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import { useCallback } from "react";
+import styled from "@emotion/styled";
+import { DefaultPortModel, PortWidget } from "@projectstorm/react-diagrams";
+import { DiagramEngine } from "@projectstorm/react-diagrams-core";
+import { Output } from "@mui/icons-material";
+
+import { DataNode, Task, Pipeline } from "../utils/names";
+import { Datanode as DIcon, Task as TIcon, Pipeline as PIcon, Scenario as SIcon } from "../icons";
+import { TaipyNodeModel } from "./models";
+import { IN_PORT_NAME } from "../utils/diagram";
+import { Input } from "../icons";
+
+namespace S {
+    export const Node = styled.div<{ background?: string; selected?: boolean }>`
+        background-color: ${(p) => p.background};
+        border-radius: 5px;
+        color: white;
+        border: solid 2px black;
+        overflow: visible;
+        border: solid 2px ${(p) => (p.selected ? "rgb(0,192,255)" : "black")};
+    `;
+    export const Title = styled.div`
+        background: rgba(0, 0, 0, 0.3);
+        display: flex;
+        white-space: nowrap;
+        justify-items: center;
+    `;
+
+    export const TitleName = styled.div`
+        flex-grow: 1;
+        padding: 5px 5px;
+    `;
+
+    export const SubTitleName = styled.span`
+        font-size: smaller;
+        padding-left: 0.7em;
+    `;
+
+    export const TitleIcon = styled.div`
+        padding: 5px 0 5px 5px;
+    `;
+
+    export const Ports = styled.div`
+        display: flex;
+        background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2));
+    `;
+
+    export const PortsContainer = styled.div`
+        flex-grow: 1;
+        display: flex;
+        flex-direction: column;
+        &:first-of-type {
+            margin-right: 10px;
+        }
+        &:only-child {
+            margin-right: 0px;
+        }
+    `;
+
+    export const OutPortLabel = styled.div`
+        display: flex;
+        margin-top: 1px;
+        align-items: center;
+        justify-content: end;
+        margin-right: 5px;
+    `;
+
+    export const InPortLabel = styled.div`
+        display: flex;
+        margin-top: 1px;
+        align-items: center;
+        margin-left: 5px;
+    `;
+}
+
+interface NodeProps {
+    node: TaipyNodeModel;
+    engine: DiagramEngine;
+}
+
+const NodeWidget = ({ node, engine }: NodeProps) => {
+    const generatePort = useCallback(
+        (port: DefaultPortModel) =>
+            port.getName() == IN_PORT_NAME ? (
+                <S.InPortLabel>
+                    <PortWidget engine={engine} port={port} key={port.getID()}>
+                        <Input />
+                    </PortWidget>
+                </S.InPortLabel>
+            ) : (
+                <S.OutPortLabel>
+                    <PortWidget engine={engine} port={port} key={port.getID()}>
+                        <Output />
+                    </PortWidget>
+                </S.OutPortLabel>
+            ),
+        [engine]
+    );
+
+    return (
+        <S.Node
+            data-default-node-name={node.getOptions().name}
+            selected={node.isSelected()}
+            background={node.getOptions().color}
+        >
+            <S.Title>
+                <S.TitleIcon className="icon" title={node.getType()}>
+                    {node.getType() == DataNode ? (
+                        <DIcon />
+                    ) : node.getType() == Task ? (
+                        <TIcon />
+                    ) : node.getType() == Pipeline ? (
+                        <PIcon />
+                    ) : (
+                        <SIcon />
+                    )}
+                </S.TitleIcon>
+                <S.TitleName>
+                    {node.getOptions().name}
+                    {node.subtype ? <S.SubTitleName>{node.subtype}</S.SubTitleName> : null}
+                </S.TitleName>
+            </S.Title>
+            <S.Ports>
+                <S.PortsContainer>{node.getInPorts().map(generatePort)}</S.PortsContainer>
+                <S.PortsContainer>{node.getOutPorts().map(generatePort)}</S.PortsContainer>
+            </S.Ports>
+        </S.Node>
+    );
+};
+
+export default NodeWidget;

+ 41 - 0
gui/src/projectstorm/factories.tsx

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import { AbstractReactFactory, GenerateModelEvent, GenerateWidgetEvent, AbstractModelFactory } from "@projectstorm/react-canvas-core";
+import { DiagramEngine } from "@projectstorm/react-diagrams-core";
+import { TaipyNodeModel, TaipyPortModel } from "./models";
+import NodeWidget from "./NodeWidget";
+
+export class TaipyNodeFactory extends AbstractReactFactory<TaipyNodeModel, DiagramEngine> {
+  constructor(nodeType: string) {
+    super(nodeType);
+  }
+
+  generateReactWidget(event: GenerateWidgetEvent<TaipyNodeModel>): JSX.Element {
+    return <NodeWidget engine={this.engine} node={event.model} />;
+  }
+
+  generateModel(_: GenerateModelEvent): TaipyNodeModel {
+    return new TaipyNodeModel();
+  }
+}
+
+export class TaipyPortFactory extends AbstractModelFactory<TaipyPortModel, DiagramEngine> {
+  constructor() {
+    super("taipy-port");
+  }
+
+  generateModel(_: GenerateModelEvent): TaipyPortModel {
+    return new TaipyPortModel({ type: "taipy-port", name: "fred" });
+  }
+}

+ 72 - 0
gui/src/projectstorm/models.ts

@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import { DefaultNodeModel, DefaultNodeModelOptions, DefaultPortModel, DefaultPortModelOptions, DiagramModel, PortModelAlignment } from "@projectstorm/react-diagrams";
+
+import { IN_PORT_NAME, OUT_PORT_NAME } from "../utils/diagram";
+import { getChildType } from "../utils/childtype";
+import { DataNode, Task } from "../utils/names";
+
+export class TaipyDiagramModel extends DiagramModel {}
+
+export interface TaipyNodeModelOptions extends DefaultNodeModelOptions {
+    subtype?: string;
+}
+export class TaipyNodeModel extends DefaultNodeModel {
+    subtype: string | undefined;
+    constructor(options?: TaipyNodeModelOptions) {
+        super(options);
+        this.subtype = options?.subtype;
+    }
+}
+
+export class TaipyPortModel extends DefaultPortModel {
+  static createInPort() {
+    return new TaipyPortModel({ in: true, name: IN_PORT_NAME, label: IN_PORT_NAME, alignment: PortModelAlignment.LEFT });
+  }
+
+  static createOutPort() {
+    return new TaipyPortModel({ in: false, name: OUT_PORT_NAME, label: OUT_PORT_NAME, alignment: PortModelAlignment.RIGHT });
+  }
+
+  constructor(options: DefaultPortModelOptions) {
+    super({
+      ...options,
+      type: "taipy-port",
+    });
+  }
+
+  /**
+   * Verify that a port can be linked to
+   * @param {PortModel} port The port being linked to
+   */
+  canLinkToPort(port: TaipyPortModel) {
+    // only out => In
+    if (this.options.in || !port.getOptions().in) {
+      return false;
+    }
+    // child type
+    if (port.getNode()?.getType() !== getChildType(this.getNode()?.getType())) {
+      // Task -> DataNode Link
+      if (port.getNode().getType() != Task || this.getNode().getType() != DataNode) {
+        return false;
+      }
+    }
+    // Check unicity
+    if (Object.values(this.getLinks()).some((link) => link.getTargetPort() === port)) {
+      return false;
+    }
+    return true;
+  }
+
+}

+ 21 - 0
gui/src/utils/childtype.ts

@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import { DataNode, Pipeline, Scenario, Task } from "./names";
+
+const childType: Record<string, string> = {
+  [Task]: DataNode,
+  [Pipeline]: Task,
+  [Scenario]: Pipeline,
+};
+export const getChildType = (nodeType: string) => childType[nodeType] || "";

+ 32 - 0
gui/src/utils/config.ts

@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import { DataNode, Pipeline, Scenario, Task } from "./names";
+
+const nodeColor: Record<string, string> = {
+  [DataNode]: "#283282",
+  [Task]: "#ff462b",
+  [Pipeline]: "#ff462b",
+  [Scenario]: "#f0faff",
+};
+export const getNodeColor = (nodeType: string) => nodeColor[nodeType] || "pink";
+
+const nodeIcon: Record<string, string> = {
+  [DataNode]: "datanode",
+  [Task]: "task",
+  [Pipeline]: "pipeline",
+  [Scenario]: "scenario",
+};
+export const getNodeIcon = (nodeType: string) => nodeIcon[nodeType];
+
+export const nodeTypes = [DataNode, Pipeline, Scenario, Task];

+ 182 - 0
gui/src/utils/diagram.ts

@@ -0,0 +1,182 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import createEngine, {
+    DefaultLinkModel,
+    DefaultNodeModel,
+    DefaultPortModel,
+    LinkModel,
+    DefaultDiagramState,
+    DiagramEngine,
+    DagreEngine,
+    PointModel,
+    DeleteItemsAction,
+} from "@projectstorm/react-diagrams";
+
+import { DataNode, Pipeline, Scenario, Task } from "./names";
+import { getNodeColor } from "./config";
+import { TaipyDiagramModel, TaipyNodeModel, TaipyPortModel } from "../projectstorm/models";
+import { TaipyNodeFactory, TaipyPortFactory } from "../projectstorm/factories";
+import { nodeTypes } from "./config";
+import { DisplayModel } from "./types";
+
+export const initDiagram = (): [DiagramEngine, DagreEngine, TaipyDiagramModel] => {
+    const engine = createEngine();
+    nodeTypes.forEach((nodeType) => engine.getNodeFactories().registerFactory(new TaipyNodeFactory(nodeType)));
+    engine.getPortFactories().registerFactory(new TaipyPortFactory());
+    const state = engine.getStateMachine().getCurrentState();
+    if (state instanceof DefaultDiagramState) {
+        state.dragNewLink.config.allowLooseLinks = false;
+    }
+    const dagreEngine = new DagreEngine({
+        graph: {
+            rankdir: "LR",
+            ranker: "longest-path",
+            marginx: 25,
+            marginy: 25,
+        },
+        includeLinks: true,
+    });
+    const model = new TaipyDiagramModel();
+    engine.setModel(model);
+    return [engine, dagreEngine, model];
+};
+
+export const getModelLinks = (model: TaipyDiagramModel) => Object.values(model.getActiveLinkLayer().getLinks());
+
+export const IN_PORT_NAME = "In";
+export const OUT_PORT_NAME = "Out";
+
+const nodePorts: Record<string, [boolean, boolean]> = {
+    [DataNode]: [true, true],
+    [Task]: [true, true],
+    [Pipeline]: [true, true],
+    [Scenario]: [false, true],
+};
+const setPorts = (node: DefaultNodeModel) => {
+    const [inPort, outPort] = nodePorts[node.getType()];
+    inPort && node.addPort(TaipyPortModel.createInPort());
+    outPort && node.addPort(TaipyPortModel.createOutPort());
+};
+
+export const getLinkId = (link: LinkModel) =>
+    `LINK.${getNodeId(link.getSourcePort().getNode() as DefaultNodeModel)}.${getNodeId(
+        link.getTargetPort().getNode() as DefaultNodeModel
+    )}`;
+export const getNodeId = (node: DefaultNodeModel) => `${node.getType()}.${node.getID()}`;
+
+export const createNode = (nodeType: string, id: string, name: string, subtype: string, createPorts = true) => {
+    const node = new TaipyNodeModel({
+        id: id,
+        type: nodeType,
+        name: name,
+        color: getNodeColor(nodeType),
+        subtype: subtype,
+    });
+    //createPorts && setPorts(node);
+    return node;
+};
+
+export const createLink = (outPort: DefaultPortModel, inPort: DefaultPortModel) => {
+    const link = outPort.link<DefaultLinkModel>(inPort);
+    return link;
+};
+
+const isInLine = (pnt: PointModel, startLine: PointModel, endLine: PointModel) => {
+    const L2 =
+        (endLine.getX() - startLine.getX()) * (endLine.getX() - startLine.getX()) +
+        (endLine.getY() - startLine.getY()) * (endLine.getY() - startLine.getY());
+    if (L2 === 0) {
+        return false;
+    }
+    const r =
+        ((pnt.getX() - startLine.getX()) * (endLine.getX() - startLine.getX()) +
+            (pnt.getY() - startLine.getY()) * (endLine.getY() - startLine.getY())) /
+        L2;
+
+    //Assume line thickness is circular
+    if (0 <= r && r <= 1) {
+        //On the line segment
+        const s =
+            ((startLine.getY() - pnt.getY()) * (endLine.getX() - startLine.getX()) -
+                (startLine.getX() - pnt.getX()) * (endLine.getY() - startLine.getY())) /
+            L2;
+        return Math.abs(s) * Math.sqrt(L2) <= lineLeeway;
+    }
+    return false;
+};
+
+const lineLeeway = 0.1;
+
+export const relayoutDiagram = (engine: DiagramEngine, dagreEngine: DagreEngine) => {
+    const model = engine.getModel();
+    dagreEngine.redistribute(model);
+    //  engine.getLinkFactories().getFactory<PathFindingLinkFactory>(PathFindingLinkFactory.NAME).calculateRoutingMatrix();
+    getModelLinks(model).forEach((l) => {
+        const points = l.getPoints();
+        if (points.length === 3) {
+            // remove unnecessary intermediate if same level
+            if (
+                Math.abs(points[0].getX() - points[2].getX()) < lineLeeway ||
+                Math.abs(points[0].getY() - points[2].getY()) < lineLeeway
+            ) {
+                points.splice(1, 1);
+                l.setPoints(points);
+            }
+        } else if (points.length > 3) {
+            const pointsToRemove = [] as number[];
+            let startIdx = 0;
+            while (startIdx + 2 < points.length) {
+                if (isInLine(points[startIdx + 1], points[startIdx], points[startIdx + 2])) {
+                    pointsToRemove.push(startIdx + 1);
+                }
+                startIdx++;
+            }
+            pointsToRemove.reverse().forEach((idx) => points.splice(idx, 1));
+            l.setPoints(points);
+        }
+    });
+    engine.repaintCanvas();
+};
+
+export const populateModel = (displayModel: DisplayModel, model: TaipyDiagramModel) => {
+    const linkModels: DefaultLinkModel[] = [];
+    const nodeModels: Record<string, Record<string, DefaultNodeModel>> = {};
+
+    displayModel.nodes &&
+        Object.entries(displayModel.nodes).forEach(([nodeType, n]) => {
+            Object.entries(n).forEach(([id, detail]) => {
+                const node = createNode(nodeType, id, detail.name, detail.type);
+                nodeModels[nodeType] = nodeModels[nodeType] || {};
+                nodeModels[nodeType][id] = node;
+            });
+        });
+
+    Array.isArray(displayModel.links) &&
+        displayModel.links.forEach(([nodeType, nodeId, childType, childId]) => {
+            const parentNode = nodeModels[nodeType] && nodeModels[nodeType][nodeId];
+            const childNode = nodeModels[childType] && nodeModels[childType][childId];
+            if (parentNode && childNode) {
+                const link = createLink(
+                    (parentNode.getPort(OUT_PORT_NAME) || parentNode.addOutPort(OUT_PORT_NAME)) as DefaultPortModel,
+                    (childNode.getPort(IN_PORT_NAME) || childNode.addInPort(IN_PORT_NAME)) as DefaultPortModel
+                );
+                linkModels.push(link);
+            }
+        });
+
+    const nodeLayer = model.getActiveNodeLayer();
+    Object.values(nodeModels).forEach((nm) => Object.values(nm).forEach((n) => nodeLayer.addModel(n)));
+    const linkLayer = model.getActiveLinkLayer();
+    linkModels.forEach((l) => linkLayer.addModel(l));
+};

+ 19 - 0
gui/src/utils/names.ts

@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+export const Taipy = "TAIPY";
+export const Job = "JOB";
+export const DataNode = "DataNode";
+export const Task = "Task";
+export const Pipeline = "Pipeline";
+export const Scenario = "Scenario";

+ 1 - 0
gui/src/utils/types.ts

@@ -0,0 +1 @@
+export type DisplayModel = { nodes: Record<string, Record<string, {name: string, type: string}>>; links: Array<[string, string, string, string]> };

+ 1 - 1
gui/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "target": "es5",
+    "target": "es2015",
     "lib": [
       "dom",
       "dom.iterable",

+ 68 - 7
src/taipy/gui_core/GuiCoreLib.py

@@ -10,12 +10,12 @@
 # specific language governing permissions and limitations under the License.
 
 import typing as t
-from urllib.parse import urlencode
+from datetime import datetime
 
 from dateutil import parser
 
 import taipy as tp
-from taipy.core import Cycle, Scenario
+from taipy.core import Cycle, DataNode, Scenario
 from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event
 from taipy.core.notification.notifier import Notifier
@@ -50,6 +50,58 @@ class GuiCoreScenarioAdapter(_TaipyBase):
         return _TaipyBase._HOLDER_PREFIX + "Sc"
 
 
+class GuiCoreScenarioIdAdapter(_TaipyBase):
+    def get(self):
+        data = super().get()
+        if isinstance(data, Scenario):
+            return data.id
+        return data
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "ScI"
+
+
+class GuiCoreScenarioGraphAdapter(_TaipyBase):
+    @staticmethod
+    def get_entity_type(node: t.Any):
+        return DataNode.__name__ if isinstance(node.entity, DataNode) else node.type
+
+    def get(self):
+        data = super().get()
+        if isinstance(data, Scenario):
+            dag = data._get_dag()
+            nodes = dict()
+            for id, node in dag.nodes.items():
+                entityType = GuiCoreScenarioGraphAdapter.get_entity_type(node)
+                cat = nodes.get(entityType)
+                if cat is None:
+                    cat = dict()
+                    nodes[entityType] = cat
+                cat[id] = {
+                    "name": node.entity.get_simple_label(),
+                    "type": node.entity.storage_type() if hasattr(node.entity, "storage_type") else None,
+                }
+            return {
+                "label": data.get_label(),
+                "nodes": nodes,
+                "links": [
+                    (
+                        GuiCoreScenarioGraphAdapter.get_entity_type(e.src),
+                        e.src.entity.id,
+                        GuiCoreScenarioGraphAdapter.get_entity_type(e.dest),
+                        e.dest.entity.id,
+                    )
+                    for e in dag.edges
+                ],
+            }
+        return None
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "ScG"
+
+
 class GuiCoreContext(CoreEventConsumerBase):
     __PROP_SCENARIO_ID = "id"
     __PROP_SCENARIO_CONFIG_ID = "config"
@@ -99,8 +151,7 @@ class GuiCoreContext(CoreEventConsumerBase):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) == 0:
             return
-        scenario_id = args[0]
-        state.assign(GuiCoreContext._SCENARIO_SELECTOR_ID_VAR, scenario_id)
+        state.assign(GuiCoreContext._SCENARIO_SELECTOR_ID_VAR, args[0])
 
     def get_scenario_by_id(self, id: str) -> t.Optional[Scenario]:
         if not id:
@@ -238,6 +289,17 @@ class GuiCore(ElementLibrary):
                 "error": ElementProperty(PropertyType.react, f"{{{GuiCoreContext._SCENARIO_VIZ_ERROR_VAR}}}"),
             },
         ),
+        "graph": Element(
+            "scenario",
+            {
+                "id": ElementProperty(PropertyType.string),
+                "scenario": ElementProperty(GuiCoreScenarioGraphAdapter),
+                "button_label": ElementProperty(PropertyType.dynamic_string),
+            },
+            inner_properties={
+                "core_changed": ElementProperty(PropertyType.broadcast, GuiCoreContext._CORE_CHANGED_NAME),
+            },
+        ),
     }
 
     def get_name(self) -> str:
@@ -258,10 +320,9 @@ class GuiCore(ElementLibrary):
             GuiCoreContext._SCENARIO_SELECTOR_ID_VAR,
             GuiCoreContext._SCENARIO_VIZ_ERROR_VAR,
         ]:
-            state._add_attribute(var)
-            state._gui._bind_var_val(var, "")
+            state._add_attribute(var, "")
 
     def get_version(self) -> str:
         if not hasattr(self, "version"):
-            self.version = _get_version()
+            self.version = _get_version() + str(datetime.now().timestamp())
         return self.version