diff --git a/package-lock.json b/package-lock.json index 80e67500..e3243ac5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,19 @@ "name": "fuel-cycle-sim", "version": "0.0.0", "dependencies": { + "@codemirror/lang-python": "^6.2.1", + "@uiw/codemirror-theme-vscode": "^4.24.2", + "@uiw/react-codemirror": "^4.24.2", "@xyflow/react": "^12.8.1", "plotly.js": "^3.0.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-plotly.js": "^2.6.0" }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.4.1", "concurrently": "^9.2.0", "eslint": "^9.25.0", @@ -265,6 +268,14 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -334,6 +345,103 @@ "findup": "bin/findup.js" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1045,6 +1153,37 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -1138,6 +1277,11 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" + }, "node_modules/@plotly/d3": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz", @@ -1706,24 +1850,29 @@ "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true + }, "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "devOptional": true, - "license": "MIT", "dependencies": { + "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", - "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, - "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^18.0.0" } }, "node_modules/@types/supercluster": { @@ -1734,6 +1883,86 @@ "@types/geojson": "*" } }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz", + "integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-theme-vscode": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.2.tgz", + "integrity": "sha512-5rLvAMKNjqtHSy3BZnI1Q4ZLqUDDJC5fKOp8Mv5VjPqEmAxEAIGrIXBnhAFlRg5VjHbHEMtM1O5ugL3IeHoBdQ==", + "dependencies": { + "@uiw/codemirror-themes": "4.24.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.24.2.tgz", + "integrity": "sha512-0fQusJE08APL+1WM0xYqvampVM2RpABHjvZbcJWHdRc+teZbrP8907VW1ZX1tO/7/xgRRt4Fc3RHPCcTs7b0NQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz", + "integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.24.2", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz", @@ -2063,6 +2292,20 @@ "node": ">=12" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-alpha": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.0.4.tgz", @@ -2240,6 +2483,11 @@ "resolved": "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz", "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==" }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4521,24 +4769,26 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { - "scheduler": "^0.26.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -4820,10 +5070,12 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/semver": { "version": "6.3.1", @@ -5009,6 +5261,11 @@ "resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz", "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==" }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, "node_modules/supercluster": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", @@ -5346,6 +5603,11 @@ "pbf": "^3.2.1" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/weak-map": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", diff --git a/package.json b/package.json index b16423e9..8f9c0e3b 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,17 @@ "dependencies": { "@xyflow/react": "^12.8.1", "plotly.js": "^3.0.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-plotly.js": "^2.6.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-plotly.js": "^2.6.0", + "@uiw/react-codemirror": "^4.24.2", + "@uiw/codemirror-theme-vscode": "^4.24.2", + "@codemirror/lang-python": "^6.2.1" }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.4.1", "concurrently": "^9.2.0", "eslint": "^9.25.0", diff --git a/src/App.jsx b/src/App.jsx index 0bbba41d..bc6aa199 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,6 +19,7 @@ import NodeSidebar from './NodeSidebar'; import { DnDProvider, useDnD } from './DnDContext.jsx'; import ContextMenu from './ContextMenu.jsx'; import EventsTab from './EventsTab.jsx'; +import GlobalVariablesTab from './GlobalVariablesTab.jsx'; import { isValidPythonIdentifier } from './utils.js'; import { makeEdge } from './CustomEdge'; import { nodeTypes } from './nodeConfig.js'; @@ -80,6 +81,10 @@ const DnDFlow = () => { // Global variables state const [globalVariables, setGlobalVariables] = useState([]); const [events, setEvents] = useState([]); + + // Python code editor state + const [pythonCode, setPythonCode] = useState("# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); + const [defaultValues, setDefaultValues] = useState({}); const [isEditingLabel, setIsEditingLabel] = useState(false); const [tempLabel, setTempLabel] = useState(''); @@ -222,7 +227,8 @@ const DnDFlow = () => { nodeCounter, solverParams, globalVariables, - events + events, + pythonCode }; // Check if File System Access API is supported @@ -302,7 +308,15 @@ const DnDFlow = () => { } // Load the graph data - const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData; + const { + nodes: loadedNodes, + edges: loadedEdges, + nodeCounter: loadedNodeCounter, + solverParams: loadedSolverParams, + globalVariables: loadedGlobalVariables, + events: loadedEvents, + pythonCode: loadedPythonCode + } = graphData; setNodes(loadedNodes || []); setEdges(loadedEdges || []); setSelectedNode(null); @@ -320,6 +334,7 @@ const DnDFlow = () => { }); setGlobalVariables(loadedGlobalVariables ?? []); setEvents(loadedEvents ?? []); + setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); alert('Graph loaded successfully!'); } catch (error) { @@ -354,7 +369,15 @@ const DnDFlow = () => { return; } - const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData; + const { + nodes: loadedNodes, + edges: loadedEdges, + nodeCounter: loadedNodeCounter, + solverParams: loadedSolverParams, + globalVariables: loadedGlobalVariables, + events: loadedEvents, + pythonCode: loadedPythonCode + } = graphData; setNodes(loadedNodes || []); setEdges(loadedEdges || []); setSelectedNode(null); @@ -372,6 +395,7 @@ const DnDFlow = () => { }); setGlobalVariables(loadedGlobalVariables ?? []); setEvents(loadedEvents ?? []); + setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); alert('Graph loaded successfully!'); } catch (error) { @@ -540,7 +564,8 @@ const DnDFlow = () => { edges, solverParams, globalVariables, - events + events, + pythonCode }; const response = await fetch(getApiEndpoint('/run-pathsim'), { @@ -568,36 +593,6 @@ const DnDFlow = () => { } }; - const addGlobalVariable = () => { - const newVariable = { - id: Date.now().toString(), - name: '', - value: '', - nameError: false - }; - setGlobalVariables([...globalVariables, newVariable]); - }; - - const removeGlobalVariable = (id) => { - setGlobalVariables(globalVariables.filter(variable => variable.id !== id)); - }; - - const updateGlobalVariable = (id, field, value) => { - setGlobalVariables(globalVariables.map(variable => { - if (variable.id === id) { - const updatedVariable = { ...variable, [field]: value }; - - // Validate name field - if (field === 'name') { - updatedVariable.nameError = value !== '' && !isValidPythonIdentifier(value); - } - - return updatedVariable; - } - return variable; - })); - }; - //When user connects two nodes by dragging, creates an edge according to the styles in our makeEdge function const onConnect = useCallback( (params) => { @@ -1645,219 +1640,13 @@ const DnDFlow = () => { {/* Global Variables Tab */} {activeTab === 'globals' && ( -
-
-

- Global Variables -

-
-

- Define global variables that can be used in node definitions throughout your model. -

- - {globalVariables.length === 0 ? ( -
- No global variables defined. Click "Add Variable" to create one. -
- ) : ( -
- {globalVariables.map((variable) => ( -
-
- - updateGlobalVariable(variable.id, 'name', e.target.value)} - placeholder="variable_name" - style={{ - width: '95%', - padding: '8px', - borderRadius: '4px', - border: variable.nameError ? '2px solid #e74c3c' : '1px solid #666', - backgroundColor: '#2c2c54', - color: '#ffffff', - fontSize: '14px' - }} - /> - {variable.nameError && ( -
- Invalid Python variable name -
- )} -
-
- - updateGlobalVariable(variable.id, 'value', e.target.value)} - placeholder="0.5" - style={{ - width: '95%', - padding: '8px', - borderRadius: '4px', - border: '1px solid #666', - backgroundColor: '#2c2c54', - color: '#ffffff', - fontSize: '14px' - }} - /> - {/* Placeholder div to maintain alignment */} -
- {/* Empty space to match error message height */} -
-
-
- -
-
- ))} -
- )} - -
- - -
-
- -
-

