diff --git a/package-lock.json b/package-lock.json index 24e0e16..81a8a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,16 +10,20 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.5.0", - "@code0-tech/triangulum": "^0.7.0", + "@code0-tech/triangulum": "^0.8.0", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lint": "^6.9.5", "date-fns": "^4.1.0", "graphql": "^16.12.0", "graphql-tag": "^2.12.6", "ldrs": "^1.1.9", "next": "16.1.6", + "prettier": "^3.8.1", "react": "19.2.4", "react-dom": "19.2.4", "rxjs": "^7.8.2", - "sass": "^1.93.3" + "sass": "^1.93.3", + "use-debounce": "^10.1.1" }, "devDependencies": { "@types/node": "^24.0.0", @@ -367,13 +371,13 @@ "peer": true }, "node_modules/@code0-tech/triangulum": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.7.0.tgz", - "integrity": "sha512-OnO26DypbGk/CiFn7UkXCry7BVaMGTeT6hymsDgpvVdrT7tNYGFh4lAlT6579+QQ5ytMZJ5DAhe3If/zAjaTew==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.8.0.tgz", + "integrity": "sha512-wiRdg5JzbE0QartNlGxPWWmVYWGf1ZPCByNd4L/JiR5sYyPKxaviXcx/LUpY3mntGoiiLLtxlwcUIrLHk4XYeQ==", "peerDependencies": { "@code0-tech/sagittarius-graphql-types": "0.0.0-experimental-2414125487-fbc8a5ec8a2dd07cc76957d4315281c246e98d57", "@typescript/vfs": "^1.6.4", - "typescript": "^5.9.3" + "typescript": "^5.9.3 || ^6.0.2" } }, "node_modules/@codemirror/autocomplete": { @@ -401,6 +405,21 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/lang-json": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", @@ -428,9 +447,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.4.tgz", - "integrity": "sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==", + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", "license": "MIT", "peer": true, "dependencies": { @@ -1307,6 +1326,17 @@ "@lezer/common": "^1.3.0" } }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/json": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", @@ -2944,17 +2974,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", - "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/type-utils": "8.52.0", - "@typescript-eslint/utils": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -2967,8 +2997,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.52.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.57.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -2983,17 +3013,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", - "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "engines": { @@ -3004,19 +3034,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", - "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.52.0", - "@typescript-eslint/types": "^8.52.0", + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "engines": { @@ -3031,14 +3061,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", - "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0" + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3049,9 +3079,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", - "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", "dev": true, "license": "MIT", "engines": { @@ -3066,15 +3096,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", - "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -3086,14 +3116,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", - "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", "dev": true, "license": "MIT", "engines": { @@ -3105,18 +3135,18 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", - "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.52.0", - "@typescript-eslint/tsconfig-utils": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/visitor-keys": "8.52.0", + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", - "minimatch": "^9.0.5", + "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" @@ -3132,36 +3162,49 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3172,16 +3215,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", - "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.52.0", - "@typescript-eslint/types": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0" + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3191,19 +3234,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", - "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.52.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5030,13 +5073,13 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5068,19 +5111,6 @@ "node": "20 || >=22" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/espree": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz", @@ -6778,6 +6808,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7656,9 +7701,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -7806,16 +7851,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.52.0.tgz", - "integrity": "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==", + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.52.0", - "@typescript-eslint/parser": "8.52.0", - "@typescript-eslint/typescript-estree": "8.52.0", - "@typescript-eslint/utils": "8.52.0" + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7825,7 +7870,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -7952,6 +7997,18 @@ } } }, + "node_modules/use-debounce": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.1.tgz", + "integrity": "sha512-kvds8BHR2k28cFsxW8k3nc/tRga2rs1RHYCqmmGqb90MEeE++oALwzh2COiuBLO1/QXiOuShXoSN2ZpWnMmvuQ==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", diff --git a/package.json b/package.json index 76b2ab5..4ad9e15 100644 --- a/package.json +++ b/package.json @@ -16,16 +16,20 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.5.0", - "@code0-tech/triangulum": "^0.7.0", + "@code0-tech/triangulum": "^0.8.0", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lint": "^6.9.5", "date-fns": "^4.1.0", "graphql": "^16.12.0", "graphql-tag": "^2.12.6", + "ldrs": "^1.1.9", "next": "16.1.6", + "prettier": "^3.8.1", "react": "19.2.4", "react-dom": "19.2.4", "rxjs": "^7.8.2", "sass": "^1.93.3", - "ldrs": "^1.1.9" + "use-debounce": "^10.1.1" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx index e8ae80e..eab0445 100644 --- a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx @@ -43,7 +43,7 @@ export const DataTypeInputComponent: React.FC = (pr ) const dataTypeVariant = React.useMemo( - () => getTypeVariant(types.parameters[parameterIndex], dataTypeService.values()), + () => getTypeVariant(types.parameters[parameterIndex], dataTypeService.values())[0].variant, [dataTypeStore, types] ) diff --git a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeEditorInput.tsx b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeEditorInput.tsx new file mode 100644 index 0000000..8471832 --- /dev/null +++ b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeEditorInput.tsx @@ -0,0 +1,367 @@ +import React, {useCallback, useMemo} from "react"; +import {tags as t} from "@lezer/highlight"; +import {Badge, hashToColor, useService, useStore} from "@code0-tech/pictor"; +import {Editor, EditorInputProps, EditorTokenHighlights} from "@code0-tech/pictor/dist/components/editor/Editor"; +import {StreamLanguage} from "@codemirror/language"; +import {CompletionContext, CompletionResult} from "@codemirror/autocomplete"; +import {DatatypeService} from "@edition/datatype/services/Datatype.service"; + +export interface DataTypeTypeEditorInputProps extends EditorInputProps { + value: string | null + isTechnicalType?: boolean +} + +export const DataTypeTypeEditorInput: React.FC = (props) => { + + const {value, onChange, isTechnicalType = false, ...rest} = props + + const dataTypeService = useService(DatatypeService) + const dataTypeStore = useStore(DatatypeService) + + const dataTypes = useMemo( + () => dataTypeService.values(), + [dataTypeStore] + ) + const typeSet = useMemo( + () => new Set(dataTypes.map(t => t.identifier)), + [dataTypes] + ); + + const dataTypeMap = useMemo( + () => new Map(dataTypes.map(t => [t.identifier, t])), + [dataTypes] + ); + + const dataTypeOptions = useMemo( + () => dataTypes.map(t => ({ + label: t.identifier!, + type: "", + apply: `${t.identifier}${t.genericKeys && t.genericKeys.length > 0 ? ` of (${t.genericKeys.join(", ")})` : ""}` + })), + [dataTypes] + ); + + const typeLanguage = useMemo( + () => { + return StreamLanguage.define({ + token(stream) { + if (stream.eatSpace()) return null; + + // Keywords + if (stream.match(/\bis\b/)) { + return "keyword"; + } + if (stream.match(/\bof\b/)) { + return "keyword"; + } + if (stream.match(/\bcontains\b/)) { + return "keyword"; + } + + // Operators + if (stream.match(/\band\b/)) { + return "operator"; + } + if (stream.match(/\bor\b/)) { + return "operator"; + } + + // Parentheses & Punctuation + if (stream.match(/[()]/)) return "punctuation"; + + // Word recognition with lookahead + const wordMatch = stream.match(/^[a-zA-Z0-9_-]+/, false); + if (wordMatch) { + // @ts-ignore + const word = wordMatch[0]; + + // Check if it's a known type + if (typeSet.has(word)) { + stream.match(/^[a-zA-Z0-9_-]+/); + return "typeName"; + } + + // Check if "is" or "contains" follows the word + const lookahead = stream.string.substring(stream.pos + word.length); + if (/^\s+(is|contains)\b/.test(lookahead)) { + stream.match(/^[a-zA-Z0-9_-]+/); + return "propertyName"; + } + + // Fallback: consume the word + stream.match(/^[a-zA-Z0-9_-]+/); + return "propertyName"; + } + + stream.next(); + return null; + } + }) + }, + [typeSet] + ) + + const suggestions = useCallback( + (context: CompletionContext): CompletionResult | null => { + const word = context.matchBefore(/\w+/); + const fullBefore = context.matchBefore(/.*?\s*$/); + const textBefore = fullBefore ? fullBefore.text : ""; + const isExplicit = context.explicit; + + // Get current line and previous lines for context + const line = context.state.doc.lineAt(context.pos); + const isNewLine = line.from === context.pos; + + let prevLineText = ""; + let prevLineNumber = line.number - 1; + while (prevLineNumber >= 1) { + const l = context.state.doc.line(prevLineNumber); + if (l.text.trim()) { + prevLineText = l.text.trim().toLowerCase(); + break; + } + prevLineNumber--; + } + + const isUnderContains = prevLineText.endsWith("contains"); + const isAfterContainsOnSameLine = textBefore.toLowerCase().trim().endsWith("contains"); + + // 1. If we are after 'contains' (either same line or next line), don't suggest types + if (isAfterContainsOnSameLine || (isNewLine && isUnderContains)) { + return null; + } + + // Helper to get the last word before potential trailing spaces + const lastWordMatch = textBefore.trim().match(/(\w+)$/); + const lastWord = lastWordMatch ? lastWordMatch[1] : ""; + const hasSpaceAfterLastWord = /\s$/.test(textBefore); + + // 2. If input is empty at the very start of the doc + if (context.pos === 0 || (line.number === 1 && textBefore.trim() === "" && !isExplicit && !word)) { + return { + from: context.pos, + options: dataTypeOptions + }; + } + + // 3. If we just typed a word + if (lastWord) { + if (hasSpaceAfterLastWord) { + if (typeSet.has(lastWord)) { + // Space after recognized type -> suggest operators + return { + from: context.pos, + options: [ + {label: "and is", type: "operator"}, + {label: "or is", type: "operator"}, + {label: "and contains", type: "operator"}, + {label: "or contains", type: "operator"} + ] + }; + } else { + // Space after a word that is NOT a type (likely a property name) -> suggest 'is' / 'contains' + const typeExpectingKeywords = ["is", "contains", "or", "and", "of"]; + if (!typeExpectingKeywords.includes(lastWord.toLowerCase())) { + return { + from: context.pos, + options: [ + {label: "is", type: "keyword"}, + {label: "contains", type: "keyword"} + ] + }; + } + } + } else { + // Still touching the word -> continue showing types (filtering) + return { + from: word ? word.from : context.pos, + options: dataTypeOptions + }; + } + } + + // 3. If we are after a keyword that expects a type + const typeExpectingKeywords = ["is", "contains", "or", "and", "of"]; + if (typeExpectingKeywords.some(kw => textBefore.toLowerCase().trim().endsWith(kw)) && hasSpaceAfterLastWord) { + return { + from: word ? word.from : context.pos, + options: dataTypeOptions + }; + } + + // 4. If we are currently typing a word (and it's not a known type yet or we are filtering) + if (word) { + return { + from: word.from, + options: dataTypeOptions + }; + } + + // 5. Default: if there is a space and we don't know what to do, show operators + if (hasSpaceAfterLastWord) { + return { + from: context.pos, + options: [ + {label: "and is", type: "operator"}, + {label: "or is", type: "operator"}, + {label: "and contains", type: "operator"}, + {label: "or contains", type: "operator"} + ] + }; + } + + // 6. Explicit trigger + if (isExplicit) { + return { + from: context.pos, + options: dataTypeOptions + }; + } + + return null; + }, + [dataTypeOptions, typeSet] + ) + + const tokenHighlights: EditorTokenHighlights = useMemo( + () => ({ + // Optimization: The badge rendering was happening inside the editor's high-frequency render path. + // Returning a stable structure or minimizing internal renders within the token highlight is key. + typeName: ({content}: { content: string }) => { + const isRealType = dataTypeMap.has(content); + if (!isRealType) return null; + return + {content} + + }, + }), + [dataTypeMap] + ) + + // Define the initial text based on whether the input is technical TS or already the human language + const displayValue = useMemo(() => { + if (isTechnicalType) { + return tsToHuman(value ?? "") + } + return value ?? "" + }, [value, isTechnicalType]); + + return +} + +function tsToHuman(ts: string): string { + if (!ts) return "" + + const transform = (str: string, level: number = 0): string => { + str = str.trim() + const nextIndent = " ".repeat(level + 1) + + // Handle Parentheses: ( ... ) + if (str.startsWith("(") && str.endsWith(")")) { + const inner = str.slice(1, -1).trim(); + const transformedInner = transform(inner, level); + return `(${transformedInner})`; + } + + // Handle Objects: { key: value, ... } -> key is/contains ... and ... + if (str.startsWith("{") && str.endsWith("}")) { + const inner = str.slice(1, -1).trim() + const pairs: string[] = [] + let bLevel = 0 + let lastIndex = 0 + for (let i = 0; i < inner.length; i++) { + if (inner[i] === "{" || inner[i] === "(" || inner[i] === "<") bLevel++ + else if (inner[i] === "}" || inner[i] === ")" || inner[i] === ">") bLevel-- + else if (inner[i] === "," && bLevel === 0) { + pairs.push(inner.substring(lastIndex, i).trim()) + lastIndex = i + 1 + } + } + pairs.push(inner.substring(lastIndex).trim()) + + const transformedPairs = pairs.filter(p => p.length > 0).map(pair => { + const colonIndex = pair.indexOf(":") + if (colonIndex === -1) return transform(pair, level + 1) + const key = pair.substring(0, colonIndex).trim() + const value = pair.substring(colonIndex + 1).trim() + + if (value.startsWith("{")) { + return `\n${nextIndent}${key} contains ${transform(value, level + 1)}` + } else { + const transformedVal = transform(value, level + 1) + return `\n${nextIndent}${key} is ${transformedVal}` + } + }) + + return transformedPairs.join("") + } + + // Handle Unions: A | B -> A or is B / A or contains B + const unionParts = [] + let lastIdx = 0 + let bLevel = 0 + for (let i = 0; i < str.length; i++) { + if (str[i] === "{" || str[i] === "<" || str[i] === "(") bLevel++ + else if (str[i] === "}" || str[i] === ">" || str[i] === ")") bLevel-- + else if (str[i] === "|" && bLevel === 0) { + unionParts.push(str.substring(lastIdx, i).trim()) + lastIdx = i + 1 + } + } + unionParts.push(str.substring(lastIdx).trim()) + if (unionParts.length > 1) { + return unionParts.map((s, idx) => { + const t = transform(s, level) + const prefix = idx === 0 ? "" : (s.trim().startsWith("{") ? " or contains " : " or is ") + const content = (t.includes("\n") || (t.includes(" ") && !t.startsWith("("))) && !t.startsWith("\n") ? `(${t.trim()})` : t + return `${prefix}${content}` + }).join("") + } + + // Handle Intersections: A & B -> A and is B / A and contains B + const intersectParts = [] + lastIdx = 0 + bLevel = 0 + for (let i = 0; i < str.length; i++) { + if (str[i] === "{" || str[i] === "<" || str[i] === "(") bLevel++ + else if (str[i] === "}" || str[i] === ">" || str[i] === ")") bLevel-- + else if (str[i] === "&" && bLevel === 0) { + intersectParts.push(str.substring(lastIdx, i).trim()) + lastIdx = i + 1 + } + } + intersectParts.push(str.substring(lastIdx).trim()) + if (intersectParts.length > 1) { + return intersectParts.map((s, idx) => { + const t = transform(s, level) + const prefix = idx === 0 ? "" : (s.trim().startsWith("{") ? " and contains " : " and is ") + const content = (t.includes("\n") || (t.includes(" ") && !t.startsWith("("))) && !t.startsWith("\n") ? `(${t.trim()})` : t + return `${prefix}${content}` + }).join("") + } + + // Handle Generics: NAME -> NAME of (TYPE) + const genericMatch = str.match(/^(\w+)<(.+)>$/) + if (genericMatch) { + const name = genericMatch[1] + const inner = genericMatch[2] + const transformedInner = transform(inner, level) + const needsParen = transformedInner.includes("\n") || (transformedInner.includes(" ") && !transformedInner.startsWith("(")); + return `${name} of ${needsParen ? `(${transformedInner.trim()})` : transformedInner}` + } + + return str + } + + return transform(ts).trim() +} \ No newline at end of file diff --git a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputComponent.tsx index eb26a54..9f97511 100644 --- a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputComponent.tsx @@ -2,33 +2,12 @@ import React from "react" import {IconEdit, IconX} from "@tabler/icons-react" import "../type/DataTypeTypeInputComponent.style.scss" import {Flow, LiteralValue} from "@code0-tech/sagittarius-graphql-types"; -import { - Button, - Card, - Flex, - InputDescription, - InputLabel, - InputMessage, - InputProps, - Text, - useService, - useStore -} from "@code0-tech/pictor"; +import {Button, Card, Flex, InputDescription, InputLabel, InputMessage, InputProps, Text} from "@code0-tech/pictor"; import {ButtonGroup} from "@code0-tech/pictor/dist/components/button-group/ButtonGroup"; -import {getTypeFromValue, getValueFromType} from "@code0-tech/triangulum"; -import {DatatypeService} from "@edition/datatype/services/Datatype.service"; -import { - DataTypeTypeInputTreeComponent -} from "@edition/datatype/components/inputs/datatype/DataTypeTypeInputTreeComponent"; import { DataTypeTypeInputEditDialogComponent } from "@edition/datatype/components/inputs/datatype/DataTypeTypeInputEditDialogComponent"; - -export interface EditableJSONEntry { - key: string - value: LiteralValue | null - path: string[] -} +import {DataTypeTypeEditorInput} from "@edition/datatype/components/inputs/datatype/DataTypeTypeEditorInput"; export interface DataTypeJSONInputComponentProps extends Omit, "wrapperComponent" | "type"> { @@ -42,55 +21,34 @@ export const DataTypeTypeInputComponent: React.FC { - return initialValue - }, []) - const initialLiteralValue = React.useMemo(() => { - return getValueFromType(initialValue, datatypeService.values()) - }, [datatypeStore, initialValue]) - - const [literalValue, setLiteralValue] = React.useState(initialLiteralValue) + const [literalValue, setLiteralValue] = React.useState(initialValue) const [editDialogOpen, setEditDialogOpen] = React.useState(false) - const [editEntry, setEditEntry] = React.useState(null) - const [collapsedState, setCollapsedStateRaw] = React.useState>({}) - - const setCollapsedState = (path: string[], collapsed: boolean) => { - setCollapsedStateRaw(prev => ({...prev, [path.join(".")]: collapsed})) - } - const handleEntryClick = (entry: EditableJSONEntry) => { - setEditEntry(entry) - setEditDialogOpen(true) - } - - const handleClear = React.useCallback(() => { - setLiteralValue(getValueFromType(initialType, datatypeService.values())) - }, []) + const handleClear = React.useCallback( + () => { + setLiteralValue("") + }, + [null] + ) React.useEffect(() => { - const type = getTypeFromValue(literalValue) - formValidation?.setValue(type) + formValidation?.setValue(literalValue) setTimeout(() => { // @ts-ignore onChange?.() - }, 500) + }, 100) }, [literalValue]) return ( <> setEditDialogOpen(open)} - onObjectChange={v => { - console.log(v) + onTypeChange={v => { setLiteralValue(v ?? null) }} /> @@ -113,12 +71,17 @@ export const DataTypeTypeInputComponent: React.FC - + {!formValidation?.valid && formValidation?.notValidMessage && ( diff --git a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputEditDialogComponent.tsx b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputEditDialogComponent.tsx index 2128c64..23c820c 100644 --- a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputEditDialogComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputEditDialogComponent.tsx @@ -1,181 +1,470 @@ -import React from "react" -import {DataTypeTypeInputTreeComponent} from "./DataTypeTypeInputTreeComponent"; -import {LiteralValue} from "@code0-tech/sagittarius-graphql-types"; -import {EditableJSONEntry} from "@edition/datatype/components/inputs/json/DataTypeJSONInputComponent"; +import React, {useEffect, useMemo, useState} from "react" import { - Badge, Button, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, - Flex, hashToColor, ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, Spacing, Text + Flex, + SegmentedControl, + SegmentedControlItem, + Text, + useService, + useStore } from "@code0-tech/pictor"; import {IconX} from "@tabler/icons-react"; -import {Editor, EditorTokenHighlights} from "@code0-tech/pictor/dist/components/editor/Editor"; +import {Editor} from "@code0-tech/pictor/dist/components/editor/Editor"; import {Layout} from "@code0-tech/pictor/dist/components/layout/Layout"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@code0-tech/pictor/dist/components/resizable/Resizable"; -import {CompletionContext, CompletionResult} from "@codemirror/autocomplete"; -import {syntaxTree} from "@codemirror/language"; +import {DatatypeService} from "@edition/datatype/services/Datatype.service"; +import {LiteralValue} from "@code0-tech/sagittarius-graphql-types"; +import {useTypeExtractionAction, useValueExtractionAction} from "@edition/flow/components/FlowWorkerProvider"; +import {DataTypeTypeEditorInput} from "@edition/datatype/components/inputs/datatype/DataTypeTypeEditorInput"; +import {DataTypeJSONInputTreeComponent} from "@edition/datatype/components/inputs/json/DataTypeJSONInputTreeComponent"; export interface DataTypeJSONInputEditDialogComponentProps { open: boolean - entry: EditableJSONEntry | null - value: LiteralValue | null + value: string | null onOpenChange?: (open: boolean) => void - onObjectChange?: (object: LiteralValue | null) => void + onTypeChange?: (type: string | null) => void } -function getValueAtPath(obj: LiteralValue | null, path: string[]): unknown { - if (!obj || !Array.isArray(path) || path.length === 0) return obj?.value - // Traverse .value recursively if nested - let current: any = obj.value - for (const key of path) { - if (current && typeof current === 'object' && key in current) { - current = current[key] - } else { - return undefined + +function humanToTs(input: string): string { + if (!input) return "" + + const findClosingBracket = (s: string, start: number) => { + let level = 1; + for (let i = start + 1; i < s.length; i++) { + if (s[i] === "(") level++; + else if (s[i] === ")") level--; + if (level === 0) return i; } - } - return current + return -1; + }; + + const getIndent = (line: string) => { + const match = line.match(/^(\s*)/); + return match ? match[0].length : 0; + }; + + const transform = (str: string): string => { + if (!str || !str.trim()) return ""; + let result = str.trim(); + + // Handle leading/trailing parentheses for the whole block + if (result.startsWith("(") && result.endsWith(")")) { + const innerMatch = findClosingBracket(result, 0); + if (innerMatch === result.length - 1) { + return `(${transform(result.substring(1, result.length - 1))})`; + } + } + + // 1. Handle Generics: NAME of (...) or NAME of TYPE + let genericChanged = true; + while (genericChanged) { + genericChanged = false; + const ofMatch = result.match(/(\b(?!is\b|contains\b|and\b|or\b)\w+\b)\s+of\s+/i); + if (ofMatch && ofMatch.index !== undefined) { + const start = ofMatch.index; + const name = ofMatch[1]; + const afterOf = result.substring(start + ofMatch[0].length); + + if (afterOf.startsWith("(")) { + const end = findClosingBracket(afterOf, 0); + if (end !== -1) { + const inner = transform(afterOf.substring(1, end)); + result = result.substring(0, start) + `${name}<${inner}>` + afterOf.substring(end + 1); + genericChanged = true; + continue; + } + } + const wordMatch = afterOf.match(/^(\w+)/); + if (wordMatch) { + const word = wordMatch[1]; + result = result.substring(0, start) + `${name}<${word}>` + afterOf.substring(start + ofMatch[0].length + word.length); + genericChanged = true; + } + } + } + + let processed = ""; + let remaining = result; + while (remaining.length > 0) { + remaining = remaining.trim(); + if (!remaining) break; + + // Check for stand-alone operators first: or is, or contains, and is, and contains + const opSpecialMatch = remaining.match(/^(and|or)\s+(is|contains)\b/i); + if (opSpecialMatch) { + const op = opSpecialMatch[1].toLowerCase() === "and" ? "&" : "|"; + const keyword = opSpecialMatch[2].toLowerCase(); + let after = remaining.substring(opSpecialMatch[0].length).trim(); + let val = ""; + if (after.startsWith("(")) { + const end = findClosingBracket(after, 0); + if (end !== -1) { + val = transform(after.substring(1, end)); + processed += ` ${op} ${keyword === "contains" ? `{ ${val} }` : (val.includes("&") || val.includes("|") ? `(${val})` : val)}`; + remaining = after.substring(end + 1); + } else { + val = transform(after.substring(1)); + processed += ` ${op} ${keyword === "contains" ? `{ ${val} }` : val}`; + remaining = ""; + } + } else { + let k = 0; + let bLevel = 0; + for (; k < after.length; k++) { + if (after[k] === "(" || after[k] === "<" || after[k] === "{") bLevel++; + else if (after[k] === ")" || after[k] === ">" || after[k] === "}") bLevel--; + else if (bLevel === 0) { + if (/^\s+(and|or)\b/i.test(after.substring(k))) break; + if (/^\s+(\b(?!is\b|contains\b|and\b|or\b)\w+\b)\s+(is|contains)\b/i.test(after.substring(k))) break; + } + } + val = transform(after.substring(0, k).trim()); + processed += ` ${op} ${keyword === "contains" ? `{ ${val} }` : val}`; + remaining = after.substring(k); + } + } + + // Property: key contains / key is (exclude keywords from being keys) + const propMatch = remaining.match(/^(\b(?!is\b|contains\b|and\b|or\b)\w+\b)\s+(contains|is)\b/i); + if (propMatch) { + const key = propMatch[1]; + const keyword = propMatch[2].toLowerCase(); + let after = remaining.substring(propMatch[0].length).trim(); + let val = ""; + if (after.startsWith("(")) { + const end = findClosingBracket(after, 0); + if (end !== -1) { + val = transform(after.substring(1, end)); + if (processed.length > 0 && !/[{&|]\s*$/.test(processed.trim())) processed += ", "; + processed += keyword === "contains" ? `${key}: { ${val} }` : `${key}: ${val.includes("&") || val.includes("|") ? `(${val})` : val}`; + remaining = after.substring(end + 1); + } else { + val = transform(after.substring(1)); + if (processed.length > 0 && !/[{&|]\s*$/.test(processed.trim())) processed += ", "; + processed += keyword === "contains" ? `${key}: { ${val} }` : `${key}: ${val}`; + remaining = ""; + } + } else { + let k = 0; + let bLevel = 0; + for (; k < after.length; k++) { + if (after[k] === "(" || after[k] === "<" || after[k] === "{") bLevel++; + else if (after[k] === ")" || after[k] === ">" || after[k] === "}") bLevel--; + else if (bLevel === 0) { + if (/^\s+(and|or)\b/i.test(after.substring(k))) break; + if (/^\s+(\b(?!is\b|contains\b|and\b|or\b)\w+\b)\s+(is|contains)\b/i.test(after.substring(k))) break; + } + } + val = transform(after.substring(0, k).trim()); + if (processed.length > 0 && !/[{&|]\s*$/.test(processed.trim())) processed += ", "; + processed += keyword === "contains" ? `${key}: { ${val} }` : `${key}: ${val}`; + remaining = after.substring(k); + } + } + + // Fallback for types or remaining keywords + const word = remaining.match(/^([a-zA-Z0-9_<>]+)/); + if (word) { + processed += word[1]; + remaining = remaining.substring(word[1].length); + continue; + } + if (remaining[0] === "(" || remaining[0] === ")") { + processed += remaining[0]; + remaining = remaining.substring(1); + continue; + } + processed += remaining[0]; + remaining = remaining.substring(1); + } + return processed; + }; + + const parseLines = (lines: string[]): string => { + let result = ""; + let i = 0; + + while (i < lines.length) { + let line = lines[i].trim(); + if (!line) { + i++; + continue; + } + const currentIndent = getIndent(lines[i]); + + const propMatch = line.match(/^(\w+)\s+(contains|is)\b(.*)$/i); + if (propMatch) { + const key = propMatch[1]; + const keyword = propMatch[2].toLowerCase(); + let rest = propMatch[3].trim(); + let value = ""; + + if (rest.startsWith("(")) { + let combined = rest; + let j = i; + while (j < lines.length) { + const openIdx = combined.indexOf("("); + const closingIdx = findClosingBracket(combined, openIdx); + if (closingIdx !== -1) { + value = transform(combined.substring(openIdx + 1, closingIdx)); + i = j; + break; + } + j++; + if (j < lines.length) combined += " " + lines[j].trim(); + } + } else { + let subLines: string[] = []; + let j = i + 1; + while (j < lines.length) { + if (lines[j].trim() === "") { + j++; + continue; + } + if (getIndent(lines[j]) > currentIndent) { + subLines.push(lines[j]); + j++; + } else break; + } + if (subLines.length > 0) { + value = parseLines(subLines); + if (rest) { + const restTransformed = transform(rest); + value = restTransformed + (value.trim().startsWith("&") || value.trim().startsWith("|") ? "" : " & ") + value; + } + i = j - 1; + } else { + value = transform(rest); + } + } + + if (result.length > 0 && !/[{&|]\s*$/.test(result.trim())) result += ", "; + result += keyword === "contains" ? `${key}: { ${value} }` : `${key}: ${value}`; + } else { + const transformed = transform(line); + if (result.length > 0 && !/[{&|]\s*$/.test(result.trim()) && !/^\s*[&|]/.test(transformed)) result += ", "; + result += transformed; + } + i++; + } + return result; + }; + + const lines = input.split("\n"); + let final = parseLines(lines).replace(/\s+/g, " ").trim(); + if (final.includes(":") && !final.startsWith("{")) final = `{ ${final} }`; + + return final.replace(/\( /g, "(").replace(/ \)/g, ")") + .replace(/: \s*:/g, ":").replace(/\{\s*:/g, "{") + .replace(/&/g, " & ").replace(/\|/g, " | ") + .replace(/,/g, ", ").replace(/\s+/g, " ").trim(); } -function setValueAtPath(obj: LiteralValue | null, path: string[], value: unknown): LiteralValue | null { - if (!obj) return null - if (path.length === 0) return { ...obj, value } - const [key, ...rest] = path - if (Array.isArray(obj.value)) { - const idx = Number(key) - const newArr = [...obj.value] - if (rest.length > 0 && typeof newArr[idx] === 'object' && newArr[idx] !== null) { - newArr[idx] = setValueAtPath({ ...obj, value: newArr[idx] }, rest, value)?.value - } else { - newArr[idx] = value +function tsToHuman(ts: string): string { + if (!ts) return "" + + const transform = (str: string, level: number = 0): string => { + str = str.trim() + const nextIndent = " ".repeat(level + 1) + + // Handle Parentheses: ( ... ) + if (str.startsWith("(") && str.endsWith(")")) { + const inner = str.slice(1, -1).trim(); + const transformedInner = transform(inner, level); + return `(${transformedInner})`; + } + + // Handle Objects: { key: value, ... } -> key is/contains ... and ... + if (str.startsWith("{") && str.endsWith("}")) { + const inner = str.slice(1, -1).trim() + const pairs: string[] = [] + let bLevel = 0 + let lastIndex = 0 + for (let i = 0; i < inner.length; i++) { + if (inner[i] === "{" || inner[i] === "(" || inner[i] === "<") bLevel++ + else if (inner[i] === "}" || inner[i] === ")" || inner[i] === ">") bLevel-- + else if (inner[i] === "," && bLevel === 0) { + pairs.push(inner.substring(lastIndex, i).trim()) + lastIndex = i + 1 + } + } + pairs.push(inner.substring(lastIndex).trim()) + + const transformedPairs = pairs.filter(p => p.length > 0).map(pair => { + const colonIndex = pair.indexOf(":") + if (colonIndex === -1) return transform(pair, level + 1) + const key = pair.substring(0, colonIndex).trim() + const value = pair.substring(colonIndex + 1).trim() + + if (value.startsWith("{")) { + return `\n${nextIndent}${key} contains ${transform(value, level + 1)}` + } else { + const transformedVal = transform(value, level + 1) + return `\n${nextIndent}${key} is ${transformedVal}` + } + }) + + return transformedPairs.join("") } - return { ...obj, value: newArr } - } else if (typeof obj.value === 'object' && obj.value !== null) { - const newObj = { ...obj.value } - if (rest.length > 0 && typeof newObj[key] === 'object' && newObj[key] !== null) { - newObj[key] = setValueAtPath({ ...obj, value: newObj[key] }, rest, value)?.value - } else { - newObj[key] = value + + // Handle Unions: A | B -> A or is B / A or contains B + const unionParts = [] + let lastIdx = 0 + let bLevel = 0 + for (let i = 0; i < str.length; i++) { + if (str[i] === "{" || str[i] === "<" || str[i] === "(") bLevel++ + else if (str[i] === "}" || str[i] === ">" || str[i] === ")") bLevel-- + else if (str[i] === "|" && bLevel === 0) { + unionParts.push(str.substring(lastIdx, i).trim()) + lastIdx = i + 1 + } + } + unionParts.push(str.substring(lastIdx).trim()) + if (unionParts.length > 1) { + return unionParts.map((s, idx) => { + const t = transform(s, level) + const prefix = idx === 0 ? "" : (s.trim().startsWith("{") ? " or contains " : " or is ") + const content = (t.includes("\n") || (t.includes(" ") && !t.startsWith("("))) && !t.startsWith("\n") ? `(${t.trim()})` : t + return `${prefix}${content}` + }).join("") + } + + // Handle Intersections: A & B -> A and is B / A and contains B + const intersectParts = [] + lastIdx = 0 + bLevel = 0 + for (let i = 0; i < str.length; i++) { + if (str[i] === "{" || str[i] === "<" || str[i] === "(") bLevel++ + else if (str[i] === "}" || str[i] === ">" || str[i] === ")") bLevel-- + else if (str[i] === "&" && bLevel === 0) { + intersectParts.push(str.substring(lastIdx, i).trim()) + lastIdx = i + 1 + } + } + intersectParts.push(str.substring(lastIdx).trim()) + if (intersectParts.length > 1) { + return intersectParts.map((s, idx) => { + const t = transform(s, level) + const prefix = idx === 0 ? "" : (s.trim().startsWith("{") ? " and contains " : " and is ") + const content = (t.includes("\n") || (t.includes(" ") && !t.startsWith("("))) && !t.startsWith("\n") ? `(${t.trim()})` : t + return `${prefix}${content}` + }).join("") } - return { ...obj, value: newObj } - } else { - // Not an object/array, just replace - return { ...obj, value } + + // Handle Generics: NAME -> NAME of (TYPE) + const genericMatch = str.match(/^(\w+)<(.+)>$/) + if (genericMatch) { + const name = genericMatch[1] + const inner = genericMatch[2] + const transformedInner = transform(inner, level) + const needsParen = transformedInner.includes("\n") || (transformedInner.includes(" ") && !transformedInner.startsWith("(")); + return `${name} of ${needsParen ? `(${transformedInner.trim()})` : transformedInner}` + } + + return str } + + return transform(ts).trim() } export const DataTypeTypeInputEditDialogComponent: React.FC = (props) => { const { open, - entry, value, - onObjectChange, + onTypeChange, onOpenChange } = props - const [editOpen, setEditOpen] = React.useState(open) - const [collapsedState, setCollapsedStateRaw] = React.useState>({}) - const [activePath, setActivePath] = React.useState(entry?.path ?? []) - const [editedObject, setEditedObject] = React.useState(value) - const [editorValue, setEditorValue] = React.useState(getValueAtPath(value, entry?.path ?? [])) - const clickTimeout = React.useRef(null) - - React.useEffect(() => { - setEditorValue(getValueAtPath(editedObject, activePath)) - }, [activePath]) + const [editOpen, setEditOpen] = useState(open) + const dataTypeService = useService(DatatypeService) + const dataTypeStore = useStore(DatatypeService) + const valueFromTypeAction = useValueExtractionAction() + const typeFromValueAction = useTypeExtractionAction() + const [humanValue, setHumanValue] = useState("") + const [tsValue, setTsValue] = useState(value ?? "") + const [literalValue, setLiteralValue] = useState({ + __typename: "LiteralValue", + value: {} + } as LiteralValue) + const [editVariant, setEditVariant] = useState<"manual" | "value">("manual") - React.useEffect(() => { - setActivePath(entry?.path ?? []) - setEditedObject(value) - }, [entry]) + const dataTypes = useMemo( + () => dataTypeService.values(), + [dataTypeStore] + ) - React.useEffect(() => { + // Sync from props ONLY when opening the dialog + useEffect(() => { setEditOpen(open) + if (open) { + const initialTs = value ?? "" + setTsValue(initialTs) + setHumanValue(tsToHuman(initialTs)) + // Generate initial literal/JSON for the tree/editor + valueFromTypeAction.execute({ + type: initialTs, + dataTypes: dataTypes + }).then(val => setLiteralValue(val as any)) + } }, [open]) - const setCollapsedState = (path: string[], collapsed: boolean) => { - setCollapsedStateRaw(prev => ({...prev, [path.join(".")]: collapsed})) - } - - const handleEntryClick = (clickedEntry: EditableJSONEntry) => { - if (clickTimeout.current) clearTimeout(clickTimeout.current) - clickTimeout.current = setTimeout(() => { - setActivePath(clickedEntry.path ?? []) - }, 200) - } + // Handler 1: Schema Editor changes + const handleSchemaChange = (human: string) => { + const ts = humanToTs(human) - const handleRuleDoubleClick = (currentPath: string[], isCollapsed: boolean) => { - if (clickTimeout.current) clearTimeout(clickTimeout.current) - setCollapsedState(currentPath, !isCollapsed) - } + if (ts !== tsValue) { + setTsValue(ts) + onTypeChange?.(ts) - const handleEditorChange = (val: unknown) => { - const updated = setValueAtPath(editedObject, activePath, val) - setEditedObject(updated) - onObjectChange?.(updated) + // Sync Tree & JSON Editor (async) + valueFromTypeAction.execute({ + type: ts, + dataTypes: dataTypes + }).then(val => { + setLiteralValue(val as any) + }) + } } - const suggestions = (context: CompletionContext): CompletionResult | null => { - - const word = context.matchBefore(/\w*/) + // Handler 2: JSON Input changes + const handleJsonChange = (json: object) => { + const newLiteral = {__typename: "LiteralValue", value: json} as LiteralValue + setLiteralValue(newLiteral) - if (!word || (word.from === word.to && !context.explicit)) { - return null; - } - - const node = syntaxTree(context.state).resolveInner(context.pos, -1); - const prevNode = syntaxTree(context.state).resolveInner(context.pos, 0); - - if (node.name === "Property" || prevNode.name === "Property") { - return { - from: word.from, - options: [ - { - label: "Text", - type: "type", - apply: `"Text"`, - }, - { - label: "Boolean", - type: "type", - apply: `true`, - }, - { - label: "Number", - type: "type", - apply: `1`, - }, - ] - } - } - return null + typeFromValueAction.execute({ + value: newLiteral, + dataTypes: dataTypes + }).then(type => { + setTsValue(type as string) + onTypeChange?.(type as string) + setHumanValue(tsToHuman(type as string)) + }) } - const tokenHighlights: EditorTokenHighlights = { - bool: ({content}) => { - return - Boolean - - }, - string: ({content}) => { - return - Text - - }, - number: ({content}) => { - return - Number - - } - } + // Use keys to force-refresh editors when switching tabs or when external sync happens + // We use humanValue for the Schema editor to ensure it's stable while typing but refreshes from JSON + const typeEditorInput = + + const jsonInput = return ( onOpenChange?.(open)}> @@ -188,48 +477,42 @@ export const DataTypeTypeInputEditDialogComponent: React.FC - {entry?.key ?? "Edit Object"} - - - - - }> + topContent={ + + {"Edit Type"} + + + + + }> - - - - - - - - - - - - - + { + }} + collapsedState={{}} + setCollapsedState={() => { + }}/> - + {editVariant === "manual" ? typeEditorInput : jsonInput} + v && setEditVariant(v as any)} + style={{transform: "translateX(-50%)", zIndex: 99}}> + + Schema + + + From Value + + @@ -237,5 +520,4 @@ export const DataTypeTypeInputEditDialogComponent: React.FC ) -} - +} \ No newline at end of file diff --git a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputTreeComponent.tsx b/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputTreeComponent.tsx deleted file mode 100644 index 6a51574..0000000 --- a/src/packages/ce/src/datatype/components/inputs/datatype/DataTypeTypeInputTreeComponent.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import {EditableJSONEntry} from "./DataTypeTypeInputComponent" -import React from "react" -import {IconChevronDown, IconChevronUp} from "@tabler/icons-react" -import {LiteralValue} from "@code0-tech/sagittarius-graphql-types" -import {Badge, Flex, hashToColor, Text} from "@code0-tech/pictor"; - -export interface DataTypeJSONInputTreeComponentProps { - object: LiteralValue - parentKey?: string - isRoot?: boolean - onEntryClick: (entry: EditableJSONEntry) => void - collapsedState: Record - setCollapsedState: (path: string[], collapsed: boolean) => void - path?: string[] - activePath?: string[] | null - onDoubleClick?: (path: string[], isCollapsed: boolean) => void - parentColor?: string -} - -export const DataTypeTypeInputTreeComponent: React.FC = (props) => { - const { - object, - parentKey, - isRoot = !parentKey, - onEntryClick, - collapsedState, - setCollapsedState, - path = [], - activePath = null, - onDoubleClick, - parentColor, - } = props - - const value = isRoot ? object?.value : object - if (typeof value !== "object" || value === null) return null - - const clickTimeout = React.useRef(null) - const CLICK_DELAY = 250 // ms - - const handleClick = (entry: EditableJSONEntry) => { - if (clickTimeout.current) clearTimeout(clickTimeout.current) - clickTimeout.current = setTimeout(() => { - onEntryClick(entry) - clickTimeout.current = null - }, CLICK_DELAY) - } - - const handleDoubleClick = (currentPath: string[], isCollapsed: boolean) => { - if (clickTimeout.current) { - clearTimeout(clickTimeout.current) - clickTimeout.current = null - } - if (onDoubleClick) { - onDoubleClick(currentPath, isCollapsed) - } else { - setCollapsedState(currentPath, !isCollapsed) - } - } - - React.useEffect(() => { - const currentPath = path ?? [] - const pathKey = (isRoot ? ["root"] : currentPath).join(".") - if (currentPath.length > 1 && collapsedState[pathKey] === undefined) { - setCollapsedState(currentPath.length === 0 ? ["root"] : currentPath, true) - } - }, [path, isRoot, collapsedState, setCollapsedState]) - - const renderRoot = () => { - const currentPath = [...path] - const pathKey = "root" - const isCollapsed = collapsedState[pathKey] || false - const isCollapsable = typeof value === "object" && value !== null && (Array.isArray(value) ? value.length > 0 : Object.keys(value).length > 0) - const isActive = Array.isArray(activePath) && activePath.length === 0 && parentKey === undefined - const icon = isCollapsable ? (isCollapsed ? : ) : null - return ( -
{ - e.stopPropagation() - handleClick({key: pathKey, value: object, path: currentPath}) - }} - onDoubleClick={e => { - e.stopPropagation() - handleDoubleClick(currentPath, isCollapsed) - }} - aria-selected={isActive || undefined} - > - - {icon} - {Array.isArray(value) ? "is a list of type" : "is a nested object"} - - {!isCollapsed &&
    {renderNodes}
} -
- ) - } - - const renderNodes = Array.isArray(value) || (value && typeof value === 'object') - ? Object.entries(value as Record).map(([key, val]) => { - const currentPath = [...path, key] - const pathKey = currentPath.join(".") - const isCollapsed = collapsedState[pathKey] || false - const isActive = activePath && activePath.length > 0 && currentPath.join(".") === activePath.join(".") - const parentColorValue = parentColor ?? hashToColor("root") - const isCollapsable = typeof (val as any) === "object" && (val as any) !== null && (Array.isArray((val as any)) ? (val as any).length > 0 : Object.keys((val as any) ?? {}).length > 0) - const collapsableColor = isCollapsable ? hashToColor(pathKey) : parentColorValue - const icon = isCollapsable ? (isCollapsed ? : - ) : null - const label = isCollapsable ? ( - - {icon} - {Number.isNaN(Number(key)) ? ( - - {key} - - ) : null} - {Array.isArray((val as any)) ? "is a list of type" : "is a nested object"} - - ) : ( - - {Number.isNaN(Number(key)) ? ( - <> - - {key} - - is a field of type - - ) : null} - - {typeof val === "string" ? "Text" : typeof val === "boolean" ? "Boolean" : "Number"} - - - ) - const childTree = isCollapsable && !isCollapsed ? ( - - ) : null - return ( -
  • -
    { - e.stopPropagation() - handleClick({key, value: val as LiteralValue, path: currentPath}) - }} - onDoubleClick={e => { - e.stopPropagation() - handleDoubleClick(currentPath, isCollapsed) - }} - > - {label} - {childTree} -
    -
  • - ) - }) - : null - - const rootNode = renderRoot() - const nodes = rootNode && isRoot ? [rootNode] : renderNodes - const validNodes = (nodes ?? []).filter(Boolean) - if (validNodes.length === 0) return null - return
      {validNodes}
    -} \ No newline at end of file diff --git a/src/packages/ce/src/datatype/hooks/DataType.value.hook.tsx b/src/packages/ce/src/datatype/hooks/DataType.value.hook.tsx index 7276738..965090f 100644 --- a/src/packages/ce/src/datatype/hooks/DataType.value.hook.tsx +++ b/src/packages/ce/src/datatype/hooks/DataType.value.hook.tsx @@ -3,7 +3,7 @@ import {useService, useStore} from "@code0-tech/pictor"; import {FunctionService} from "@edition/function/services/Function.service"; import {FlowService} from "@edition/flow/services/Flow.service"; import {DatatypeService} from "@edition/datatype/services/Datatype.service"; -import {useTypeExtractionAction, useValueExtractionAction} from "@edition/flow/components/FlowWorkerProvider"; +import {useNodeTypeExtractionAction, useValueExtractionAction} from "@edition/flow/components/FlowWorkerProvider"; import React, {startTransition} from "react"; export const useValue = ( @@ -20,7 +20,7 @@ export const useValue = ( const dataTypeStore = useStore(DatatypeService) const dataTypeService = useService(DatatypeService) - const typeAction = useTypeExtractionAction() + const typeAction = useNodeTypeExtractionAction() const valueAction = useValueExtractionAction() const [types, setTypes] = React.useState(null); const [value, setValue] = React.useState(null); diff --git a/src/packages/ce/src/flow/components/Flow.worker.js b/src/packages/ce/src/flow/components/Flow.worker.js index 90c22f1..a3efa9b 100644 --- a/src/packages/ce/src/flow/components/Flow.worker.js +++ b/src/packages/ce/src/flow/components/Flow.worker.js @@ -1,7 +1,7 @@ import { getFlowValidation, getNodeSuggestions, - getReferenceSuggestions, + getReferenceSuggestions, getTypeFromValue, getTypesFromNode, getValueFromType, getValueSuggestions } from "@code0-tech/triangulum"; @@ -41,9 +41,12 @@ addEventListener("message", (event) => { case 'node_suggestions': result = getNodeSuggestions(payload.type, payload.functions, payload.dataTypes); break; - case 'type_extraction': + case 'node_type_extraction': result = getTypesFromNode(payload.node, payload.functions, payload.dataTypes); break; + case 'type_extraction': + result = getTypeFromValue(payload.value, payload.dataTypes); + break; case 'value_extraction': result = getValueFromType(payload.type, payload.dataTypes); break; diff --git a/src/packages/ce/src/flow/components/FlowWorkerProvider.tsx b/src/packages/ce/src/flow/components/FlowWorkerProvider.tsx index 0396800..8833c38 100644 --- a/src/packages/ce/src/flow/components/FlowWorkerProvider.tsx +++ b/src/packages/ce/src/flow/components/FlowWorkerProvider.tsx @@ -1,5 +1,5 @@ import React from "react" -import {DataType, Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import {DataType, Flow, FunctionDefinition, LiteralValue, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; interface Deferred { resolve: (value: any) => void @@ -11,6 +11,7 @@ type FlowWorkerActions = | "value_suggestions" | "reference_suggestions" | "node_suggestions" + | "node_type_extraction" | "type_extraction" | "value_extraction" @@ -39,7 +40,7 @@ interface FlowWorkerNodeSuggestionsPayload { dataTypes: DataType[] } -interface FlowWorkerTypeExtractionPayload { +interface FlowWorkerNodeTypeExtractionPayload { node: NodeFunction functions: FunctionDefinition[] dataTypes: DataType[] @@ -50,12 +51,17 @@ interface FlowWorkerValueExtractionPayload { dataTypes: DataType[] } +interface FlowWorkerTypeExtractionPayload { + value: LiteralValue + dataTypes: DataType[] +} + type FlowWorkerPayload = FlowWorkerValidationPayload | FlowWorkerValueSuggestionsPayload | FlowWorkerReferenceSuggestionsPayload | FlowWorkerNodeSuggestionsPayload - | FlowWorkerTypeExtractionPayload + | FlowWorkerNodeTypeExtractionPayload | FlowWorkerValueExtractionPayload interface WorkerContextType { @@ -150,8 +156,11 @@ export const useReferenceSuggestionsAction = () => export const useNodeSuggestionsAction = () => useWorkerAction("node_suggestions"); +export const useNodeTypeExtractionAction = () => + useWorkerAction("node_type_extraction"); + export const useTypeExtractionAction = () => - useWorkerAction("type_extraction"); + useWorkerAction("type_extraction"); export const useValueExtractionAction = () => useWorkerAction("value_extraction"); \ No newline at end of file diff --git a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts index dc47ff0..275b68e 100644 --- a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts @@ -93,10 +93,9 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj node.parameters?.nodes?.forEach((param, index) => { const parameterValue = param?.value; const parameterDefinition = functionService.getById(node.functionDefinition?.id!!)?.parameterDefinitions?.nodes?.find(p => p?.id === param?.parameterDefinition?.id); - const variant = getTypeVariant(types.parameters[index], dataTypeService.values()); + const variant = getTypeVariant(types.parameters[index], dataTypeService.values())[0].variant; if (!parameterValue) return - //@ts-ignore if (variant === DataTypeVariant.NODE) { if (parameterValue && parameterValue.__typename === "NodeFunctionIdWrapper") { diff --git a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts index c95e07a..c44f242 100644 --- a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts @@ -165,7 +165,7 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], const value = param?.value; if (!value || value.__typename !== "NodeFunctionIdWrapper") return; - const variant = getTypeVariant(types.parameters[index], dataTypeService.values()); + const variant = getTypeVariant(types.parameters[index], dataTypeService.values())[0].variant; if (variant === DataTypeVariant.NODE) { const groupId = `${nodeId}-group-${groupCounter++}`; diff --git a/src/packages/ce/src/function/hooks/FunctionSuggestion.hook.tsx b/src/packages/ce/src/function/hooks/FunctionSuggestion.hook.tsx index 7e46415..00fb056 100644 --- a/src/packages/ce/src/function/hooks/FunctionSuggestion.hook.tsx +++ b/src/packages/ce/src/function/hooks/FunctionSuggestion.hook.tsx @@ -8,7 +8,7 @@ import {FunctionSuggestion} from "@edition/function/components/suggestion/Functi import {FunctionService} from "@edition/function/services/Function.service"; import {FlowService} from "@edition/flow/services/Flow.service"; import {DatatypeService} from "@edition/datatype/services/Datatype.service"; -import {useTypeExtractionAction} from "@edition/flow/components/FlowWorkerProvider"; +import {useNodeTypeExtractionAction} from "@edition/flow/components/FlowWorkerProvider"; //TODO: deep type search //TODO: calculate FUNCTION_COMBINATION deepness max 2 @@ -26,7 +26,7 @@ export const useSuggestions = ( const dataTypeStore = useStore(DatatypeService) const dataTypeService = useService(DatatypeService) - const {execute} = useTypeExtractionAction() + const {execute} = useNodeTypeExtractionAction() const [types, setTypes] = React.useState(null); const node = React.useMemo(