Usage Instructions:

-
    -
  • Variable names must be valid Python identifiers (start with letter/underscore, contain only letters/digits/underscores)
  • -
  • Cannot use Python keywords like "if", "for", "class", "def", etc.
  • -
  • Use meaningful names (e.g., "flow_rate", "temperature", "my_constant")
  • -
  • Use numeric values, expressions, or references to other variables
  • -
  • Variables can be referenced in node parameters using their exact names
  • -
  • Variables are saved and loaded with your graph files
  • -
-
-
-
+ )} {/* Results Tab */} diff --git a/src/GlobalVariablesTab.jsx b/src/GlobalVariablesTab.jsx new file mode 100644 index 00000000..c01348da --- /dev/null +++ b/src/GlobalVariablesTab.jsx @@ -0,0 +1,304 @@ +import { isValidPythonIdentifier } from './utils.js'; + + +import PythonCodeEditor from './PythonCodeEditor'; +import './PythonCodeEditor.css'; + +export const IdeWithAutocomplete = ({ pythonCode, setPythonCode }) => { + const handleCodeExecution = (result) => { + if (result.success) { + console.log('Code executed successfully:', result); + // You can add notifications here if needed + } else { + console.error('Code execution failed:', result.error); + } + }; + + return ( +
+ +
+ ); +}; + +const GlobalVariablesTab = ({ + globalVariables, + setGlobalVariables, + setActiveTab, + pythonCode, + setPythonCode +}) => { + + const addGlobalVariable = () => { + const newVariable = { + id: Date.now().toString(), + name: '', + value: '', + nameError: false + }; + setGlobalVariables([...globalVariables, newVariable]); + }; + + const removeGlobalVariable = (id) => { + setGlobalVariables(globalVariables.filter(variable => variable.id !== id)); + }; + + const updateGlobalVariable = (id, field, value) => { + setGlobalVariables(globalVariables.map(variable => { + if (variable.id === id) { + const updatedVariable = { ...variable, [field]: value }; + + // Validate name field + if (field === 'name') { + updatedVariable.nameError = value !== '' && !isValidPythonIdentifier(value); + } + + return updatedVariable; + } + return variable; + })); + }; + + return ( +
+
+

+ Global Variables +

+
+

+ Define global variables that can be used in node definitions throughout your model. +

+ + {globalVariables.length === 0 ? ( +
+ No global variables defined. Click "Add Variable" to create one. +
+ ) : ( +
+ {globalVariables.map((variable) => ( +
+
+ + updateGlobalVariable(variable.id, 'name', e.target.value)} + placeholder="variable_name" + style={{ + width: '95%', + padding: '8px', + borderRadius: '4px', + border: variable.nameError ? '2px solid #e74c3c' : '1px solid #666', + backgroundColor: '#2c2c54', + color: '#ffffff', + fontSize: '14px' + }} + /> + {variable.nameError && ( +
+ Invalid Python variable name +
+ )} +
+
+ + updateGlobalVariable(variable.id, 'value', e.target.value)} + placeholder="0.5" + style={{ + width: '95%', + padding: '8px', + borderRadius: '4px', + border: '1px solid #666', + backgroundColor: '#2c2c54', + color: '#ffffff', + fontSize: '14px' + }} + /> + {/* Placeholder div to maintain alignment */} +
+ {/* Empty space to match error message height */} +
+
+
+ +
+
+ ))} +
+ )} + +
+ + +
+
+ +
+

Usage Instructions:

+
    +
  • Variable names must be valid Python identifiers (start with letter/underscore, contain only letters/digits/underscores)
  • +
  • Cannot use Python keywords like "if", "for", "class", "def", etc.
  • +
  • Use meaningful names (e.g., "flow_rate", "temperature", "my_constant")
  • +
  • Use numeric values, expressions, or references to other variables
  • +
  • Variables can be referenced in node parameters using their exact names
  • +
  • Variables are saved and loaded with your graph files
  • +
+
+ {/* Python Code Editor Section */} +
+

+ Python Code Editor +

+
+

+ Define Python variables and functions here. They will be available in your event functions and throughout the simulation. +

+ +
+
+
+
+ ); +}; + +export default GlobalVariablesTab; diff --git a/src/PythonCodeEditor.css b/src/PythonCodeEditor.css new file mode 100644 index 00000000..00dbce9f --- /dev/null +++ b/src/PythonCodeEditor.css @@ -0,0 +1,122 @@ +/* Python Code Editor Styles */ +.python-code-editor { + border: 1px solid #333; + border-radius: 8px; + background: #1e1e1e; + color: #d4d4d4; + margin: 10px 0; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 15px; + background: #2d2d30; + border-bottom: 1px solid #333; + border-radius: 8px 8px 0 0; +} + +.editor-header h3 { + margin: 0; + color: #d4d4d4; + font-size: 16px; + font-weight: 500; +} + +.execute-btn { + background: #007acc; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +.execute-btn:hover:not(:disabled) { + background: #005a9e; +} + +.execute-btn:disabled { + background: #555; + cursor: not-allowed; +} + +.editor-container { + border-radius: 0 0 8px 8px; + overflow: hidden; +} + +.execution-result { + margin-top: 10px; + padding: 15px; + border-radius: 4px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.execution-result.success { + background: #1e3a1e; + border: 1px solid #4caf50; +} + +.execution-result.error { + background: #3a1e1e; + border: 1px solid #f44336; +} + +.result-header { + margin-bottom: 10px; + font-weight: bold; +} + +.output pre { + background: #2d2d30; + padding: 10px; + border-radius: 4px; + margin: 5px 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +.variables ul, +.functions ul { + list-style: none; + padding: 0; + margin: 5px 0; +} + +.variables li, +.functions li { + background: #2d2d30; + padding: 5px 10px; + margin: 2px 0; + border-radius: 3px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.variables code, +.functions code { + color: #9cdcfe; + background: transparent; +} + +.error-message pre { + background: #3a1e1e; + padding: 10px; + border-radius: 4px; + margin: 5px 0; + white-space: pre-wrap; + word-wrap: break-word; + color: #ff9999; +} + +/* Dark theme adjustments */ +.python-code-editor .monaco-editor { + background: #1e1e1e !important; +} + +.python-code-editor .monaco-editor .margin { + background: #1e1e1e !important; +} diff --git a/src/PythonCodeEditor.jsx b/src/PythonCodeEditor.jsx new file mode 100644 index 00000000..36c00d13 --- /dev/null +++ b/src/PythonCodeEditor.jsx @@ -0,0 +1,121 @@ +import { useState, useCallback } from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { vscodeDark } from '@uiw/codemirror-theme-vscode'; +import { python } from '@codemirror/lang-python'; +import { getApiEndpoint } from './config.js'; + + +const PythonCodeEditor = ({ + code = "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n", + onCodeChange, + onExecute, + height = "400px" +}) => { + const [isExecuting, setIsExecuting] = useState(false); + const [executionResult, setExecutionResult] = useState(null); + + const handleCodeChange = useCallback((newCode) => { + if (onCodeChange) { + onCodeChange(newCode); + } + }, [onCodeChange]); + + const executeCode = async () => { + setIsExecuting(true); + setExecutionResult(null); + + try { + const response = await fetch(getApiEndpoint('/execute-python'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code }) + }); + + const result = await response.json(); + setExecutionResult(result); + + if (onExecute) { + onExecute(result); + } + } catch (error) { + setExecutionResult({ + success: false, + error: `Network error: ${error.message}` + }); + } finally { + setIsExecuting(false); + } + }; + + return ( +
+
+ {/*

Python Code Editor

*/} + +
+ +
+ +
+ + {executionResult && ( +
+
+ {executionResult.success ? 'Execution Result:' : 'Error:'} +
+ + {executionResult.success ? ( +
+ {executionResult.output && ( +
+ Output: +
{executionResult.output}
+
+ )} + + {executionResult.variables && Object.keys(executionResult.variables).length > 0 && ( +
+ Variables added to namespace: +
    + {Object.entries(executionResult.variables).map(([name, value]) => ( +
  • + {name} = {JSON.stringify(value)} +
  • + ))} +
+
+ )} + + {executionResult.functions && executionResult.functions.length > 0 && ( +
+ Functions added to namespace: +
    + {executionResult.functions.map((funcName) => ( +
  • + {funcName}() +
  • + ))} +
+
+ )} +
+ ) : ( +
+
{executionResult.error}
+
+ )} +
+ )} +
+ ); +}; + +export default PythonCodeEditor; diff --git a/src/backend.py b/src/backend.py index 047e1781..040ee77d 100644 --- a/src/backend.py +++ b/src/backend.py @@ -8,7 +8,8 @@ import plotly import json as plotly_json import inspect -import numpy as np +import io +from contextlib import redirect_stdout, redirect_stderr from .convert_to_python import convert_graph_to_python from .pathsim_utils import make_pathsim_model, map_str_to_object @@ -320,6 +321,80 @@ def run_pathsim(): return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 +@app.route("/execute-python", methods=["POST"]) +def execute_python(): + """Execute Python code and returns variables/functions.""" + + try: + data = request.json + code = data.get("code", "") + + if not code.strip(): + return jsonify({"success": False, "error": "No code provided"}), 400 + + # Create a temporary namespace that includes current eval_namespace + temp_namespace = {} + # temp_namespace.update(globals()) + + # Capture stdout and stderr + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() + + try: + with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture): + exec(code, temp_namespace) + + # Capture any output + output = stdout_capture.getvalue() + error_output = stderr_capture.getvalue() + + if error_output: + return jsonify({"success": False, "error": error_output}), 400 + + # Find new variables and functions + vars = set(temp_namespace.keys()) + # new_vars = vars_after - vars_before + + # Filter out built-ins and modules, keep user-defined items + user_variables = {} + user_functions = [] + + for var_name in vars: + if not var_name.startswith("__"): + value = temp_namespace[var_name] + if callable(value) and hasattr(value, "__name__"): + user_functions.append(var_name) + else: + # Try to serialize the value for display + try: + if isinstance(value, (int, float, str, bool, list, dict)): + user_variables[var_name] = value + else: + user_variables[var_name] = str(value) + except Exception: + user_variables[var_name] = ( + f"<{type(value).__name__} object>" + ) + + return jsonify( + { + "success": True, + "output": output if output else None, + "variables": user_variables, + "functions": user_functions, + "message": f"Executed successfully. Added {len(user_variables)} variables and {len(user_functions)} functions to namespace.", + } + ) + + except SyntaxError as e: + return jsonify({"success": False, "error": f"Syntax Error: {str(e)}"}), 400 + except Exception as e: + return jsonify({"success": False, "error": f"Runtime Error: {str(e)}"}), 400 + + except Exception as e: + return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 + + # Catch-all route for React Router (SPA routing) @app.route("/") def catch_all(path): diff --git a/src/custom_pathsim_blocks.py b/src/custom_pathsim_blocks.py index b01e9971..c2b172e7 100644 --- a/src/custom_pathsim_blocks.py +++ b/src/custom_pathsim_blocks.py @@ -255,6 +255,7 @@ def create_reset_events(self) -> list[pathsim.blocks.Schedule]: # FESTIM wall +from pathsim.utils.register import Register class FestimWall(Block): @@ -270,6 +271,9 @@ def __init__( except ImportError: raise ImportError("festim is needed for FestimWall node.") + self.inputs = Register(size=2) + self.outputs = Register(size=2) + self.thickness = thickness self.temperature = temperature self.surface_area = surface_area @@ -344,6 +348,7 @@ def update(self, t): inputs = self.inputs.to_array() c_0 = inputs[self.name_to_input_port["c_0"]] c_L = inputs[self.name_to_input_port["c_L"]] + # print(c_0, c_L) if t == 0.0: flux_0, flux_L = 0, 0 diff --git a/src/pathsim_utils.py b/src/pathsim_utils.py index 6bb583f7..110cd2eb 100644 --- a/src/pathsim_utils.py +++ b/src/pathsim_utils.py @@ -36,6 +36,7 @@ from flask import jsonify import inspect + NAME_TO_SOLVER = { "SSPRK22": pathsim.solvers.SSPRK22, "SSPRK33": pathsim.solvers.SSPRK33, @@ -107,7 +108,7 @@ def find_block_by_id(block_id: str, blocks: list[Block]) -> Block: def make_global_variables(global_vars): # Validate and exec global variables so that they are usable later in this script. # Return a namespace dictionary containing the global variables - global_namespace = globals() + global_namespace = globals().copy() for var in global_vars: var_name = var.get("name", "").strip() @@ -136,7 +137,7 @@ def make_global_variables(global_vars): # Execute in global namespace for backwards compatibility exec(f"{var_name} = {var_value}", global_namespace) # Also store in local namespace for eval calls - global_namespace[var_name] = eval(var_value) + global_namespace[var_name] = eval(var_value, global_namespace) except Exception as e: raise ValueError(f"Error setting global variable '{var_name}': {str(e)}") @@ -199,8 +200,6 @@ def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: Returns: The constructed block object. """ - if eval_namespace is None: - eval_namespace = globals() if node["type"] not in map_str_to_object: raise ValueError(f"Unknown block type: {node['type']}") @@ -271,16 +270,23 @@ def get_parameters_for_event_class( if k in ["func_evt", "func_act"]: # Execute func code if provided func_code = event_data[k] - if func_code: - try: - exec(func_code, event_namespace) - if k not in event_namespace: - raise ValueError(f"{k} function not found after execution") - except Exception as e: - raise ValueError(f"Error executing {k} code: {str(e)}") - else: + if not func_code: raise ValueError(f"{k} code is required but not provided") + + if func_code in event_namespace: + parameters[k] = event_namespace[func_code] + # parameters[f"{k}_identifier"] = func_code + continue + + try: + exec(func_code, event_namespace) + if k not in event_namespace: + raise ValueError(f"{k} function not found after execution") + except Exception as e: + raise ValueError(f"Error executing {k} code: {str(e)}") + parameters[k] = event_namespace[k] + # parameters[f"{k}_identifier"] = k else: parameters[k] = eval(user_input, event_namespace) return parameters @@ -521,7 +527,16 @@ def make_pathsim_model(graph_data: dict) -> tuple[Simulation, float]: global_namespace = make_global_variables(global_vars) # Create a combined namespace that includes built-in functions and global variables - eval_namespace = {**globals(), **global_namespace} + eval_namespace = globals().copy() + eval_namespace.update(global_namespace) + + # Execute python code first to define any variables that blocks might need + python_code = graph_data.get("pythonCode", "") + if python_code: + try: + exec(python_code, eval_namespace) + except Exception as e: + return jsonify({"error": f"Error executing Python code: {str(e)}"}), 400 solver_prms, extra_params, duration = make_solver_params( solver_prms, eval_namespace @@ -543,6 +558,7 @@ def make_pathsim_model(graph_data: dict) -> tuple[Simulation, float]: for node in nodes: var_name = make_var_name(node) eval_namespace[var_name] = find_block_by_id(node["id"], blocks) + events += make_events(graph_data.get("events", []), eval_namespace) # Create the simulation